Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 806a1de120 | |||
| b71777a130 | |||
| 57f6fafcdf | |||
| 52bdfbbdbd | |||
| d68fb4a425 | |||
| 1aecaac78b | |||
| 5b3a4d8584 | |||
| d4010c19e2 | |||
| d0c43465c7 |
@@ -6,11 +6,10 @@
|
||||
-->
|
||||
<moko-platform xmlns="https://standards.mokoconsulting.tech/moko-platform/1.0" schema-version="1.0">
|
||||
<identity>
|
||||
<name>MokoJoomHero</name>
|
||||
<display-name>Package - MokoJoomHero</display-name>
|
||||
<name>Module - MokoJoomHero</name>
|
||||
<org>MokoConsulting</org>
|
||||
<description>A Joomla Module designed to provide a random image from a folder with content on top as a Hero.</description>
|
||||
<version>01.08.00</version>
|
||||
<version>01.07.00</version>
|
||||
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
|
||||
</identity>
|
||||
<governance>
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Release
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# PATH: /.mokogitea/workflows/auto-bump.yml
|
||||
# VERSION: 09.02.00
|
||||
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
|
||||
|
||||
name: "Universal: Auto Version Bump"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- rc
|
||||
- 'feature/**'
|
||||
- 'patch/**'
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
bump:
|
||||
name: Version Bump
|
||||
runs-on: release
|
||||
if: >-
|
||||
!contains(github.event.head_commit.message, '[skip ci]') &&
|
||||
!contains(github.event.head_commit.message, '[skip bump]') &&
|
||||
!startsWith(github.event.head_commit.message, 'Merge pull request')
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup moko-platform tools
|
||||
run: |
|
||||
if ! command -v composer &> /dev/null; then
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||
fi
|
||||
if [ -d "/opt/moko-platform/cli" ]; then
|
||||
echo "MOKO_CLI=/opt/moko-platform/cli" >> "$GITHUB_ENV"
|
||||
else
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \
|
||||
/tmp/moko-platform-api
|
||||
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
|
||||
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Bump version
|
||||
run: |
|
||||
php ${MOKO_CLI}/version_auto_bump.php \
|
||||
--path . --branch "${GITHUB_REF_NAME}" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
--repo-url "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||
@@ -131,19 +131,6 @@ jobs:
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||
|
||||
- name: Check for merge conflict markers
|
||||
run: |
|
||||
CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true)
|
||||
if [ -n "$CONFLICTS" ]; then
|
||||
echo "::error::Merge conflict markers found — aborting release"
|
||||
echo "## Release Blocked: Conflict Markers" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
echo "No conflict markers found"
|
||||
|
||||
- name: Setup moko-platform tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Maintenance
|
||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
|
||||
# PATH: /templates/workflows/cascade-dev.yml.template
|
||||
# VERSION: 02.00.00
|
||||
# BRIEF: Forward-merge main → all open branches after every push to main
|
||||
#
|
||||
# +========================================================================+
|
||||
# | CASCADE MAIN → ALL BRANCHES |
|
||||
# +========================================================================+
|
||||
# | |
|
||||
# | Triggers on every push to main (PR merges, bot commits, etc.) |
|
||||
# | |
|
||||
# | 1. List all branches matching: dev, rc/*, beta/*, alpha/* |
|
||||
# | 2. For each: create PR (main → branch), auto-merge if clean |
|
||||
# | 3. On conflict: leave PR open for manual resolution |
|
||||
# | |
|
||||
# +========================================================================+
|
||||
|
||||
name: "Universal: Cascade Main → Dev"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
cascade:
|
||||
name: Cascade main → branches
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
!contains(github.event.head_commit.message, '[skip ci]') &&
|
||||
!contains(github.event.head_commit.message, '[skip cascade]')
|
||||
|
||||
steps:
|
||||
- name: Discover target branches
|
||||
id: branches
|
||||
env:
|
||||
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
run: |
|
||||
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
|
||||
# Fetch all branches (paginated)
|
||||
PAGE=1
|
||||
ALL_BRANCHES=""
|
||||
while true; do
|
||||
BATCH=$(curl -sS \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
"${API}/branches?page=${PAGE}&limit=50" \
|
||||
| jq -r '.[].name // empty')
|
||||
[ -z "$BATCH" ] && break
|
||||
ALL_BRANCHES="$ALL_BRANCHES $BATCH"
|
||||
PAGE=$((PAGE + 1))
|
||||
done
|
||||
|
||||
# Filter to cascade targets: dev, dev/*, rc/*, beta/*, alpha/*
|
||||
TARGETS=""
|
||||
for BRANCH in $ALL_BRANCHES; do
|
||||
case "$BRANCH" in
|
||||
dev|dev/*|rc/*|beta/*|alpha/*)
|
||||
TARGETS="$TARGETS $BRANCH"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
TARGETS=$(echo "$TARGETS" | xargs) # trim whitespace
|
||||
|
||||
if [ -z "$TARGETS" ]; then
|
||||
echo "targets=" >> "$GITHUB_OUTPUT"
|
||||
echo "ℹ️ No cascade target branches found"
|
||||
else
|
||||
echo "targets=$TARGETS" >> "$GITHUB_OUTPUT"
|
||||
COUNT=$(echo "$TARGETS" | wc -w)
|
||||
echo "📋 Found ${COUNT} target branch(es): ${TARGETS}"
|
||||
fi
|
||||
|
||||
- name: Cascade to all target branches
|
||||
if: steps.branches.outputs.targets != ''
|
||||
env:
|
||||
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
run: |
|
||||
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
SHORT_SHA="${GITHUB_SHA:0:7}"
|
||||
TARGETS="${{ steps.branches.outputs.targets }}"
|
||||
|
||||
SUCCESS=0
|
||||
CONFLICTS=0
|
||||
SKIPPED=0
|
||||
FAILED=0
|
||||
|
||||
for BRANCH in $TARGETS; do
|
||||
echo ""
|
||||
echo "═══ main → ${BRANCH} ═══"
|
||||
|
||||
# Check if branch is already up to date
|
||||
ENCODED_BRANCH=$(echo "$BRANCH" | sed 's|/|%2F|g')
|
||||
RESPONSE=$(curl -sS \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
"${API}/compare/${ENCODED_BRANCH}...main")
|
||||
|
||||
AHEAD=$(echo "$RESPONSE" | jq '.total_commits // 0')
|
||||
|
||||
if [ "$AHEAD" -eq 0 ]; then
|
||||
echo " ✅ Already up to date"
|
||||
SKIPPED=$((SKIPPED + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
echo " ℹ️ main is ${AHEAD} commit(s) ahead"
|
||||
|
||||
# Check for existing cascade PR
|
||||
EXISTING=$(curl -sS \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
"${API}/pulls?state=open&head=${GITEA_ORG}:main&base=${ENCODED_BRANCH}&limit=1")
|
||||
|
||||
EXISTING_COUNT=$(echo "$EXISTING" | jq 'length')
|
||||
PR_NUMBER=""
|
||||
|
||||
if [ "$EXISTING_COUNT" -gt 0 ]; then
|
||||
PR_NUMBER=$(echo "$EXISTING" | jq -r '.[0].number')
|
||||
echo " ℹ️ Reusing existing PR #${PR_NUMBER}"
|
||||
else
|
||||
# Create cascade PR
|
||||
PR_RESPONSE=$(curl -sS -w "\n%{http_code}" \
|
||||
-X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"title\": \"chore: cascade main → ${BRANCH} (${SHORT_SHA}) [skip ci]\",
|
||||
\"body\": \"## Automatic cascade\\n\\nForward-merging \`main\` (${SHORT_SHA}) into \`${BRANCH}\`.\\n\\nIf conflicts exist, resolve manually and merge.\\n\\n> Auto-created by **Cascade Main → Dev**.\",
|
||||
\"head\": \"main\",
|
||||
\"base\": \"${BRANCH}\"
|
||||
}" \
|
||||
"${API}/pulls")
|
||||
|
||||
HTTP_CODE=$(echo "$PR_RESPONSE" | tail -1)
|
||||
BODY=$(echo "$PR_RESPONSE" | sed '$d')
|
||||
PR_NUMBER=$(echo "$BODY" | jq -r '.number // empty')
|
||||
|
||||
if [ "$HTTP_CODE" != "201" ] || [ -z "$PR_NUMBER" ]; then
|
||||
MSG=$(echo "$BODY" | jq -r '.message // .' 2>/dev/null | head -1)
|
||||
echo " ❌ Failed to create PR (HTTP ${HTTP_CODE}): ${MSG}"
|
||||
FAILED=$((FAILED + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
echo " ✅ Created PR #${PR_NUMBER}"
|
||||
fi
|
||||
|
||||
# Try auto-merge
|
||||
PR_DATA=$(curl -sS \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
"${API}/pulls/${PR_NUMBER}")
|
||||
|
||||
MERGEABLE=$(echo "$PR_DATA" | jq -r '.mergeable // false')
|
||||
|
||||
if [ "$MERGEABLE" != "true" ]; then
|
||||
echo " ⚠️ Conflicts — PR #${PR_NUMBER} left open"
|
||||
CONFLICTS=$((CONFLICTS + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
MERGE_RESPONSE=$(curl -sS -w "\n%{http_code}" \
|
||||
-X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"Do\": \"merge\",
|
||||
\"merge_message_field\": \"chore: cascade main → ${BRANCH} [skip ci]\",
|
||||
\"delete_branch_after_merge\": false
|
||||
}" \
|
||||
"${API}/pulls/${PR_NUMBER}/merge")
|
||||
|
||||
MERGE_HTTP=$(echo "$MERGE_RESPONSE" | tail -1)
|
||||
|
||||
if [ "$MERGE_HTTP" = "200" ] || [ "$MERGE_HTTP" = "204" ]; then
|
||||
echo " ✅ Merged — ${BRANCH} is in sync"
|
||||
SUCCESS=$((SUCCESS + 1))
|
||||
else
|
||||
MERGE_BODY=$(echo "$MERGE_RESPONSE" | sed '$d')
|
||||
echo " ⚠️ Merge failed (HTTP ${MERGE_HTTP}) — PR #${PR_NUMBER} left open"
|
||||
CONFLICTS=$((CONFLICTS + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
echo "════════════════════════════════════════"
|
||||
echo " ✅ Merged: ${SUCCESS}"
|
||||
echo " ⚠️ Conflicts: ${CONFLICTS}"
|
||||
echo " ⏭️ Up to date: ${SKIPPED}"
|
||||
echo " ❌ Failed: ${FAILED}"
|
||||
echo "════════════════════════════════════════"
|
||||
|
||||
if [ "$FAILED" -gt 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
@@ -5,7 +5,7 @@
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Automation
|
||||
# VERSION: 01.08.00
|
||||
# VERSION: 01.07.00
|
||||
# BRIEF: Auto-create feature branch when an issue is opened
|
||||
|
||||
name: "Universal: Issue Branch"
|
||||
|
||||
+236
-277
@@ -1,277 +1,236 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.CI
|
||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
|
||||
# PATH: /templates/workflows/universal/pr-check.yml.template
|
||||
# VERSION: 09.23.00
|
||||
# BRIEF: PR gate — branch policy + code validation before merge
|
||||
|
||||
name: "Universal: PR Check"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, edited]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
# ── Branch Policy ──────────────────────────────────────────────────────
|
||||
branch-policy:
|
||||
name: Branch Policy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check branch merge target
|
||||
run: |
|
||||
HEAD="${{ github.head_ref }}"
|
||||
BASE="${{ github.base_ref }}"
|
||||
|
||||
echo "PR: ${HEAD} → ${BASE}"
|
||||
|
||||
ALLOWED=true
|
||||
REASON=""
|
||||
|
||||
case "$HEAD" in
|
||||
feature/*|feat/*)
|
||||
if [ "$BASE" != "dev" ]; then
|
||||
ALLOWED=false
|
||||
REASON="Feature branches must target 'dev', not '${BASE}'"
|
||||
fi
|
||||
;;
|
||||
fix/*|bugfix/*)
|
||||
if [ "$BASE" != "dev" ]; then
|
||||
ALLOWED=false
|
||||
REASON="Fix branches must target 'dev', not '${BASE}'"
|
||||
fi
|
||||
;;
|
||||
patch/*)
|
||||
if [ "$BASE" != "dev" ] && [ "$BASE" != "rc" ]; then
|
||||
ALLOWED=false
|
||||
REASON="Patch branches must target 'dev' or 'rc', not '${BASE}'"
|
||||
fi
|
||||
;;
|
||||
hotfix/*)
|
||||
if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then
|
||||
ALLOWED=false
|
||||
REASON="Hotfix branches can only target 'dev' or 'main', not '${BASE}'"
|
||||
fi
|
||||
;;
|
||||
rc)
|
||||
if [ "$BASE" != "main" ]; then
|
||||
ALLOWED=false
|
||||
REASON="RC branch can only merge into 'main', not '${BASE}'"
|
||||
fi
|
||||
;;
|
||||
dev)
|
||||
if [ "$BASE" != "main" ]; then
|
||||
ALLOWED=false
|
||||
REASON="Dev branch can only merge into 'main', not '${BASE}'"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$ALLOWED" = false ]; then
|
||||
echo "::error::${REASON}"
|
||||
echo "## Branch Policy Violation" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "${REASON}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Branch policy: OK (${HEAD} → ${BASE})"
|
||||
echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ── Code Validation ────────────────────────────────────────────────────
|
||||
validate:
|
||||
name: Validate PR
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check for merge conflict markers
|
||||
run: |
|
||||
CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true)
|
||||
if [ -n "$CONFLICTS" ]; then
|
||||
echo "::error::Merge conflict markers found in source files"
|
||||
echo "## Conflict Markers Found" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
echo "No conflict markers found"
|
||||
|
||||
- name: Detect platform
|
||||
id: platform
|
||||
run: |
|
||||
# Read platform from XML manifest (<platform> tag) or plain text fallback
|
||||
PLATFORM=$(sed -n 's/.*<platform>\([^<]*\)<\/platform>.*/\1/p' .mokogitea/manifest.xml 2>/dev/null | head -1)
|
||||
[ -z "$PLATFORM" ] && PLATFORM=$(cat .mokogitea/manifest.xml 2>/dev/null | tr -d '[:space:]')
|
||||
[ -z "$PLATFORM" ] && PLATFORM="generic"
|
||||
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Setup PHP
|
||||
if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr'
|
||||
run: |
|
||||
if ! command -v php &> /dev/null; then
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
- name: PHP syntax check
|
||||
if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr'
|
||||
run: |
|
||||
ERRORS=0
|
||||
while IFS= read -r -d '' file; do
|
||||
if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
done < <(find . -name "*.php" -not -path "./.git/*" -not -path "./vendor/*" -print0)
|
||||
echo "PHP lint: ${ERRORS} error(s)"
|
||||
[ "$ERRORS" -eq 0 ] || { echo "::error::PHP syntax errors found"; exit 1; }
|
||||
|
||||
- name: Validate platform manifest
|
||||
run: |
|
||||
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||
case "$PLATFORM" in
|
||||
joomla)
|
||||
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
||||
if [ -z "$MANIFEST" ]; then
|
||||
echo "::warning::No Joomla manifest found (WaaS site)"
|
||||
exit 0
|
||||
fi
|
||||
echo "Manifest: ${MANIFEST}"
|
||||
if command -v php &> /dev/null; then
|
||||
php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('$MANIFEST'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}" || { echo "::error::Manifest XML is malformed"; exit 1; }
|
||||
fi
|
||||
for ELEMENT in name version description; do
|
||||
grep -q "<${ELEMENT}>" "$MANIFEST" || { echo "::error::Missing <${ELEMENT}> in manifest"; exit 1; }
|
||||
done
|
||||
echo "Joomla manifest valid"
|
||||
;;
|
||||
dolibarr)
|
||||
MOD_FILE=$(find . -maxdepth 4 -name "mod*.class.php" ! -path "./.git/*" -exec grep -l 'extends DolibarrModules' {} \; 2>/dev/null | head -1)
|
||||
if [ -z "$MOD_FILE" ]; then
|
||||
echo "::error::No mod*.class.php found"
|
||||
exit 1
|
||||
fi
|
||||
echo "Dolibarr module: ${MOD_FILE}"
|
||||
;;
|
||||
*)
|
||||
echo "Generic platform — no manifest validation"
|
||||
;;
|
||||
esac
|
||||
|
||||
- name: Check update stream format
|
||||
run: |
|
||||
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||
case "$PLATFORM" in
|
||||
joomla)
|
||||
if [ -f "updates.xml" ]; then
|
||||
if command -v php &> /dev/null; then
|
||||
php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('updates.xml'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}" || { echo "::error::updates.xml is malformed"; exit 1; }
|
||||
fi
|
||||
echo "updates.xml valid"
|
||||
fi
|
||||
;;
|
||||
dolibarr)
|
||||
[ -f "update.txt" ] && echo "update.txt present" || echo "::warning::No update.txt"
|
||||
;;
|
||||
esac
|
||||
|
||||
- name: Check changelog has unreleased entry
|
||||
run: |
|
||||
if [ ! -f "CHANGELOG.md" ]; then
|
||||
echo "::warning::No CHANGELOG.md found"
|
||||
exit 0
|
||||
fi
|
||||
# Check for content under [Unreleased] section
|
||||
if ! grep -q "## \[Unreleased\]" CHANGELOG.md; then
|
||||
echo "::error::CHANGELOG.md missing [Unreleased] section"
|
||||
exit 1
|
||||
fi
|
||||
# Check there's at least one entry (Added/Changed/Fixed/Removed) under Unreleased
|
||||
UNRELEASED_CONTENT=$(sed -n '/## \[Unreleased\]/,/## \[/p' CHANGELOG.md | grep -cE '^\s*-\s' || true)
|
||||
if [ "$UNRELEASED_CONTENT" -eq 0 ]; then
|
||||
echo "::error::CHANGELOG.md [Unreleased] section has no entries. Add a changelog entry describing your changes."
|
||||
echo "## Changelog Check: Failed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "The \`[Unreleased]\` section in CHANGELOG.md has no entries." >> $GITHUB_STEP_SUMMARY
|
||||
echo "Add a line like \`- Description of your change\` under a heading (\`### Added\`, \`### Changed\`, \`### Fixed\`, etc.)" >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
echo "Changelog: ${UNRELEASED_CONTENT} entry/entries in [Unreleased]"
|
||||
|
||||
- name: Verify package source
|
||||
run: |
|
||||
SOURCE_DIR="src"
|
||||
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
||||
if [ ! -d "$SOURCE_DIR" ]; then
|
||||
echo "::warning::No src/ or htdocs/ directory"
|
||||
exit 0
|
||||
fi
|
||||
FILE_COUNT=$(find "$SOURCE_DIR" -type f | wc -l)
|
||||
echo "Source: ${FILE_COUNT} files"
|
||||
[ "$FILE_COUNT" -gt 0 ] || { echo "::error::Source directory is empty"; exit 1; }
|
||||
|
||||
# ── Pre-Release RC Build ─────────────────────────────────────────────────
|
||||
pre-release:
|
||||
name: Build RC Package
|
||||
runs-on: ubuntu-latest
|
||||
needs: [branch-policy, validate]
|
||||
|
||||
steps:
|
||||
- name: Trigger RC pre-release
|
||||
env:
|
||||
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
REPO: ${{ github.repository }}
|
||||
BRANCH: ${{ github.head_ref }}
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
run: |
|
||||
curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
|
||||
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ── Issue Reporter ──────────────────────────────────────────────────────
|
||||
report-issues:
|
||||
name: Report Issues
|
||||
runs-on: ubuntu-latest
|
||||
needs: [branch-policy, validate]
|
||||
if: >-
|
||||
always() &&
|
||||
needs.validate.result == 'failure'
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: automation/ci-issue-reporter.sh
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: "File issue for PR validation failure"
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
run: |
|
||||
chmod +x automation/ci-issue-reporter.sh
|
||||
./automation/ci-issue-reporter.sh \
|
||||
--gate "PR Validation" \
|
||||
--workflow "PR Check" \
|
||||
--severity error \
|
||||
--details "PR validation failed (syntax, manifest, changelog, or source checks). See the CI run for the specific check that failed."
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.CI
|
||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
|
||||
# PATH: /templates/workflows/universal/pr-check.yml.template
|
||||
# VERSION: 05.00.00
|
||||
# BRIEF: PR gate — branch policy + code validation before merge
|
||||
|
||||
name: "Universal: PR Check"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, edited]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
# ── Branch Policy ──────────────────────────────────────────────────────
|
||||
branch-policy:
|
||||
name: Branch Policy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check branch merge target
|
||||
run: |
|
||||
HEAD="${{ github.head_ref }}"
|
||||
BASE="${{ github.base_ref }}"
|
||||
|
||||
echo "PR: ${HEAD} → ${BASE}"
|
||||
|
||||
ALLOWED=true
|
||||
REASON=""
|
||||
|
||||
case "$HEAD" in
|
||||
feature/*|feat/*)
|
||||
if [ "$BASE" != "dev" ]; then
|
||||
ALLOWED=false
|
||||
REASON="Feature branches must target 'dev', not '${BASE}'"
|
||||
fi
|
||||
;;
|
||||
fix/*|bugfix/*)
|
||||
if [ "$BASE" != "dev" ]; then
|
||||
ALLOWED=false
|
||||
REASON="Fix branches must target 'dev', not '${BASE}'"
|
||||
fi
|
||||
;;
|
||||
patch/*)
|
||||
if [ "$BASE" != "dev" ] && [ "$BASE" != "rc" ]; then
|
||||
ALLOWED=false
|
||||
REASON="Patch branches must target 'dev' or 'rc', not '${BASE}'"
|
||||
fi
|
||||
;;
|
||||
hotfix/*)
|
||||
if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then
|
||||
ALLOWED=false
|
||||
REASON="Hotfix branches can only target 'dev' or 'main', not '${BASE}'"
|
||||
fi
|
||||
;;
|
||||
rc)
|
||||
if [ "$BASE" != "main" ]; then
|
||||
ALLOWED=false
|
||||
REASON="RC branch can only merge into 'main', not '${BASE}'"
|
||||
fi
|
||||
;;
|
||||
dev)
|
||||
if [ "$BASE" != "main" ]; then
|
||||
ALLOWED=false
|
||||
REASON="Dev branch can only merge into 'main', not '${BASE}'"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$ALLOWED" = false ]; then
|
||||
echo "::error::${REASON}"
|
||||
echo "## Branch Policy Violation" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "${REASON}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Branch policy: OK (${HEAD} → ${BASE})"
|
||||
echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ── Code Validation ────────────────────────────────────────────────────
|
||||
validate:
|
||||
name: Validate PR
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Detect platform
|
||||
id: platform
|
||||
run: |
|
||||
# Read platform from XML manifest (<platform> tag) or plain text fallback
|
||||
PLATFORM=$(sed -n 's/.*<platform>\([^<]*\)<\/platform>.*/\1/p' .mokogitea/manifest.xml 2>/dev/null | head -1)
|
||||
[ -z "$PLATFORM" ] && PLATFORM=$(cat .mokogitea/manifest.xml 2>/dev/null | tr -d '[:space:]')
|
||||
[ -z "$PLATFORM" ] && PLATFORM="generic"
|
||||
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Setup PHP
|
||||
if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr'
|
||||
run: |
|
||||
if ! command -v php &> /dev/null; then
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
- name: PHP syntax check
|
||||
if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr'
|
||||
run: |
|
||||
ERRORS=0
|
||||
while IFS= read -r -d '' file; do
|
||||
if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
done < <(find . -name "*.php" -not -path "./.git/*" -not -path "./vendor/*" -print0)
|
||||
echo "PHP lint: ${ERRORS} error(s)"
|
||||
[ "$ERRORS" -eq 0 ] || { echo "::error::PHP syntax errors found"; exit 1; }
|
||||
|
||||
- name: Validate platform manifest
|
||||
run: |
|
||||
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||
case "$PLATFORM" in
|
||||
joomla)
|
||||
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
||||
if [ -z "$MANIFEST" ]; then
|
||||
echo "::warning::No Joomla manifest found (WaaS site)"
|
||||
exit 0
|
||||
fi
|
||||
echo "Manifest: ${MANIFEST}"
|
||||
if command -v php &> /dev/null; then
|
||||
php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('$MANIFEST'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}" || { echo "::error::Manifest XML is malformed"; exit 1; }
|
||||
fi
|
||||
for ELEMENT in name version description; do
|
||||
grep -q "<${ELEMENT}>" "$MANIFEST" || { echo "::error::Missing <${ELEMENT}> in manifest"; exit 1; }
|
||||
done
|
||||
echo "Joomla manifest valid"
|
||||
;;
|
||||
dolibarr)
|
||||
MOD_FILE=$(find . -maxdepth 4 -name "mod*.class.php" ! -path "./.git/*" -exec grep -l 'extends DolibarrModules' {} \; 2>/dev/null | head -1)
|
||||
if [ -z "$MOD_FILE" ]; then
|
||||
echo "::error::No mod*.class.php found"
|
||||
exit 1
|
||||
fi
|
||||
echo "Dolibarr module: ${MOD_FILE}"
|
||||
;;
|
||||
*)
|
||||
echo "Generic platform — no manifest validation"
|
||||
;;
|
||||
esac
|
||||
|
||||
- name: Check update stream format
|
||||
run: |
|
||||
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||
case "$PLATFORM" in
|
||||
joomla)
|
||||
if [ -f "updates.xml" ]; then
|
||||
if command -v php &> /dev/null; then
|
||||
php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('updates.xml'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}" || { echo "::error::updates.xml is malformed"; exit 1; }
|
||||
fi
|
||||
echo "updates.xml valid"
|
||||
fi
|
||||
;;
|
||||
dolibarr)
|
||||
[ -f "update.txt" ] && echo "update.txt present" || echo "::warning::No update.txt"
|
||||
;;
|
||||
esac
|
||||
|
||||
- name: Check changelog has unreleased entry
|
||||
run: |
|
||||
if [ ! -f "CHANGELOG.md" ]; then
|
||||
echo "::warning::No CHANGELOG.md found"
|
||||
exit 0
|
||||
fi
|
||||
# Check for content under [Unreleased] section
|
||||
if ! grep -q "## \[Unreleased\]" CHANGELOG.md; then
|
||||
echo "::error::CHANGELOG.md missing [Unreleased] section"
|
||||
exit 1
|
||||
fi
|
||||
# Check there's at least one entry (Added/Changed/Fixed/Removed) under Unreleased
|
||||
UNRELEASED_CONTENT=$(sed -n '/## \[Unreleased\]/,/## \[/p' CHANGELOG.md | grep -cE '^\s*-\s' || true)
|
||||
if [ "$UNRELEASED_CONTENT" -eq 0 ]; then
|
||||
echo "::error::CHANGELOG.md [Unreleased] section has no entries. Add a changelog entry describing your changes."
|
||||
echo "## Changelog Check: Failed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "The \`[Unreleased]\` section in CHANGELOG.md has no entries." >> $GITHUB_STEP_SUMMARY
|
||||
echo "Add a line like \`- Description of your change\` under a heading (\`### Added\`, \`### Changed\`, \`### Fixed\`, etc.)" >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
echo "Changelog: ${UNRELEASED_CONTENT} entry/entries in [Unreleased]"
|
||||
|
||||
- name: Verify package source
|
||||
run: |
|
||||
SOURCE_DIR="src"
|
||||
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
||||
if [ ! -d "$SOURCE_DIR" ]; then
|
||||
echo "::warning::No src/ or htdocs/ directory"
|
||||
exit 0
|
||||
fi
|
||||
FILE_COUNT=$(find "$SOURCE_DIR" -type f | wc -l)
|
||||
echo "Source: ${FILE_COUNT} files"
|
||||
[ "$FILE_COUNT" -gt 0 ] || { echo "::error::Source directory is empty"; exit 1; }
|
||||
|
||||
# ── Pre-Release RC Build ─────────────────────────────────────────────────
|
||||
pre-release:
|
||||
name: Build RC Package
|
||||
runs-on: ubuntu-latest
|
||||
needs: [branch-policy, validate]
|
||||
|
||||
steps:
|
||||
- name: Trigger RC pre-release
|
||||
env:
|
||||
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
REPO: ${{ github.repository }}
|
||||
BRANCH: ${{ github.head_ref }}
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
run: |
|
||||
curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
|
||||
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Release
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# PATH: /templates/workflows/universal/pre-release.yml.template
|
||||
# VERSION: 05.01.00
|
||||
# BRIEF: Manual pre-release -- builds dev/alpha/beta/rc packages from any branch
|
||||
|
||||
name: "Universal: Pre-Release"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches:
|
||||
- dev
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
stability:
|
||||
description: 'Pre-release channel'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- development
|
||||
- alpha
|
||||
- beta
|
||||
- release-candidate
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: "Build Pre-Release (${{ inputs.stability || 'development' }})"
|
||||
runs-on: release
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'dev')
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
|
||||
- name: Setup moko-platform tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||
run: |
|
||||
if ! command -v composer &> /dev/null; then
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||
fi
|
||||
# Always fetch latest CLI tools — never use stale cache from previous runs
|
||||
rm -rf /tmp/moko-platform-api
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||
/tmp/moko-platform-api
|
||||
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
|
||||
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Detect platform
|
||||
id: platform
|
||||
run: |
|
||||
php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
||||
|
||||
- name: Resolve metadata and bump version
|
||||
id: meta
|
||||
run: |
|
||||
STABILITY="${{ inputs.stability || 'development' }}"
|
||||
|
||||
case "$STABILITY" in
|
||||
development) SUFFIX="-dev"; TAG="development" ;;
|
||||
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
|
||||
beta) SUFFIX="-beta"; TAG="beta" ;;
|
||||
release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
|
||||
esac
|
||||
|
||||
# Read current version (bump already handled by push workflow)
|
||||
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null)
|
||||
[ -z "$VERSION" ] && VERSION="00.00.01"
|
||||
|
||||
# Strip any existing suffix from version before applying stability
|
||||
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
|
||||
|
||||
php ${MOKO_CLI}/version_set_platform.php \
|
||||
--path . --version "$VERSION" --branch "${{ github.ref_name }}" --stability "$STABILITY" 2>/dev/null || true
|
||||
|
||||
# Verify version consistency across all files
|
||||
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
||||
|
||||
# Update VERSION variable with suffix
|
||||
if [ -n "$SUFFIX" ]; then
|
||||
VERSION="${VERSION}${SUFFIX}"
|
||||
fi
|
||||
|
||||
# Commit version bump
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||
git add -A
|
||||
git diff --cached --quiet || {
|
||||
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
|
||||
git push origin HEAD 2>&1
|
||||
}
|
||||
|
||||
# Auto-detect element via manifest_element.php
|
||||
php ${MOKO_CLI}/manifest_element.php \
|
||||
--path . --version "$VERSION" --stability "$STABILITY" \
|
||||
--repo "${GITEA_REPO}" --github-output
|
||||
|
||||
# Read back element outputs
|
||||
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
||||
ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
||||
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
||||
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
|
||||
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
||||
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
|
||||
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
|
||||
|
||||
- name: Create release
|
||||
id: release
|
||||
run: |
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
php ${MOKO_CLI}/release_create.php \
|
||||
--path . --version "$VERSION" --tag "$TAG" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--repo "${GITEA_REPO}" --branch dev --prerelease
|
||||
|
||||
- name: Build package and upload
|
||||
id: package
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
php ${MOKO_CLI}/release_package.php \
|
||||
--path . --version "$VERSION" --tag "$TAG" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--repo "${GITEA_REPO}" --output /tmp || true
|
||||
|
||||
- name: Update updates.xml
|
||||
if: steps.platform.outputs.platform == 'joomla'
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
||||
|
||||
if [ ! -f "updates.xml" ]; then
|
||||
echo "No updates.xml -- skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
SHA_FLAG=""
|
||||
[ -n "$SHA256" ] && SHA_FLAG="--sha ${SHA256}"
|
||||
|
||||
php ${MOKO_CLI}/updates_xml_build.php \
|
||||
--path . --version "${VERSION}" --stability "${STABILITY}" \
|
||||
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
|
||||
${SHA_FLAG}
|
||||
|
||||
# Commit and push
|
||||
if ! git diff --quiet updates.xml 2>/dev/null; then
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git add updates.xml
|
||||
git commit -m "chore: update ${STABILITY} channel ${VERSION} [skip ci]"
|
||||
git push origin HEAD 2>&1 || echo "WARNING: push failed"
|
||||
fi
|
||||
|
||||
- name: "Sync updates.xml to all branches"
|
||||
if: steps.platform.outputs.platform == 'joomla'
|
||||
run: |
|
||||
CURRENT_BRANCH="${{ github.ref_name }}"
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
|
||||
for BRANCH in main dev; do
|
||||
[ "$BRANCH" = "$CURRENT_BRANCH" ] && continue
|
||||
echo "Syncing updates.xml -> ${BRANCH}"
|
||||
git fetch origin "${BRANCH}" 2>/dev/null || continue
|
||||
git checkout "origin/${BRANCH}" -- updates.xml 2>/dev/null || continue
|
||||
git checkout "${CURRENT_BRANCH}" -- updates.xml
|
||||
if ! git diff --quiet updates.xml 2>/dev/null; then
|
||||
git add updates.xml
|
||||
git commit -m "chore: sync updates.xml from ${CURRENT_BRANCH} [skip ci]"
|
||||
git push origin HEAD:refs/heads/${BRANCH} 2>&1 || echo "WARNING: push to ${BRANCH} failed"
|
||||
fi
|
||||
git checkout "${CURRENT_BRANCH}" 2>/dev/null
|
||||
done
|
||||
|
||||
- name: "Delete lesser pre-release channels (cascade)"
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
php ${MOKO_CLI}/release_cascade.php \
|
||||
--stability "${{ steps.meta.outputs.stability }}" \
|
||||
--token "${TOKEN}" \
|
||||
--api-base "${API_BASE}"
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
|
||||
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
||||
echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,312 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Universal
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# PATH: /templates/workflows/update-server.yml
|
||||
# VERSION: 05.00.00
|
||||
# BRIEF: Pre-release build + update server XML for dev/alpha/beta/rc branches
|
||||
#
|
||||
# Thin wrapper around moko-platform CLI tools.
|
||||
# Builds packages, updates updates.xml, and optionally deploys via SFTP.
|
||||
#
|
||||
# Joomla filters update entries by the user's "Minimum Stability" setting.
|
||||
|
||||
name: "Update Server"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'dev'
|
||||
- 'dev/**'
|
||||
- 'alpha/**'
|
||||
- 'beta/**'
|
||||
- 'rc/**'
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'htdocs/**'
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches:
|
||||
- 'dev'
|
||||
- 'dev/**'
|
||||
- 'alpha/**'
|
||||
- 'beta/**'
|
||||
- 'rc/**'
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'htdocs/**'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
stability:
|
||||
description: 'Stability tag'
|
||||
required: true
|
||||
default: 'development'
|
||||
type: choice
|
||||
options:
|
||||
- development
|
||||
- alpha
|
||||
- beta
|
||||
- rc
|
||||
- stable
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
update-xml:
|
||||
name: Update Server
|
||||
runs-on: release
|
||||
if: >-
|
||||
github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' || github.event_name == 'push'
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup moko-platform tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||
COMPOSER_AUTH: '{"http-basic":{"git.mokoconsulting.tech":{"username":"token","password":"${{ secrets.MOKOGITEA_TOKEN }}"}}}'
|
||||
run: |
|
||||
if ! command -v composer &> /dev/null; then
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||
fi
|
||||
# Always fetch latest CLI tools — never use stale cache from previous runs
|
||||
rm -rf /tmp/moko-platform
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||
/tmp/moko-platform 2>/dev/null || true
|
||||
if [ -d "/tmp/moko-platform" ] && [ -f "/tmp/moko-platform/composer.json" ]; then
|
||||
cd /tmp/moko-platform && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
||||
fi
|
||||
echo "MOKO_CLI=/tmp/moko-platform/cli" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Detect platform
|
||||
id: platform
|
||||
run: php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
||||
|
||||
- name: Resolve stability and bump version
|
||||
id: meta
|
||||
run: |
|
||||
BRANCH="${{ github.ref_name }}"
|
||||
|
||||
# Configure git for bot pushes
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||
|
||||
# Auto-bump patch version
|
||||
php ${MOKO_CLI}/version_bump.php --path . 2>/dev/null || true
|
||||
|
||||
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "0.0.0")
|
||||
|
||||
# Strip any existing suffix before applying stability
|
||||
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
|
||||
|
||||
# Determine stability from branch or manual input
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
STABILITY="${{ inputs.stability }}"
|
||||
elif [[ "$BRANCH" == rc/* ]]; then
|
||||
STABILITY="rc"
|
||||
elif [[ "$BRANCH" == beta/* ]]; then
|
||||
STABILITY="beta"
|
||||
elif [[ "$BRANCH" == alpha/* ]]; then
|
||||
STABILITY="alpha"
|
||||
else
|
||||
STABILITY="development"
|
||||
fi
|
||||
|
||||
# Version suffix per stability stream
|
||||
case "$STABILITY" in
|
||||
development) SUFFIX="-dev"; TAG="development" ;;
|
||||
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
|
||||
beta) SUFFIX="-beta"; TAG="beta" ;;
|
||||
rc) SUFFIX="-rc"; TAG="release-candidate" ;;
|
||||
*) SUFFIX=""; TAG="stable" ;;
|
||||
esac
|
||||
|
||||
# Propagate version with stability suffix to all manifest files
|
||||
php ${MOKO_CLI}/version_set_platform.php \
|
||||
--path . --version "$VERSION" --branch "$BRANCH" --stability "$STABILITY" 2>/dev/null || true
|
||||
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
||||
|
||||
# Re-read version (now includes suffix from version_set_platform)
|
||||
if [ -n "$SUFFIX" ]; then
|
||||
VERSION="${VERSION}${SUFFIX}"
|
||||
fi
|
||||
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
||||
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "display_version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Commit version bump if changed
|
||||
git add -A
|
||||
git diff --cached --quiet || {
|
||||
git commit -m "chore(version): auto-bump ${VERSION} [skip ci]" \
|
||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||
git push
|
||||
}
|
||||
|
||||
- name: Create release and upload package
|
||||
id: package
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
|
||||
# Create or update Gitea release
|
||||
php ${MOKO_CLI}/release_create.php \
|
||||
--path . --version "$VERSION" --tag "$TAG" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease
|
||||
|
||||
# Build package and upload
|
||||
php ${MOKO_CLI}/release_package.php \
|
||||
--path . --version "$VERSION" --tag "$TAG" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--repo "${GITEA_REPO}" --output /tmp || true
|
||||
|
||||
- name: Update updates.xml
|
||||
if: steps.platform.outputs.platform == 'joomla'
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
||||
|
||||
if [ ! -f "updates.xml" ]; then
|
||||
echo "No updates.xml — skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
SHA_FLAG=""
|
||||
[ -n "$SHA256" ] && SHA_FLAG="--sha ${SHA256}"
|
||||
|
||||
php ${MOKO_CLI}/updates_xml_build.php \
|
||||
--path . --version "${VERSION}" --stability "${STABILITY}" \
|
||||
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
|
||||
${SHA_FLAG}
|
||||
|
||||
# Commit and push updates.xml
|
||||
git add updates.xml
|
||||
git diff --cached --quiet || {
|
||||
git commit -m "chore: update ${STABILITY} channel ${VERSION} [skip ci]"
|
||||
git push
|
||||
}
|
||||
|
||||
- name: Sync updates.xml to main
|
||||
if: github.ref_name != 'main' && steps.platform.outputs.platform == 'joomla'
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
GITEA_TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
FILE_SHA=$(curl -sf -H "Authorization: token ${GITEA_TOKEN}" \
|
||||
"${API_BASE}/contents/updates.xml?ref=main" | python3 -c "import sys,json; print(json.load(sys.stdin).get('sha',''))" 2>/dev/null || true)
|
||||
|
||||
if [ -n "$FILE_SHA" ] && [ -f "updates.xml" ]; then
|
||||
python3 -c "
|
||||
import base64, json, urllib.request, sys
|
||||
with open('updates.xml', 'rb') as f:
|
||||
content = base64.b64encode(f.read()).decode()
|
||||
payload = json.dumps({
|
||||
'content': content,
|
||||
'sha': '${FILE_SHA}',
|
||||
'message': 'chore: sync updates.xml from ${{ steps.meta.outputs.stability }} [skip ci]',
|
||||
'branch': 'main'
|
||||
}).encode()
|
||||
req = urllib.request.Request(
|
||||
'${API_BASE}/contents/updates.xml',
|
||||
data=payload, method='PUT',
|
||||
headers={
|
||||
'Authorization': 'token ${GITEA_TOKEN}',
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
try:
|
||||
urllib.request.urlopen(req)
|
||||
print('updates.xml synced to main')
|
||||
except Exception as e:
|
||||
print(f'WARNING: sync to main failed: {e}', file=sys.stderr)
|
||||
"
|
||||
fi
|
||||
|
||||
- name: SFTP deploy to dev server
|
||||
if: contains(github.ref, 'dev/') || github.ref == 'refs/heads/dev'
|
||||
env:
|
||||
DEV_HOST: ${{ vars.DEV_FTP_HOST }}
|
||||
DEV_PATH: ${{ vars.DEV_FTP_PATH }}
|
||||
DEV_SUFFIX: ${{ vars.DEV_FTP_SUFFIX }}
|
||||
DEV_USER: ${{ vars.DEV_FTP_USERNAME }}
|
||||
DEV_PORT: ${{ vars.DEV_FTP_PORT }}
|
||||
DEV_KEY: ${{ secrets.DEV_FTP_KEY }}
|
||||
DEV_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
|
||||
run: |
|
||||
# Permission check: admin or maintain role required
|
||||
ACTOR="${{ github.actor }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
|
||||
PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
"${API_BASE}/collaborators/${ACTOR}/permission" 2>/dev/null | \
|
||||
python3 -c "import sys,json; print(json.load(sys.stdin).get('permission','read'))" 2>/dev/null || echo "read")
|
||||
case "$PERMISSION" in
|
||||
admin|maintain|write) ;;
|
||||
*)
|
||||
echo "Deploy denied: ${ACTOR} has '${PERMISSION}' — requires admin, maintain, or write"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
[ -z "$DEV_HOST" ] || [ -z "$DEV_PATH" ] && { echo "DEV FTP not configured — skipping SFTP"; exit 0; }
|
||||
|
||||
SOURCE_DIR="src"
|
||||
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
||||
[ ! -d "$SOURCE_DIR" ] && exit 0
|
||||
|
||||
PORT="${DEV_PORT:-22}"
|
||||
REMOTE="${DEV_PATH%/}"
|
||||
[ -n "$DEV_SUFFIX" ] && REMOTE="${REMOTE}/${DEV_SUFFIX#/}"
|
||||
|
||||
printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \
|
||||
"$DEV_HOST" "$PORT" "$DEV_USER" "$REMOTE" > /tmp/sftp-config.json
|
||||
if [ -n "$DEV_KEY" ]; then
|
||||
echo "$DEV_KEY" > /tmp/deploy_key && chmod 600 /tmp/deploy_key
|
||||
printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json
|
||||
else
|
||||
printf ',"password":"%s"}' "$DEV_PASS" >> /tmp/sftp-config.json
|
||||
fi
|
||||
|
||||
PLATFORM=$(php ${MOKO_CLI}/platform_detect.php --path . 2>/dev/null || true)
|
||||
if [ "$PLATFORM" = "waas-component" ] && [ -f "${MOKO_CLI}/../deploy/deploy-joomla.php" ]; then
|
||||
php ${MOKO_CLI}/../deploy/deploy-joomla.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
||||
elif [ -f "${MOKO_CLI}/../deploy/deploy-sftp.php" ]; then
|
||||
php ${MOKO_CLI}/../deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
||||
fi
|
||||
rm -f /tmp/deploy_key /tmp/sftp-config.json
|
||||
echo "SFTP deploy to dev complete" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
DISPLAY="${{ steps.meta.outputs.display_version }}"
|
||||
echo "## Update Server" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Stability | \`${STABILITY}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Version | \`${DISPLAY}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
+4
-12
@@ -1,14 +1,16 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
|
||||
## [01.07.00] --- 2026-05-30
|
||||
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
Version format: `XX.YY.ZZ` (zero-padded semver).
|
||||
|
||||
## [01.08.00] --- 2026-06-04
|
||||
## [01.06.00] --- 2026-05-30
|
||||
|
||||
## [01.04.00] - 2026-05-30
|
||||
|
||||
@@ -34,13 +36,3 @@ Version format: `XX.YY.ZZ` (zero-padded semver).
|
||||
- Module title renders inside the hero card as `<h2>`, respecting Joomla's Show Title toggle
|
||||
- IntersectionObserver pauses/resumes videos when the hero scrolls out of/into the viewport (YouTube, Vimeo, and native `<video>`)
|
||||
- YouTube embeds include `enablejsapi=1` for postMessage playback control
|
||||
|
||||
## [01.01.00] - 2026-05-30
|
||||
|
||||
### Changed
|
||||
- Migrated all workflow and template paths from `.github/` to `.mokogitea/`
|
||||
- Template source paths updated
|
||||
- HCL definition files removed -- Template repos are now the canonical source
|
||||
|
||||
### Added
|
||||
- `branch-cleanup.yml`: auto-delete merged feature branches after PR merge
|
||||
|
||||
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code when working with this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
**MokoJoomHero** -- Random hero image slideshow/video with content overlay for Joomla
|
||||
**MokoJoomHero** -- A Joomla Module designed to provide a random image from a folder with content on top as a Hero.
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
@@ -32,40 +32,20 @@ composer install # Install PHP dependencies
|
||||
|
||||
## Architecture
|
||||
|
||||
This is a Joomla **package** extension (`pkg_mokojoomhero`) containing two sub-extensions:
|
||||
|
||||
### mod_mokojoomhero (Site Module)
|
||||
- Random hero image slideshow or background video with content overlay
|
||||
- Supports image folders, YouTube, Vimeo, local video, solid colour, gradient
|
||||
- Configurable overlay, text alignment, card animation, parallax, content animations
|
||||
- Works independently — no plugin dependency required
|
||||
|
||||
### plg_system_mokojoomhero (System Plugin)
|
||||
- Placeholder for future system-level features
|
||||
- Auto-enabled on package install via `pkg_script.php`
|
||||
- Namespace: `Joomla\Plugin\System\MokoJoomHero`
|
||||
|
||||
### Key files
|
||||
- `src/pkg_mokojoomhero.xml` — package manifest
|
||||
- `src/pkg_script.php` — auto-enables system plugin on install
|
||||
- `src/packages/mod_mokojoomhero/` — hero module source
|
||||
- `src/packages/plg_system_mokojoomhero/` — system plugin source
|
||||
- `updates.xml` — Joomla update server (includes legacy module entries for migration)
|
||||
This is a Joomla extension. Key directories:
|
||||
- `src/` -- extension source (deployed to Joomla)
|
||||
- `src/*.xml` -- manifest file (version, files, params)
|
||||
- `src/src/` or `src/services/` -- PHP classes
|
||||
- `src/language/` -- translation strings
|
||||
- `src/media/` -- CSS/JS/images
|
||||
|
||||
## Rules
|
||||
|
||||
- **Workflow directory**: `.mokogitea/` (not `.gitea/` or `.github/`)
|
||||
|
||||
- **Never commit** `.claude/`, `.mcp.json`, `TODO.md`, or `*.min.css`/`*.min.js`
|
||||
- **Attribution**: use `Authored-by: Moko Consulting` in commits
|
||||
- **Branch strategy**: develop on `dev`, merge to `main` for release
|
||||
- **Minification**: handled at build time (CI)
|
||||
- **Minification**: handled at build time (CI) and runtime (MokoMinifyHelper for Joomla templates)
|
||||
- **Wiki**: documentation lives in the Gitea wiki, not in `docs/` files
|
||||
- **Standards**: this repo follows [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)
|
||||
|
||||
## Coding Standards
|
||||
|
||||
- PHP 8.1+ minimum
|
||||
- Joomla 5/6 DI container pattern: `services/provider.php` → Extension class
|
||||
- Legacy stub `.php` file required for plugin loader but empty
|
||||
- `SubscriberInterface` for event subscription (not `on*` method naming)
|
||||
- SPDX license headers on all PHP files
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
DEFGROUP:
|
||||
INGROUP: Project.Documentation
|
||||
REPO:
|
||||
VERSION: 01.08.00
|
||||
VERSION: 01.07.00
|
||||
PATH: ./CODE_OF_CONDUCT.md
|
||||
BRIEF: Reference + packaging repo for Moko Consulting Developer GPT Other Default
|
||||
-->
|
||||
|
||||
+108
-141
@@ -1,161 +1,128 @@
|
||||
# Contributing to Moko Consulting Projects
|
||||
<!--
|
||||
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
Thank you for your interest in contributing. All Moko Consulting repositories follow this universal workflow and version policy.
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
## Branching Workflow
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This program 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License (./LICENSE).
|
||||
|
||||
# FILE INFORMATION
|
||||
DEFGROUP: {{DEFGROUP}}
|
||||
INGROUP: Project.Documentation
|
||||
REPO: https://github.com/mokoconsulting-tech/MokoJoomHero
|
||||
VERSION: 01.07.00
|
||||
PATH: ./CONTRIBUTING.md
|
||||
BRIEF: How to contribute; branch strategy, commit conventions, PR workflow, and release pipeline
|
||||
-->
|
||||
|
||||
# Contributing
|
||||
|
||||
Thank you for your interest in contributing to **MokoJoomHero**!
|
||||
|
||||
This repository is governed by **[MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards)** — the authoritative source of coding standards, workflows, and policies for all Moko Consulting repositories.
|
||||
|
||||
## Branch Strategy
|
||||
|
||||
| Branch | Purpose | Deploys To |
|
||||
|--------|---------|------------|
|
||||
| `main` | Bleeding edge — all development merges here | CI only |
|
||||
| `dev/XX.YY.ZZ` | Feature development | Dev server (version: "development") |
|
||||
| `version/XX.YY` | Stable frozen snapshot | Demo + RS servers |
|
||||
|
||||
### Development Workflow
|
||||
|
||||
```
|
||||
feature/* ──PR──> dev ──draft PR──> (renamed to rc) ──merge──> main
|
||||
1. Create branch: git checkout -b dev/XX.YY.ZZ/my-feature
|
||||
2. Develop + test (dev server auto-deploys on push)
|
||||
3. Open PR → main (squash merge only)
|
||||
4. Auto-release (version branch + tag + GitHub Release created automatically)
|
||||
```
|
||||
|
||||
### Step by step
|
||||
### Branch Naming
|
||||
|
||||
1. **Create a feature branch** from `dev`:
|
||||
```bash
|
||||
git checkout dev && git pull
|
||||
git checkout -b feature/my-change
|
||||
```
|
||||
| Prefix | Use |
|
||||
|--------|-----|
|
||||
| `dev/XX.YY.ZZ` | Feature development (e.g., `dev/02.00.00/add-extrafields`) |
|
||||
| `version/XX.YY` | Stable release (auto-created, never manually pushed) |
|
||||
| `chore/` | Automated sync branches (managed by MokoStandards) |
|
||||
|
||||
2. **Work and commit** on your feature branch. Push to origin.
|
||||
> **Never use** `feature/`, `hotfix/`, or `release/` prefixes — they are not part of the MokoStandards branch strategy.
|
||||
|
||||
3. **Open a PR**: `feature/my-change` → `dev`. After review and checks, merge it.
|
||||
## Commit Conventions
|
||||
|
||||
4. **When ready for release**, open a **draft PR**: `dev` → `main`.
|
||||
- This automatically renames the source branch to `rc` (release candidate)
|
||||
- An RC pre-release is built and uploaded
|
||||
|
||||
5. **Alpha and beta branches** are created by manually renaming the branch before the RC stage:
|
||||
- Rename `dev` to `alpha` for early testing → alpha pre-release is built
|
||||
- Rename `alpha` to `beta` for feature-complete testing → beta pre-release is built
|
||||
- When the draft PR is created, the branch is renamed to `rc`
|
||||
|
||||
6. **Once PR checks pass** on the `rc` branch, mark the PR as ready and merge to `main`.
|
||||
|
||||
7. **Merging to main** triggers the stable release pipeline:
|
||||
- Minor version bump (e.g., `02.09.xx` → `02.10.00`)
|
||||
- Stability suffix stripped (clean version)
|
||||
- Gitea release created with ZIP/tar.gz packages
|
||||
- `updates.xml` updated (Joomla extensions)
|
||||
- `dev` branch recreated from `main`
|
||||
|
||||
### Branch summary
|
||||
|
||||
| Branch | Purpose | Created by |
|
||||
|--------|---------|-----------|
|
||||
| `feature/*` | New features and fixes | Developer |
|
||||
| `dev` | Integration branch | Auto-recreated after release |
|
||||
| `alpha` | Alpha pre-release testing | Manual rename from `dev` |
|
||||
| `beta` | Beta pre-release testing | Manual rename from `alpha` |
|
||||
| `rc` | Release candidate | Auto-renamed on draft PR to main |
|
||||
| `main` | Stable releases | Protected, merge only |
|
||||
| `version/XX.YY.ZZ` | Archived release snapshots | Auto-created by CI |
|
||||
|
||||
### Protected branches
|
||||
|
||||
| Branch | Direct push | Merge via |
|
||||
|--------|------------|-----------|
|
||||
| `main` | Blocked (CI bot whitelisted) | PR merge only |
|
||||
| `dev` | Blocked (CI bot whitelisted) | PR merge from feature/* |
|
||||
| `rc` | Blocked (CI bot whitelisted) | Auto-created on draft PR |
|
||||
| `alpha` | Blocked (CI bot whitelisted) | Manual rename |
|
||||
| `beta` | Blocked (CI bot whitelisted) | Manual rename |
|
||||
| `feature/*` | Open | N/A (source branch) |
|
||||
|
||||
## Version Policy
|
||||
|
||||
### Format
|
||||
|
||||
All versions use `XX.YY.ZZ` — three two-digit segments, zero-padded:
|
||||
|
||||
- **XX** — Major version (breaking changes)
|
||||
- **YY** — Minor version (new features, bumped on release to main)
|
||||
- **ZZ** — Patch version (auto-incremented on every push to dev/feature branches)
|
||||
|
||||
Rollover: patch `99` → `00` increments minor; minor `99` → `00` increments major.
|
||||
|
||||
### Stability suffixes
|
||||
|
||||
Each branch appends a suffix to indicate stability:
|
||||
|
||||
| Branch | Suffix | Example |
|
||||
|--------|--------|---------|
|
||||
| `main` | (none) | `02.09.00` |
|
||||
| `dev` | `-dev` | `02.09.01-dev` |
|
||||
| `feature/*` | `-dev` | `02.09.01-dev` |
|
||||
| `alpha` | `-alpha` | `02.09.01-alpha` |
|
||||
| `beta` | `-beta` | `02.09.01-beta` |
|
||||
| `rc` | `-rc` | `02.09.01-rc` |
|
||||
|
||||
### Auto version bump
|
||||
|
||||
On every push to `dev`, `feature/*`, or `patch/*`:
|
||||
|
||||
1. Patch version incremented
|
||||
2. Stability suffix `-dev` applied
|
||||
3. All version-bearing files updated (manifests, CHANGELOG, PHP headers, etc.)
|
||||
4. Commit created with `[skip ci]` to avoid loops
|
||||
|
||||
### Release version flow
|
||||
|
||||
Version bumps happen at specific release events:
|
||||
|
||||
| Event | Bump | Example |
|
||||
|-------|------|---------|
|
||||
| Feature merged to dev | Patch bump after dev release | `02.09.01-dev` → release → `02.09.02-dev` |
|
||||
| Dev promoted to RC | Minor bump | `02.09.02-dev` → `02.10.00-rc` |
|
||||
| RC merged to main | Minor bump | `02.10.00-rc` → `02.11.00` (stable) |
|
||||
| Dev recreated from main | Patch bump | `02.11.00` → `02.11.01-dev` |
|
||||
|
||||
### Release stream copies
|
||||
|
||||
When a higher-stability release is published, copies are created for all lesser streams with the same base version:
|
||||
|
||||
- **RC `02.10.00-rc`** also creates: `02.10.00-dev`, `02.10.00-alpha`, `02.10.00-beta`
|
||||
- **Stable `02.11.00`** also creates: `02.11.00-dev`, `02.11.00-alpha`, `02.11.00-beta`, `02.11.00-rc`
|
||||
|
||||
This ensures Joomla sites on ANY stability channel see the update (Joomla only shows versions higher than what's installed).
|
||||
|
||||
### Version files
|
||||
|
||||
The version tools update all files containing version stamps:
|
||||
|
||||
- `.mokogitea/manifest.xml` (canonical source)
|
||||
- Joomla XML manifests (`<version>` tag)
|
||||
- `README.md`, `CHANGELOG.md` (`VERSION:` pattern)
|
||||
- `package.json`, `pyproject.toml`
|
||||
- Any text file with a `VERSION: XX.YY.ZZ` label
|
||||
|
||||
Files synced from other repos (with a `# REPO:` header) are not touched.
|
||||
|
||||
## Code Standards
|
||||
|
||||
- **PHP**: PSR-12, tabs for indentation
|
||||
- **Copyright**: all files must include the Moko Consulting copyright header
|
||||
- **License**: SPDX identifier `GPL-3.0-or-later` (or as specified per repo)
|
||||
- **Attribution**: use `Authored-by: Moko Consulting` in commits, not individual names
|
||||
|
||||
## Commit Messages
|
||||
|
||||
Use conventional commit format:
|
||||
Use [conventional commits](https://www.conventionalcommits.org/):
|
||||
|
||||
```
|
||||
type(scope): short description
|
||||
|
||||
Optional body with context.
|
||||
|
||||
Authored-by: Moko Consulting
|
||||
feat(scope): add new extrafield for invoice tracking
|
||||
fix(sql): correct column type in llx_mytable
|
||||
docs(readme): update installation instructions
|
||||
chore(deps): bump enterprise library to 04.02.30
|
||||
```
|
||||
|
||||
Types: `feat`, `fix`, `chore`, `docs`, `style`, `refactor`, `test`, `ci`
|
||||
**Valid types:** `feat` | `fix` | `docs` | `chore` | `ci` | `refactor` | `style` | `test` | `perf` | `revert` | `build`
|
||||
|
||||
Special flags in commit messages:
|
||||
- `[skip ci]` — skip all CI workflows
|
||||
- `[skip bump]` — skip auto version bump only
|
||||
## Pull Request Workflow
|
||||
|
||||
## Reporting Issues
|
||||
1. **Branch** from `main` using `dev/XX.YY.ZZ/description` format
|
||||
2. **Bump** the patch version in `README.md` before opening the PR
|
||||
3. **Title** must be a valid conventional commit subject line
|
||||
4. **Target** `main` — squash merge only (merge commits are disabled)
|
||||
5. **CI checks** must pass before merge
|
||||
|
||||
Use the repository's issue tracker with the appropriate template.
|
||||
### What Happens on Merge
|
||||
|
||||
When your PR is merged to `main`, these workflows run automatically:
|
||||
|
||||
1. **sync-version-on-merge** — auto-bumps patch version, propagates to all file headers
|
||||
2. **auto-release** — creates `version/XX.YY` branch, git tag, and GitHub Release
|
||||
3. **deploy-demo / deploy-rs** — deploys to demo and RS servers (if `src/**` changed)
|
||||
|
||||
## Coding Standards
|
||||
|
||||
All contributions must follow [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards):
|
||||
|
||||
| Standard | Reference |
|
||||
|----------|-----------|
|
||||
| Coding Style | [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) |
|
||||
| File Headers | [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) |
|
||||
| Branching | [branch-release-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branch-release-strategy.md) |
|
||||
| Merge Strategy | [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) |
|
||||
| Scripting | [scripting-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/scripting-standards.md) |
|
||||
| Build & Release | [build-release.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/workflows/build-release.md) |
|
||||
|
||||
## PR Checklist
|
||||
|
||||
- [ ] Branch named `dev/XX.YY.ZZ/description`
|
||||
- [ ] Patch version bumped in `README.md`
|
||||
- [ ] Conventional commit format for PR title
|
||||
- [ ] All new files have FILE INFORMATION headers
|
||||
- [ ] `declare(strict_types=1)` in all PHP files
|
||||
- [ ] PHPDoc on all public methods
|
||||
- [ ] Tests pass
|
||||
- [ ] CHANGELOG.md updated
|
||||
- [ ] No secrets, tokens, or credentials committed
|
||||
|
||||
## Custom Workflows
|
||||
|
||||
Place repo-specific workflows in `.github/workflows/custom/` — they are **never overwritten or deleted** by MokoStandards sync:
|
||||
|
||||
```
|
||||
.github/workflows/
|
||||
├── deploy-dev.yml ← Synced from MokoStandards
|
||||
├── auto-release.yml ← Synced from MokoStandards
|
||||
└── custom/ ← Your custom workflows (safe)
|
||||
└── my-custom-ci.yml
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
By contributing, you agree that your contributions will be licensed under the [GPL-3.0-or-later](LICENSE) license.
|
||||
|
||||
---
|
||||
|
||||
*Moko Consulting <hello@mokoconsulting.tech>*
|
||||
*This file is synced from [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). Do not edit directly — changes will be overwritten on the next sync.*
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
# Extension Configuration
|
||||
EXTENSION_NAME := mokojoomhero
|
||||
EXTENSION_TYPE := package
|
||||
EXTENSION_TYPE := module
|
||||
# Options: module, plugin, component, package, template
|
||||
EXTENSION_VERSION := 1.0.0
|
||||
|
||||
@@ -26,7 +26,7 @@ PLUGIN_GROUP := system
|
||||
# Options: system, content, user, authentication, etc.
|
||||
|
||||
# Directories
|
||||
SRC_DIR := src
|
||||
SRC_DIR := .
|
||||
BUILD_DIR := build
|
||||
DIST_DIR := dist
|
||||
DOCS_DIR := docs
|
||||
@@ -155,30 +155,62 @@ clean: ## Clean build artifacts
|
||||
@echo "$(COLOR_GREEN)✓ Build artifacts cleaned$(COLOR_RESET)"
|
||||
|
||||
.PHONY: build
|
||||
build: clean ## Build extension package
|
||||
@echo "$(COLOR_BLUE)Building Joomla package extension...$(COLOR_RESET)"
|
||||
@mkdir -p $(DIST_DIR) $(BUILD_DIR)/packages
|
||||
|
||||
@# --- Build each sub-extension as a separate ZIP ---
|
||||
@for EXT_DIR in $(SRC_DIR)/packages/*/; do \
|
||||
EXT_NAME=$$(basename "$$EXT_DIR"); \
|
||||
[ "$$EXT_NAME" = "index.html" ] && continue; \
|
||||
echo " Packaging $$EXT_NAME..."; \
|
||||
cd "$$EXT_DIR" && $(ZIP) -r "$(CURDIR)/$(BUILD_DIR)/packages/$${EXT_NAME}.zip" . \
|
||||
-x "*.git*" -x "*/index.html" 2>/dev/null; \
|
||||
cd "$(CURDIR)"; \
|
||||
done
|
||||
|
||||
@# --- Build the outer package ZIP ---
|
||||
@echo " Assembling pkg_$(EXTENSION_NAME)..."
|
||||
@cp $(SRC_DIR)/pkg_mokojoomhero.xml $(BUILD_DIR)/pkg_mokojoomhero.xml
|
||||
@cp $(SRC_DIR)/pkg_script.php $(BUILD_DIR)/pkg_script.php
|
||||
@cd $(BUILD_DIR) && $(ZIP) -r "$(CURDIR)/$(DIST_DIR)/pkg_$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip" \
|
||||
pkg_mokojoomhero.xml pkg_script.php packages/
|
||||
|
||||
@echo "$(COLOR_GREEN)✓ Package created: $(DIST_DIR)/pkg_$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip$(COLOR_RESET)"
|
||||
@echo " Contents:"
|
||||
@unzip -l "$(DIST_DIR)/pkg_$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip" | tail -n +4 | head -20
|
||||
build: clean validate ## Build extension package
|
||||
@echo "$(COLOR_BLUE)Building Joomla extension package...$(COLOR_RESET)"
|
||||
@mkdir -p $(DIST_DIR) $(BUILD_DIR)
|
||||
|
||||
# Determine package prefix based on extension type
|
||||
@case "$(EXTENSION_TYPE)" in \
|
||||
module) \
|
||||
PACKAGE_PREFIX="mod_$(EXTENSION_NAME)"; \
|
||||
BUILD_TARGET="$(BUILD_DIR)/$$PACKAGE_PREFIX"; \
|
||||
;; \
|
||||
plugin) \
|
||||
PACKAGE_PREFIX="plg_$(PLUGIN_GROUP)_$(EXTENSION_NAME)"; \
|
||||
BUILD_TARGET="$(BUILD_DIR)/$$PACKAGE_PREFIX"; \
|
||||
;; \
|
||||
component) \
|
||||
PACKAGE_PREFIX="com_$(EXTENSION_NAME)"; \
|
||||
BUILD_TARGET="$(BUILD_DIR)/$$PACKAGE_PREFIX"; \
|
||||
;; \
|
||||
package) \
|
||||
PACKAGE_PREFIX="pkg_$(EXTENSION_NAME)"; \
|
||||
BUILD_TARGET="$(BUILD_DIR)/$$PACKAGE_PREFIX"; \
|
||||
;; \
|
||||
template) \
|
||||
PACKAGE_PREFIX="tpl_$(EXTENSION_NAME)"; \
|
||||
BUILD_TARGET="$(BUILD_DIR)/$$PACKAGE_PREFIX"; \
|
||||
;; \
|
||||
*) \
|
||||
echo "$(COLOR_RED)✗ Unknown extension type: $(EXTENSION_TYPE)$(COLOR_RESET)"; \
|
||||
exit 1; \
|
||||
;; \
|
||||
esac; \
|
||||
\
|
||||
mkdir -p "$$BUILD_TARGET"; \
|
||||
\
|
||||
echo "Building $$PACKAGE_PREFIX..."; \
|
||||
\
|
||||
rsync -av --progress \
|
||||
--exclude='$(BUILD_DIR)' \
|
||||
--exclude='$(DIST_DIR)' \
|
||||
--exclude='.git*' \
|
||||
--exclude='vendor/' \
|
||||
--exclude='node_modules/' \
|
||||
--exclude='tests/' \
|
||||
--exclude='Makefile' \
|
||||
--exclude='composer.json' \
|
||||
--exclude='composer.lock' \
|
||||
--exclude='package.json' \
|
||||
--exclude='package-lock.json' \
|
||||
--exclude='phpunit.xml' \
|
||||
--exclude='*.md' \
|
||||
--exclude='.editorconfig' \
|
||||
. "$$BUILD_TARGET/"; \
|
||||
\
|
||||
cd $(BUILD_DIR) && $(ZIP) -r "../$(DIST_DIR)/$${PACKAGE_PREFIX}-$(EXTENSION_VERSION).zip" "$${PACKAGE_PREFIX}"; \
|
||||
\
|
||||
echo "$(COLOR_GREEN)✓ Package created: $(DIST_DIR)/$${PACKAGE_PREFIX}-$(EXTENSION_VERSION).zip$(COLOR_RESET)"
|
||||
|
||||
.PHONY: package
|
||||
package: build ## Alias for build
|
||||
@@ -293,15 +325,49 @@ security-check: ## Run security checks on dependencies
|
||||
$(NPM) audit || echo "$(COLOR_YELLOW)⚠ Vulnerabilities found$(COLOR_RESET)"; \
|
||||
fi
|
||||
|
||||
.PHONY: minify
|
||||
minify: ## Minify CSS/JS assets
|
||||
@echo "Minifying assets..."
|
||||
@MOKO_PLATFORM=$$(echo ../moko-platform $$HOME/moko-platform /opt/moko-platform | tr ' ' '\n' | while read p; do [ -d "$$p" ] && echo "$$p" && break; done); \
|
||||
if [ -n "$$MOKO_PLATFORM" ] && [ -f "$$MOKO_PLATFORM/build/minify.js" ]; then \
|
||||
node "$$MOKO_PLATFORM/build/minify.js" $(SRC_DIR); \
|
||||
else \
|
||||
echo "No minify script found"; \
|
||||
.PHONY: deploy
|
||||
deploy: ## Deploy to a Joomla site via SSH (usage: make deploy HOST=user@host WEBROOT=/path/to/joomla)
|
||||
@if [ -z "$(HOST)" ] || [ -z "$(WEBROOT)" ]; then \
|
||||
echo "$(COLOR_RED)✗ Usage: make deploy HOST=user@host WEBROOT=/path/to/joomla [KEY=~/.ssh/id_rsa]$(COLOR_RESET)"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@SSH_OPTS="-o StrictHostKeyChecking=no -o ConnectTimeout=10"; \
|
||||
if [ -n "$(KEY)" ]; then SSH_OPTS="$$SSH_OPTS -i $(KEY)"; fi; \
|
||||
echo "$(COLOR_BLUE)Deploying mod_$(EXTENSION_NAME) to $(HOST):$(WEBROOT)...$(COLOR_RESET)"; \
|
||||
ssh $$SSH_OPTS $(HOST) "\
|
||||
W=$(WEBROOT) && \
|
||||
cp -r \$$W/modules/mod_$(EXTENSION_NAME)/language/en-US/* /dev/null 2>&1; \
|
||||
true" && \
|
||||
for f in src/mod_mokojoomhero.php src/mod_mokojoomhero.xml src/script.php; do \
|
||||
scp $$SSH_OPTS $$f $(HOST):$(WEBROOT)/modules/mod_$(EXTENSION_NAME)/$$(basename $$f); \
|
||||
done && \
|
||||
scp -r $$SSH_OPTS src/tmpl/* $(HOST):$(WEBROOT)/modules/mod_$(EXTENSION_NAME)/tmpl/ && \
|
||||
scp -r $$SSH_OPTS src/language/* $(HOST):$(WEBROOT)/modules/mod_$(EXTENSION_NAME)/language/ && \
|
||||
scp $$SSH_OPTS src/media/joomla.asset.json $(HOST):$(WEBROOT)/media/mod_$(EXTENSION_NAME)/ && \
|
||||
scp -r $$SSH_OPTS src/media/css/* $(HOST):$(WEBROOT)/media/mod_$(EXTENSION_NAME)/css/ && \
|
||||
scp -r $$SSH_OPTS src/media/js/* $(HOST):$(WEBROOT)/media/mod_$(EXTENSION_NAME)/js/ && \
|
||||
ssh $$SSH_OPTS $(HOST) "\
|
||||
W=$(WEBROOT) && \
|
||||
mkdir -p \$$W/images/heroes && \
|
||||
for lang in en-US en-GB; do \
|
||||
for ini in mod_mokojoomhero.ini mod_mokojoomhero.sys.ini; do \
|
||||
src=\$$W/modules/mod_$(EXTENSION_NAME)/language/\$$lang/\$$ini; \
|
||||
if [ -f \$$src ]; then \
|
||||
cp \$$src \$$W/administrator/language/\$$lang/\$$ini 2>/dev/null; \
|
||||
cp \$$src \$$W/language/\$$lang/\$$ini 2>/dev/null; \
|
||||
fi; \
|
||||
done; \
|
||||
done && \
|
||||
echo 'OK'" && \
|
||||
echo "$(COLOR_GREEN)✓ Deployed to $(HOST)$(COLOR_RESET)"
|
||||
|
||||
.PHONY: deploy-all
|
||||
deploy-all: ## Deploy to all configured sites (requires SITES_FILE or inline)
|
||||
@echo "$(COLOR_BLUE)Deploying to all sites...$(COLOR_RESET)"
|
||||
@echo "$(COLOR_YELLOW)Usage: Create a sites.conf with HOST:WEBROOT per line, then:$(COLOR_RESET)"
|
||||
@echo " while IFS=: read -r host webroot; do"
|
||||
@echo " make deploy HOST=\$$host WEBROOT=\$$webroot KEY=path/to/key"
|
||||
@echo " done < sites.conf"
|
||||
|
||||
.PHONY: all
|
||||
all: install-deps validate test build ## Run complete build pipeline
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# FILE INFORMATION
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
||||
FILE: ./README.md
|
||||
VERSION: 01.08.00
|
||||
VERSION: 01.07.00
|
||||
BRIEF: MokoJoomHero - Joomla Module
|
||||
-->
|
||||
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ DEFGROUP: [PROJECT_NAME]
|
||||
INGROUP: [PROJECT_NAME].Documentation
|
||||
REPO: [REPOSITORY_URL]
|
||||
PATH: /SECURITY.md
|
||||
VERSION: 01.08.00
|
||||
VERSION: 01.07.00
|
||||
BRIEF: Security vulnerability reporting and handling policy
|
||||
-->
|
||||
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================================
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Automation.CI
|
||||
# INGROUP: moko-platform.Automation
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# PATH: /automation/ci-issue-reporter.sh
|
||||
# VERSION: 09.23.00
|
||||
# BRIEF: Creates or updates a Gitea issue when a CI gate fails.
|
||||
# Deduplicates by searching open issues with the "ci-auto" label
|
||||
# whose title matches the gate. If a matching issue exists, a comment
|
||||
# is appended instead of opening a duplicate.
|
||||
# ============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Defaults ────────────────────────────────────────────────────────────────
|
||||
GITEA_URL="${GITEA_URL:-https://git.mokoconsulting.tech}"
|
||||
GITEA_TOKEN="${GITEA_TOKEN:-}"
|
||||
REPO="${GITHUB_REPOSITORY:-}"
|
||||
RUN_URL="${GITHUB_SERVER_URL:-${GITEA_URL}}/${REPO}/actions/runs/${GITHUB_RUN_ID:-0}"
|
||||
LABEL_NAME="ci-auto"
|
||||
LABEL_COLOR="#e11d48"
|
||||
|
||||
GATE=""
|
||||
DETAILS=""
|
||||
SEVERITY="error"
|
||||
WORKFLOW=""
|
||||
|
||||
# ── Parse arguments ─────────────────────────────────────────────────────────
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: ci-issue-reporter.sh --gate NAME --details TEXT [OPTIONS]
|
||||
|
||||
Required:
|
||||
--gate CI gate name (e.g. "Code Quality", "Self-Health")
|
||||
--details Human-readable failure description
|
||||
|
||||
Optional:
|
||||
--severity "error" (default) or "warning"
|
||||
--workflow Workflow name for the issue title
|
||||
--repo owner/repo (default: \$GITHUB_REPOSITORY)
|
||||
--run-url URL to the CI run (auto-detected from env)
|
||||
--token Gitea API token (default: \$GITEA_TOKEN)
|
||||
--url Gitea base URL (default: \$GITEA_URL)
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--gate) GATE="$2"; shift 2 ;;
|
||||
--details) DETAILS="$2"; shift 2 ;;
|
||||
--severity) SEVERITY="$2"; shift 2 ;;
|
||||
--workflow) WORKFLOW="$2"; shift 2 ;;
|
||||
--repo) REPO="$2"; shift 2 ;;
|
||||
--run-url) RUN_URL="$2"; shift 2 ;;
|
||||
--token) GITEA_TOKEN="$2"; shift 2 ;;
|
||||
--url) GITEA_URL="$2"; shift 2 ;;
|
||||
-h|--help) usage ;;
|
||||
*) echo "Unknown option: $1"; usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -z "$GATE" ]] && { echo "ERROR: --gate is required"; usage; }
|
||||
[[ -z "$DETAILS" ]] && { echo "ERROR: --details is required"; usage; }
|
||||
[[ -z "$GITEA_TOKEN" ]] && { echo "ERROR: GITEA_TOKEN not set"; exit 1; }
|
||||
[[ -z "$REPO" ]] && { echo "ERROR: GITHUB_REPOSITORY not set"; exit 1; }
|
||||
|
||||
API="${GITEA_URL}/api/v1/repos/${REPO}"
|
||||
|
||||
# ── Build title ─────────────────────────────────────────────────────────────
|
||||
if [[ -n "$WORKFLOW" ]]; then
|
||||
TITLE="[CI] ${WORKFLOW}: ${GATE} failed"
|
||||
else
|
||||
TITLE="[CI] ${GATE} failed"
|
||||
fi
|
||||
|
||||
# ── Ensure label exists ─────────────────────────────────────────────────────
|
||||
ensure_label() {
|
||||
local exists
|
||||
exists=$(curl -sf -o /dev/null -w '%{http_code}' \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
"${API}/labels" 2>/dev/null || echo "000")
|
||||
|
||||
if [[ "$exists" == "200" ]]; then
|
||||
# Check if label already exists
|
||||
local found
|
||||
found=$(curl -sf \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
"${API}/labels" 2>/dev/null \
|
||||
| grep -o "\"name\":\"${LABEL_NAME}\"" || true)
|
||||
|
||||
if [[ -z "$found" ]]; then
|
||||
curl -sf -X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API}/labels" \
|
||||
-d "{\"name\":\"${LABEL_NAME}\",\"color\":\"${LABEL_COLOR}\",\"description\":\"Auto-created by CI issue reporter\"}" \
|
||||
> /dev/null 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Search for existing open issue ──────────────────────────────────────────
|
||||
find_existing_issue() {
|
||||
# URL-encode the gate name for the query
|
||||
local query
|
||||
query=$(printf '%s' "[CI] ${GATE}" | sed 's/ /%20/g; s/\[/%5B/g; s/\]/%5D/g')
|
||||
|
||||
local response
|
||||
response=$(curl -sf \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
"${API}/issues?type=issues&state=open&labels=${LABEL_NAME}&q=${query}&limit=5" \
|
||||
2>/dev/null || echo "[]")
|
||||
|
||||
# Extract the first matching issue number
|
||||
echo "$response" \
|
||||
| grep -oP '"number":\s*\K[0-9]+' \
|
||||
| head -1
|
||||
}
|
||||
|
||||
# ── Build issue body ────────────────────────────────────────────────────────
|
||||
build_body() {
|
||||
local severity_badge
|
||||
if [[ "$SEVERITY" == "error" ]]; then
|
||||
severity_badge="**Severity:** Error"
|
||||
else
|
||||
severity_badge="**Severity:** Warning"
|
||||
fi
|
||||
|
||||
cat <<BODY
|
||||
## CI Gate Failure: ${GATE}
|
||||
|
||||
${severity_badge}
|
||||
**Workflow:** ${WORKFLOW:-unknown}
|
||||
**Branch:** ${GITHUB_REF_NAME:-unknown}
|
||||
**Commit:** \`${GITHUB_SHA:0:8}\`
|
||||
**Run:** [View CI run](${RUN_URL})
|
||||
|
||||
### Details
|
||||
|
||||
${DETAILS}
|
||||
|
||||
### Resolution
|
||||
|
||||
Fix the issue described above and push a new commit. This issue will be closed automatically when the gate passes, or can be closed manually.
|
||||
|
||||
---
|
||||
*Auto-created by [ci-issue-reporter](${GITEA_URL}/${REPO}/src/branch/main/automation/ci-issue-reporter.sh)*
|
||||
BODY
|
||||
}
|
||||
|
||||
# ── Build comment body (for existing issues) ────────────────────────────────
|
||||
build_comment() {
|
||||
cat <<COMMENT
|
||||
### CI failure recurrence
|
||||
|
||||
**Branch:** ${GITHUB_REF_NAME:-unknown}
|
||||
**Commit:** \`${GITHUB_SHA:0:8}\`
|
||||
**Run:** [View CI run](${RUN_URL})
|
||||
|
||||
${DETAILS}
|
||||
COMMENT
|
||||
}
|
||||
|
||||
# ── Main ────────────────────────────────────────────────────────────────────
|
||||
ensure_label
|
||||
|
||||
EXISTING=$(find_existing_issue)
|
||||
|
||||
if [[ -n "$EXISTING" ]]; then
|
||||
# Append comment to existing issue
|
||||
COMMENT_BODY=$(build_comment)
|
||||
COMMENT_JSON=$(printf '%s' "$COMMENT_BODY" | python3 -c "
|
||||
import sys, json
|
||||
print(json.dumps({'body': sys.stdin.read()}))" 2>/dev/null)
|
||||
|
||||
HTTP=$(curl -sf -o /dev/null -w '%{http_code}' -X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API}/issues/${EXISTING}/comments" \
|
||||
-d "${COMMENT_JSON}" 2>/dev/null || echo "000")
|
||||
|
||||
if [[ "$HTTP" == "201" ]]; then
|
||||
echo "Commented on existing issue #${EXISTING}"
|
||||
else
|
||||
echo "WARNING: Failed to comment on issue #${EXISTING} (HTTP ${HTTP})"
|
||||
fi
|
||||
else
|
||||
# Create new issue
|
||||
ISSUE_BODY=$(build_body)
|
||||
ISSUE_JSON=$(python3 -c "
|
||||
import sys, json
|
||||
body = sys.stdin.read()
|
||||
print(json.dumps({
|
||||
'title': sys.argv[1],
|
||||
'body': body,
|
||||
'labels': []
|
||||
}))" "$TITLE" <<< "$ISSUE_BODY" 2>/dev/null)
|
||||
|
||||
# Create the issue
|
||||
RESPONSE=$(curl -sf -X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API}/issues" \
|
||||
-d "${ISSUE_JSON}" 2>/dev/null || echo "{}")
|
||||
|
||||
ISSUE_NUM=$(echo "$RESPONSE" | grep -oP '"number":\s*\K[0-9]+' | head -1)
|
||||
|
||||
if [[ -n "$ISSUE_NUM" ]]; then
|
||||
# Apply label (separate call — more reliable across Gitea versions)
|
||||
LABEL_ID=$(curl -sf \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
"${API}/labels" 2>/dev/null \
|
||||
| grep -oP "\"id\":\s*\K[0-9]+(?=[^}]*\"name\":\s*\"${LABEL_NAME}\")" \
|
||||
| head -1 || true)
|
||||
|
||||
if [[ -n "$LABEL_ID" ]]; then
|
||||
curl -sf -X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API}/issues/${ISSUE_NUM}/labels" \
|
||||
-d "{\"labels\":[${LABEL_ID}]}" \
|
||||
> /dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
echo "Created issue #${ISSUE_NUM}: ${TITLE}"
|
||||
else
|
||||
echo "WARNING: Failed to create issue"
|
||||
echo "Response: ${RESPONSE}"
|
||||
fi
|
||||
fi
|
||||
@@ -1 +0,0 @@
|
||||
<html><body bgcolor="#FFFFFF"></body></html>
|
||||
@@ -0,0 +1,16 @@
|
||||
# Docs Index: /templates/repos/joomla/module/src
|
||||
|
||||
## Purpose
|
||||
|
||||
This index provides navigation to documentation within this folder.
|
||||
|
||||
## Metadata
|
||||
|
||||
- **Document Type:** index
|
||||
- **Auto-generated:** This file is automatically generated by rebuild_indexes.py
|
||||
|
||||
## Revision History
|
||||
|
||||
| Change | Notes | Author |
|
||||
| --- | --- | --- |
|
||||
| Automated update | Generated by documentation index automation | rebuild_indexes.py |
|
||||
@@ -0,0 +1,71 @@
|
||||
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
; SPDX-License-Identifier: GPL-3.0-or-later
|
||||
;
|
||||
; FILE INFORMATION
|
||||
; DEFGROUP: MokoJoomHero.Module.Language
|
||||
; INGROUP: MokoJoomHero.Module
|
||||
; REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
||||
; PATH: /src/language/en-GB/mod_mokojoomhero.ini
|
||||
; VERSION: 01.07.00
|
||||
; BRIEF: Language strings for MokoJoomHero module (frontend + admin form fields)
|
||||
|
||||
MOD_MOKOJOOMHERO_NO_CONTENT="Add content to this module to display it over the hero image."
|
||||
|
||||
; Content fieldset
|
||||
MOD_MOKOJOOMHERO_FIELDSET_CONTENT="Hero Content"
|
||||
MOD_MOKOJOOMHERO_CONTENT_LABEL="Content"
|
||||
MOD_MOKOJOOMHERO_CONTENT_DESC="HTML content displayed on the hero. Use the editor to add headings, text, buttons, or any HTML."
|
||||
MOD_MOKOJOOMHERO_SHOW_CARD_LABEL="Show Card"
|
||||
MOD_MOKOJOOMHERO_SHOW_CARD_DESC="Wrap the content in a card with a white background and shadow."
|
||||
|
||||
; Hero mode
|
||||
MOD_MOKOJOOMHERO_MODE_LABEL="Hero Mode"
|
||||
MOD_MOKOJOOMHERO_MODE_DESC="Choose between a slideshow of images, an embedded video (YouTube/Vimeo), or a local video file."
|
||||
MOD_MOKOJOOMHERO_MODE_IMAGES="Images"
|
||||
MOD_MOKOJOOMHERO_MODE_VIDEO="Video (YouTube/Vimeo)"
|
||||
MOD_MOKOJOOMHERO_MODE_LOCALVIDEO="Local Video"
|
||||
|
||||
; Image settings
|
||||
MOD_MOKOJOOMHERO_IMAGE_FOLDER_LABEL="Image Folder"
|
||||
MOD_MOKOJOOMHERO_IMAGE_FOLDER_DESC="Path to folder containing hero images, relative to Joomla root (e.g. images/heroes)."
|
||||
MOD_MOKOJOOMHERO_IMAGE_COUNT_LABEL="Number of Images"
|
||||
MOD_MOKOJOOMHERO_IMAGE_COUNT_DESC="How many random images to include in the slideshow (1–5)."
|
||||
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_LABEL="Slide Interval (ms)"
|
||||
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_DESC="Time between slides in milliseconds (e.g. 5000 = 5 seconds)."
|
||||
|
||||
; Video settings (embedded)
|
||||
MOD_MOKOJOOMHERO_VIDEO_FILE_LABEL="Video URL"
|
||||
MOD_MOKOJOOMHERO_VIDEO_FILE_DESC="YouTube or Vimeo URL. The module auto-detects the source."
|
||||
|
||||
; Local video settings
|
||||
MOD_MOKOJOOMHERO_LOCAL_VIDEO_LABEL="Video File"
|
||||
MOD_MOKOJOOMHERO_LOCAL_VIDEO_DESC="Select a video file from the Media Manager (mp4, webm, ogg)."
|
||||
|
||||
; Card delay
|
||||
MOD_MOKOJOOMHERO_CARD_DELAY_LABEL="Card Fade-in Delay (ms)"
|
||||
MOD_MOKOJOOMHERO_CARD_DELAY_DESC="Delay in milliseconds before the content card fades in. Set to 0 for no delay."
|
||||
|
||||
; Mute toggle
|
||||
MOD_MOKOJOOMHERO_MUTE_TOGGLE_LABEL="Show Mute Toggle"
|
||||
MOD_MOKOJOOMHERO_MUTE_TOGGLE_DESC="Show a mute/unmute button on the hero video. Videos always start muted (required for autoplay)."
|
||||
|
||||
; Hero height
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_LABEL="Hero Height"
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_DESC="Height of the hero section. Use px for fixed pixels (e.g. 400px) or vh for viewport height (e.g. 60vh for 60%% of screen)."
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_HINT="e.g. 60vh or 400px"
|
||||
|
||||
; Overlay fieldset
|
||||
MOD_MOKOJOOMHERO_FIELDSET_OVERLAY="Overlay & Text"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_COLOR_LABEL="Overlay Colour"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_COLOR_DESC="Background colour of the overlay on top of the hero image."
|
||||
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_LABEL="Overlay Opacity"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_DESC="Transparency of the overlay (0 = fully transparent, 1 = fully opaque)."
|
||||
MOD_MOKOJOOMHERO_TEXT_ALIGN_LABEL="Text Alignment"
|
||||
MOD_MOKOJOOMHERO_TEXT_ALIGN_DESC="Horizontal alignment of the overlay text."
|
||||
MOD_MOKOJOOMHERO_TEXT_COLOR_LABEL="Text Colour"
|
||||
MOD_MOKOJOOMHERO_TEXT_COLOR_DESC="Colour of the text displayed over the hero image."
|
||||
|
||||
; Alignment options
|
||||
MOD_MOKOJOOMHERO_ALIGN_LEFT="Left"
|
||||
MOD_MOKOJOOMHERO_ALIGN_CENTER="Centre"
|
||||
MOD_MOKOJOOMHERO_ALIGN_RIGHT="Right"
|
||||
@@ -0,0 +1,72 @@
|
||||
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
; SPDX-License-Identifier: GPL-3.0-or-later
|
||||
;
|
||||
; FILE INFORMATION
|
||||
; DEFGROUP: MokoJoomHero.Module.Language
|
||||
; INGROUP: MokoJoomHero.Module
|
||||
; REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
||||
; PATH: /src/language/en-GB/mod_mokojoomhero.sys.ini
|
||||
; VERSION: 01.07.00
|
||||
; BRIEF: System language strings — used in admin Extension Manager and Module Manager
|
||||
|
||||
MOD_MOKOJOOMHERO="Module - MokoJoomHero"
|
||||
MOD_MOKOJOOMHERO_DESCRIPTION="Displays a random hero image slideshow or background video with content overlaid. Designed for MokoOnyx template. By Moko Consulting."
|
||||
|
||||
; Content fieldset
|
||||
MOD_MOKOJOOMHERO_FIELDSET_CONTENT="Hero Content"
|
||||
MOD_MOKOJOOMHERO_CONTENT_LABEL="Content"
|
||||
MOD_MOKOJOOMHERO_CONTENT_DESC="HTML content displayed on the hero. Use the editor to add headings, text, buttons, or any HTML."
|
||||
MOD_MOKOJOOMHERO_SHOW_CARD_LABEL="Show Card"
|
||||
MOD_MOKOJOOMHERO_SHOW_CARD_DESC="Wrap the content in a card with a white background and shadow."
|
||||
|
||||
; Hero mode
|
||||
MOD_MOKOJOOMHERO_MODE_LABEL="Hero Mode"
|
||||
MOD_MOKOJOOMHERO_MODE_DESC="Choose between a slideshow of images, an embedded video (YouTube/Vimeo), or a local video file."
|
||||
MOD_MOKOJOOMHERO_MODE_IMAGES="Images"
|
||||
MOD_MOKOJOOMHERO_MODE_VIDEO="Video (YouTube/Vimeo)"
|
||||
MOD_MOKOJOOMHERO_MODE_LOCALVIDEO="Local Video"
|
||||
|
||||
; Image settings
|
||||
MOD_MOKOJOOMHERO_IMAGE_FOLDER_LABEL="Image Folder"
|
||||
MOD_MOKOJOOMHERO_IMAGE_FOLDER_DESC="Path to folder containing hero images, relative to Joomla root (e.g. images/heroes)."
|
||||
MOD_MOKOJOOMHERO_IMAGE_COUNT_LABEL="Number of Images"
|
||||
MOD_MOKOJOOMHERO_IMAGE_COUNT_DESC="How many random images to include in the slideshow (1–5)."
|
||||
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_LABEL="Slide Interval (ms)"
|
||||
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_DESC="Time between slides in milliseconds (e.g. 5000 = 5 seconds)."
|
||||
|
||||
; Video settings (embedded)
|
||||
MOD_MOKOJOOMHERO_VIDEO_FILE_LABEL="Video URL"
|
||||
MOD_MOKOJOOMHERO_VIDEO_FILE_DESC="YouTube or Vimeo URL. The module auto-detects the source."
|
||||
|
||||
; Local video settings
|
||||
MOD_MOKOJOOMHERO_LOCAL_VIDEO_LABEL="Video File"
|
||||
MOD_MOKOJOOMHERO_LOCAL_VIDEO_DESC="Select a video file from the Media Manager (mp4, webm, ogg)."
|
||||
|
||||
; Card delay
|
||||
MOD_MOKOJOOMHERO_CARD_DELAY_LABEL="Card Fade-in Delay (ms)"
|
||||
MOD_MOKOJOOMHERO_CARD_DELAY_DESC="Delay in milliseconds before the content card fades in. Set to 0 for no delay."
|
||||
|
||||
; Mute toggle
|
||||
MOD_MOKOJOOMHERO_MUTE_TOGGLE_LABEL="Show Mute Toggle"
|
||||
MOD_MOKOJOOMHERO_MUTE_TOGGLE_DESC="Show a mute/unmute button on the hero video. Videos always start muted (required for autoplay)."
|
||||
|
||||
; Hero height
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_LABEL="Hero Height"
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_DESC="Height of the hero section. Use px for fixed pixels (e.g. 400px) or vh for viewport height (e.g. 60vh for 60%% of screen)."
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_HINT="e.g. 60vh or 400px"
|
||||
|
||||
; Overlay fieldset
|
||||
MOD_MOKOJOOMHERO_FIELDSET_OVERLAY="Overlay & Text"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_COLOR_LABEL="Overlay Colour"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_COLOR_DESC="Background colour of the overlay on top of the hero image."
|
||||
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_LABEL="Overlay Opacity"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_DESC="Transparency of the overlay (0 = fully transparent, 1 = fully opaque)."
|
||||
MOD_MOKOJOOMHERO_TEXT_ALIGN_LABEL="Text Alignment"
|
||||
MOD_MOKOJOOMHERO_TEXT_ALIGN_DESC="Horizontal alignment of the overlay text."
|
||||
MOD_MOKOJOOMHERO_TEXT_COLOR_LABEL="Text Colour"
|
||||
MOD_MOKOJOOMHERO_TEXT_COLOR_DESC="Colour of the text displayed over the hero image."
|
||||
|
||||
; Alignment options
|
||||
MOD_MOKOJOOMHERO_ALIGN_LEFT="Left"
|
||||
MOD_MOKOJOOMHERO_ALIGN_CENTER="Centre"
|
||||
MOD_MOKOJOOMHERO_ALIGN_RIGHT="Right"
|
||||
@@ -0,0 +1,71 @@
|
||||
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
; SPDX-License-Identifier: GPL-3.0-or-later
|
||||
;
|
||||
; FILE INFORMATION
|
||||
; DEFGROUP: MokoJoomHero.Module.Language
|
||||
; INGROUP: MokoJoomHero.Module
|
||||
; REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
||||
; PATH: /src/language/en-US/mod_mokojoomhero.ini
|
||||
; VERSION: 01.07.00
|
||||
; BRIEF: Language strings for MokoJoomHero module (en-US, frontend + admin form fields)
|
||||
|
||||
MOD_MOKOJOOMHERO_NO_CONTENT="Add content to this module to display it over the hero image."
|
||||
|
||||
; Content fieldset
|
||||
MOD_MOKOJOOMHERO_FIELDSET_CONTENT="Hero Content"
|
||||
MOD_MOKOJOOMHERO_CONTENT_LABEL="Content"
|
||||
MOD_MOKOJOOMHERO_CONTENT_DESC="HTML content displayed on the hero. Use the editor to add headings, text, buttons, or any HTML."
|
||||
MOD_MOKOJOOMHERO_SHOW_CARD_LABEL="Show Card"
|
||||
MOD_MOKOJOOMHERO_SHOW_CARD_DESC="Wrap the content in a card with a white background and shadow."
|
||||
|
||||
; Hero mode
|
||||
MOD_MOKOJOOMHERO_MODE_LABEL="Hero Mode"
|
||||
MOD_MOKOJOOMHERO_MODE_DESC="Choose between a slideshow of images, an embedded video (YouTube/Vimeo), or a local video file."
|
||||
MOD_MOKOJOOMHERO_MODE_IMAGES="Images"
|
||||
MOD_MOKOJOOMHERO_MODE_VIDEO="Video (YouTube/Vimeo)"
|
||||
MOD_MOKOJOOMHERO_MODE_LOCALVIDEO="Local Video"
|
||||
|
||||
; Image settings
|
||||
MOD_MOKOJOOMHERO_IMAGE_FOLDER_LABEL="Image Folder"
|
||||
MOD_MOKOJOOMHERO_IMAGE_FOLDER_DESC="Path to folder containing hero images, relative to Joomla root (e.g. images/heroes)."
|
||||
MOD_MOKOJOOMHERO_IMAGE_COUNT_LABEL="Number of Images"
|
||||
MOD_MOKOJOOMHERO_IMAGE_COUNT_DESC="How many random images to include in the slideshow (1-5)."
|
||||
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_LABEL="Slide Interval (ms)"
|
||||
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_DESC="Time between slides in milliseconds (e.g. 5000 = 5 seconds)."
|
||||
|
||||
; Video settings (embedded)
|
||||
MOD_MOKOJOOMHERO_VIDEO_FILE_LABEL="Video URL"
|
||||
MOD_MOKOJOOMHERO_VIDEO_FILE_DESC="YouTube or Vimeo URL. The module auto-detects the source."
|
||||
|
||||
; Local video settings
|
||||
MOD_MOKOJOOMHERO_LOCAL_VIDEO_LABEL="Video File"
|
||||
MOD_MOKOJOOMHERO_LOCAL_VIDEO_DESC="Select a video file from the Media Manager (mp4, webm, ogg)."
|
||||
|
||||
; Hero height
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_LABEL="Hero Height"
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_DESC="Height of the hero section. Use px for fixed pixels (e.g. 400px) or vh for viewport height (e.g. 60vh for 60% of screen)."
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_HINT="e.g. 60vh or 400px"
|
||||
|
||||
; Card delay
|
||||
MOD_MOKOJOOMHERO_CARD_DELAY_LABEL="Card Fade-in Delay (ms)"
|
||||
MOD_MOKOJOOMHERO_CARD_DELAY_DESC="Delay in milliseconds before the content card fades in. Set to 0 for no delay."
|
||||
|
||||
; Mute toggle
|
||||
MOD_MOKOJOOMHERO_MUTE_TOGGLE_LABEL="Show Mute Toggle"
|
||||
MOD_MOKOJOOMHERO_MUTE_TOGGLE_DESC="Show a mute/unmute button on the hero video. Videos always start muted (required for autoplay)."
|
||||
|
||||
; Overlay fieldset
|
||||
MOD_MOKOJOOMHERO_FIELDSET_OVERLAY="Overlay & Text"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_COLOR_LABEL="Overlay Color"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_COLOR_DESC="Background color of the overlay on top of the hero image."
|
||||
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_LABEL="Overlay Opacity"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_DESC="Transparency of the overlay (0 = fully transparent, 1 = fully opaque)."
|
||||
MOD_MOKOJOOMHERO_TEXT_ALIGN_LABEL="Text Alignment"
|
||||
MOD_MOKOJOOMHERO_TEXT_ALIGN_DESC="Horizontal alignment of the overlay text."
|
||||
MOD_MOKOJOOMHERO_TEXT_COLOR_LABEL="Text Color"
|
||||
MOD_MOKOJOOMHERO_TEXT_COLOR_DESC="Color of the text displayed over the hero image."
|
||||
|
||||
; Alignment options
|
||||
MOD_MOKOJOOMHERO_ALIGN_LEFT="Left"
|
||||
MOD_MOKOJOOMHERO_ALIGN_CENTER="Center"
|
||||
MOD_MOKOJOOMHERO_ALIGN_RIGHT="Right"
|
||||
@@ -0,0 +1,72 @@
|
||||
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
; SPDX-License-Identifier: GPL-3.0-or-later
|
||||
;
|
||||
; FILE INFORMATION
|
||||
; DEFGROUP: MokoJoomHero.Module.Language
|
||||
; INGROUP: MokoJoomHero.Module
|
||||
; REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
||||
; PATH: /src/language/en-US/mod_mokojoomhero.sys.ini
|
||||
; VERSION: 01.07.00
|
||||
; BRIEF: System language strings — used in admin Extension Manager and Module Manager (en-US)
|
||||
|
||||
MOD_MOKOJOOMHERO="Module - MokoJoomHero"
|
||||
MOD_MOKOJOOMHERO_DESCRIPTION="Displays a random hero image slideshow or background video with content overlaid. Designed for MokoOnyx template. By Moko Consulting."
|
||||
|
||||
; Content fieldset
|
||||
MOD_MOKOJOOMHERO_FIELDSET_CONTENT="Hero Content"
|
||||
MOD_MOKOJOOMHERO_CONTENT_LABEL="Content"
|
||||
MOD_MOKOJOOMHERO_CONTENT_DESC="HTML content displayed on the hero. Use the editor to add headings, text, buttons, or any HTML."
|
||||
MOD_MOKOJOOMHERO_SHOW_CARD_LABEL="Show Card"
|
||||
MOD_MOKOJOOMHERO_SHOW_CARD_DESC="Wrap the content in a card with a white background and shadow."
|
||||
|
||||
; Hero mode
|
||||
MOD_MOKOJOOMHERO_MODE_LABEL="Hero Mode"
|
||||
MOD_MOKOJOOMHERO_MODE_DESC="Choose between a slideshow of images, an embedded video (YouTube/Vimeo), or a local video file."
|
||||
MOD_MOKOJOOMHERO_MODE_IMAGES="Images"
|
||||
MOD_MOKOJOOMHERO_MODE_VIDEO="Video (YouTube/Vimeo)"
|
||||
MOD_MOKOJOOMHERO_MODE_LOCALVIDEO="Local Video"
|
||||
|
||||
; Image settings
|
||||
MOD_MOKOJOOMHERO_IMAGE_FOLDER_LABEL="Image Folder"
|
||||
MOD_MOKOJOOMHERO_IMAGE_FOLDER_DESC="Path to folder containing hero images, relative to Joomla root (e.g. images/heroes)."
|
||||
MOD_MOKOJOOMHERO_IMAGE_COUNT_LABEL="Number of Images"
|
||||
MOD_MOKOJOOMHERO_IMAGE_COUNT_DESC="How many random images to include in the slideshow (1-5)."
|
||||
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_LABEL="Slide Interval (ms)"
|
||||
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_DESC="Time between slides in milliseconds (e.g. 5000 = 5 seconds)."
|
||||
|
||||
; Video settings (embedded)
|
||||
MOD_MOKOJOOMHERO_VIDEO_FILE_LABEL="Video URL"
|
||||
MOD_MOKOJOOMHERO_VIDEO_FILE_DESC="YouTube or Vimeo URL. The module auto-detects the source."
|
||||
|
||||
; Local video settings
|
||||
MOD_MOKOJOOMHERO_LOCAL_VIDEO_LABEL="Video File"
|
||||
MOD_MOKOJOOMHERO_LOCAL_VIDEO_DESC="Select a video file from the Media Manager (mp4, webm, ogg)."
|
||||
|
||||
; Card delay
|
||||
MOD_MOKOJOOMHERO_CARD_DELAY_LABEL="Card Fade-in Delay (ms)"
|
||||
MOD_MOKOJOOMHERO_CARD_DELAY_DESC="Delay in milliseconds before the content card fades in. Set to 0 for no delay."
|
||||
|
||||
; Mute toggle
|
||||
MOD_MOKOJOOMHERO_MUTE_TOGGLE_LABEL="Show Mute Toggle"
|
||||
MOD_MOKOJOOMHERO_MUTE_TOGGLE_DESC="Show a mute/unmute button on the hero video. Videos always start muted (required for autoplay)."
|
||||
|
||||
; Hero height
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_LABEL="Hero Height"
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_DESC="Height of the hero section. Use px for fixed pixels (e.g. 400px) or vh for viewport height (e.g. 60vh for 60% of screen)."
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_HINT="e.g. 60vh or 400px"
|
||||
|
||||
; Overlay fieldset
|
||||
MOD_MOKOJOOMHERO_FIELDSET_OVERLAY="Overlay & Text"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_COLOR_LABEL="Overlay Color"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_COLOR_DESC="Background color of the overlay on top of the hero image."
|
||||
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_LABEL="Overlay Opacity"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_DESC="Transparency of the overlay (0 = fully transparent, 1 = fully opaque)."
|
||||
MOD_MOKOJOOMHERO_TEXT_ALIGN_LABEL="Text Alignment"
|
||||
MOD_MOKOJOOMHERO_TEXT_ALIGN_DESC="Horizontal alignment of the overlay text."
|
||||
MOD_MOKOJOOMHERO_TEXT_COLOR_LABEL="Text Color"
|
||||
MOD_MOKOJOOMHERO_TEXT_COLOR_DESC="Color of the text displayed over the hero image."
|
||||
|
||||
; Alignment options
|
||||
MOD_MOKOJOOMHERO_ALIGN_LEFT="Left"
|
||||
MOD_MOKOJOOMHERO_ALIGN_CENTER="Center"
|
||||
MOD_MOKOJOOMHERO_ALIGN_RIGHT="Right"
|
||||
@@ -0,0 +1,193 @@
|
||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: MokoJoomHero.Module.Assets
|
||||
* INGROUP: MokoJoomHero.Module
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
||||
* PATH: /src/css/template.css
|
||||
* VERSION: 01.07.00
|
||||
* BRIEF: Hero module stylesheet — slideshow, video background, overlay
|
||||
*/
|
||||
|
||||
/* ============================================================
|
||||
Hero container
|
||||
============================================================ */
|
||||
.mokojoomhero {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Image slides
|
||||
============================================================ */
|
||||
.mokojoomhero__slide {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
opacity: 0;
|
||||
transition: opacity 1s ease;
|
||||
}
|
||||
|
||||
.mokojoomhero__slide--active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Video background
|
||||
============================================================ */
|
||||
/* Native <video> elements: object-fit works directly */
|
||||
video.mokojoomhero__video {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Embedded <iframe> (YouTube/Vimeo): object-fit doesn't apply to iframes,
|
||||
so we oversize the iframe and centre-crop via the parent's overflow:hidden */
|
||||
iframe.mokojoomhero__video {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 100vw;
|
||||
height: 56.25vw; /* 16:9 aspect ratio */
|
||||
min-height: 100%;
|
||||
min-width: 177.78vh; /* 100 × 16/9 — ensures cover in portrait viewports */
|
||||
transform: translate(-50%, -50%);
|
||||
border: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Overlay
|
||||
============================================================ */
|
||||
.mokojoomhero__overlay {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Content
|
||||
============================================================ */
|
||||
.mokojoomhero__content {
|
||||
max-width: 900px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mokojoomhero__content h1,
|
||||
.mokojoomhero__content h2,
|
||||
.mokojoomhero__content h3 {
|
||||
margin-top: 0;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.mokojoomhero__content p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Card
|
||||
============================================================ */
|
||||
.mokojoomhero__card {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
color: #333;
|
||||
border-radius: 8px;
|
||||
padding: 2rem 2.5rem;
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.mokojoomhero__card h1,
|
||||
.mokojoomhero__card h2,
|
||||
.mokojoomhero__card h3 {
|
||||
color: #222;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.mokojoomhero__card a:not(.btn) {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Card fade-in delay
|
||||
============================================================ */
|
||||
.mokojoomhero__card[data-card-delay] {
|
||||
opacity: 0;
|
||||
animation: mokojoomhero-fadein 0.6s ease forwards;
|
||||
}
|
||||
|
||||
@keyframes mokojoomhero-fadein {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Mute toggle
|
||||
============================================================ */
|
||||
.mokojoomhero__mute-toggle {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
right: 1rem;
|
||||
z-index: 2;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.mokojoomhero__mute-toggle:hover {
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Responsive
|
||||
============================================================ */
|
||||
@media (max-width: 768px) {
|
||||
.mokojoomhero {
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.mokojoomhero__video,
|
||||
.mokojoomhero__slide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mokojoomhero__overlay {
|
||||
padding: 1rem;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.mokojoomhero__content {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.mokojoomhero__card {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: MokoJoomHero.Module.Assets
|
||||
* INGROUP: MokoJoomHero.Module
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
||||
* PATH: /src/js/template.js
|
||||
* VERSION: 01.07.00
|
||||
* BRIEF: Hero module JavaScript — image slideshow crossfade
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Skip slideshow on mobile — video/images are hidden by CSS
|
||||
if (window.matchMedia('(max-width: 768px)').matches) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Image slideshow ──
|
||||
document.querySelectorAll('.mokojoomhero[data-slides]').forEach(function (hero) {
|
||||
var slides = hero.querySelectorAll('.mokojoomhero__slide');
|
||||
var interval = parseInt(hero.dataset.interval, 10) || 5000;
|
||||
var current = 0;
|
||||
|
||||
if (slides.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
setInterval(function () {
|
||||
slides[current].classList.remove('mokojoomhero__slide--active');
|
||||
slides[current].setAttribute('aria-hidden', 'true');
|
||||
|
||||
current = (current + 1) % slides.length;
|
||||
|
||||
slides[current].classList.add('mokojoomhero__slide--active');
|
||||
slides[current].setAttribute('aria-hidden', 'false');
|
||||
}, interval);
|
||||
});
|
||||
|
||||
// ── Pause/resume videos when out of viewport ──
|
||||
if (!('IntersectionObserver' in window)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var observer = new IntersectionObserver(function (entries) {
|
||||
entries.forEach(function (entry) {
|
||||
var hero = entry.target;
|
||||
var video = hero.querySelector('video.mokojoomhero__video');
|
||||
var iframe = hero.querySelector('iframe.mokojoomhero__video');
|
||||
|
||||
if (entry.isIntersecting) {
|
||||
// Resume
|
||||
if (video) {
|
||||
video.play();
|
||||
}
|
||||
if (iframe) {
|
||||
var src = iframe.src || '';
|
||||
if (src.indexOf('youtube') !== -1) {
|
||||
iframe.contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}', '*');
|
||||
} else if (src.indexOf('vimeo') !== -1) {
|
||||
iframe.contentWindow.postMessage('{"method":"play"}', '*');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Pause
|
||||
if (video) {
|
||||
video.pause();
|
||||
}
|
||||
if (iframe) {
|
||||
var src = iframe.src || '';
|
||||
if (src.indexOf('youtube') !== -1) {
|
||||
iframe.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}', '*');
|
||||
} else if (src.indexOf('vimeo') !== -1) {
|
||||
iframe.contentWindow.postMessage('{"method":"pause"}', '*');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.1 });
|
||||
|
||||
document.querySelectorAll('.mokojoomhero').forEach(function (hero) {
|
||||
observer.observe(hero);
|
||||
});
|
||||
|
||||
// ── Mute/unmute toggle ──
|
||||
document.querySelectorAll('.mokojoomhero__mute-toggle').forEach(function (btn) {
|
||||
var hero = btn.closest('.mokojoomhero');
|
||||
var video = hero.querySelector('video.mokojoomhero__video');
|
||||
var iframe = hero.querySelector('iframe.mokojoomhero__video');
|
||||
var icon = btn.querySelector('.mokojoomhero__mute-icon');
|
||||
|
||||
btn.addEventListener('click', function () {
|
||||
var muted = btn.getAttribute('data-muted') === 'true';
|
||||
|
||||
if (video) {
|
||||
video.muted = !muted;
|
||||
}
|
||||
if (iframe) {
|
||||
var src = iframe.src || '';
|
||||
if (src.indexOf('youtube') !== -1) {
|
||||
var func = muted ? 'unMute' : 'mute';
|
||||
iframe.contentWindow.postMessage('{"event":"command","func":"' + func + '","args":""}', '*');
|
||||
} else if (src.indexOf('vimeo') !== -1) {
|
||||
var vol = muted ? 1 : 0;
|
||||
iframe.contentWindow.postMessage('{"method":"setVolume","value":' + vol + '}', '*');
|
||||
}
|
||||
}
|
||||
|
||||
btn.setAttribute('data-muted', muted ? 'false' : 'true');
|
||||
btn.setAttribute('aria-label', muted ? 'Mute video' : 'Unmute video');
|
||||
icon.textContent = muted ? '\u{1F50A}' : '\u{1F507}';
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Site
|
||||
* @subpackage mod_mokojoomhero
|
||||
*
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Helper\ModuleHelper;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
|
||||
/** @var \Joomla\CMS\Application\SiteApplication $app */
|
||||
/** @var \stdClass $module */
|
||||
/** @var \Joomla\Registry\Registry $params */
|
||||
|
||||
// Load module assets via Web Asset Manager (registry in media/mod_mokojoomhero/)
|
||||
$wa = $app->getDocument()->getWebAssetManager();
|
||||
$wa->getRegistry()->addExtensionRegistryFile('mod_mokojoomhero');
|
||||
$wa->usePreset('mod_mokojoomhero');
|
||||
|
||||
// Module parameters
|
||||
$heroMode = $params->get('heroMode', 'images');
|
||||
$imageFolder = $params->get('imageFolder', 'images/heroes');
|
||||
$imageCount = (int) $params->get('imageCount', 5);
|
||||
$slideInterval = (int) $params->get('slideInterval', 5000);
|
||||
$videoFile = $params->get('videoFile', '');
|
||||
$heroHeight = $params->get('heroHeight', '60vh');
|
||||
$overlayColor = $params->get('overlayColor', '#000000');
|
||||
$overlayOpacity = (float) $params->get('overlayOpacity', 0.5);
|
||||
$textAlign = $params->get('textAlign', 'center');
|
||||
$textColor = $params->get('textColor', '#ffffff');
|
||||
$heroContent = $params->get('heroContent', '');
|
||||
$showCard = (bool) $params->get('showCard', 1);
|
||||
$cardDelay = (int) $params->get('cardDelay', 0);
|
||||
$showMuteToggle = (bool) $params->get('showMuteToggle', 0);
|
||||
$localVideoFile = $params->get('localVideoFile', '');
|
||||
|
||||
// Collect hero images
|
||||
$heroImages = [];
|
||||
|
||||
if ($heroMode === 'images') {
|
||||
$folderPath = JPATH_ROOT . '/' . ltrim($imageFolder, '/');
|
||||
|
||||
if (is_dir($folderPath)) {
|
||||
$allowed = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif', 'svg'];
|
||||
$all = [];
|
||||
|
||||
foreach (new DirectoryIterator($folderPath) as $file) {
|
||||
if ($file->isFile() && in_array(strtolower($file->getExtension()), $allowed, true)) {
|
||||
$all[] = $file->getFilename();
|
||||
}
|
||||
}
|
||||
|
||||
if ($all) {
|
||||
shuffle($all);
|
||||
$picked = array_slice($all, 0, min($imageCount, 5));
|
||||
|
||||
foreach ($picked as $filename) {
|
||||
$heroImages[] = Uri::root() . $imageFolder . '/' . $filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build video URL — smartly detect YouTube, Vimeo, or local/direct file
|
||||
$videoUrl = '';
|
||||
$youtubeId = '';
|
||||
$vimeoId = '';
|
||||
|
||||
if ($heroMode === 'localvideo' && $localVideoFile) {
|
||||
$videoUrl = Uri::root() . ltrim($localVideoFile, '/');
|
||||
} elseif ($heroMode === 'video' && $videoFile) {
|
||||
// YouTube: watch, embed, shorts, youtu.be, with optional timestamps/params
|
||||
if (preg_match('/(?:youtube\.com\/(?:watch\?.*v=|embed\/|shorts\/|v\/)|youtu\.be\/)([\w-]{11})/', $videoFile, $m)) {
|
||||
$youtubeId = $m[1];
|
||||
// Vimeo: vimeo.com/123456 or player.vimeo.com/video/123456
|
||||
} elseif (preg_match('/vimeo\.com\/(?:video\/)?(\d+)/', $videoFile, $m)) {
|
||||
$vimeoId = $m[1];
|
||||
} else {
|
||||
// Direct URL or local file path
|
||||
$videoUrl = (strpos($videoFile, '://') !== false)
|
||||
? $videoFile
|
||||
: Uri::root() . ltrim($videoFile, '/');
|
||||
}
|
||||
}
|
||||
|
||||
// Module content from the editor (overlay text)
|
||||
$content = $module->content ?? '';
|
||||
|
||||
require ModuleHelper::getLayoutPath('mod_mokojoomhero', $params->get('layout', 'default'));
|
||||
@@ -0,0 +1,215 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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
|
||||
|
||||
FILE INFORMATION
|
||||
DEFGROUP: MokoJoomHero.Module
|
||||
INGROUP: MokoJoomHero
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
||||
PATH: /src/mod_mokojoomhero.xml
|
||||
VERSION: 01.00.20
|
||||
BRIEF: Joomla module manifest — random hero image with content overlay
|
||||
-->
|
||||
<extension type="module" client="site" method="upgrade">
|
||||
<name>Module - MokoJoomHero</name>
|
||||
<creationDate>2026-05</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<version>01.07.00</version>
|
||||
<description>Displays a random hero image slideshow or background video with content overlaid. Designed for MokoOnyx template. By Moko Consulting.</description>
|
||||
|
||||
<scriptfile>script.php</scriptfile>
|
||||
|
||||
<files>
|
||||
<filename module="mod_mokojoomhero">mod_mokojoomhero.php</filename>
|
||||
<filename>mod_mokojoomhero.xml</filename>
|
||||
<filename>script.php</filename>
|
||||
<folder>tmpl</folder>
|
||||
<folder>language</folder>
|
||||
</files>
|
||||
|
||||
<media destination="mod_mokojoomhero" folder="media">
|
||||
<filename>joomla.asset.json</filename>
|
||||
<folder>css</folder>
|
||||
<folder>js</folder>
|
||||
</media>
|
||||
|
||||
<languages folder="language">
|
||||
<language tag="en-GB">en-GB/mod_mokojoomhero.ini</language>
|
||||
<language tag="en-GB">en-GB/mod_mokojoomhero.sys.ini</language>
|
||||
<language tag="en-US">en-US/mod_mokojoomhero.ini</language>
|
||||
<language tag="en-US">en-US/mod_mokojoomhero.sys.ini</language>
|
||||
</languages>
|
||||
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="basic">
|
||||
<field
|
||||
name="heroMode"
|
||||
type="list"
|
||||
label="MOD_MOKOJOOMHERO_MODE_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_MODE_DESC"
|
||||
default="images"
|
||||
>
|
||||
<option value="images">MOD_MOKOJOOMHERO_MODE_IMAGES</option>
|
||||
<option value="video">MOD_MOKOJOOMHERO_MODE_VIDEO</option>
|
||||
<option value="localvideo">MOD_MOKOJOOMHERO_MODE_LOCALVIDEO</option>
|
||||
</field>
|
||||
<field
|
||||
name="imageFolder"
|
||||
type="text"
|
||||
label="MOD_MOKOJOOMHERO_IMAGE_FOLDER_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_IMAGE_FOLDER_DESC"
|
||||
default="images/heroes"
|
||||
filter="path"
|
||||
showon="heroMode:images"
|
||||
/>
|
||||
<field
|
||||
name="imageCount"
|
||||
type="number"
|
||||
label="MOD_MOKOJOOMHERO_IMAGE_COUNT_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_IMAGE_COUNT_DESC"
|
||||
default="5"
|
||||
min="1"
|
||||
max="5"
|
||||
showon="heroMode:images"
|
||||
/>
|
||||
<field
|
||||
name="slideInterval"
|
||||
type="number"
|
||||
label="MOD_MOKOJOOMHERO_SLIDE_INTERVAL_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_SLIDE_INTERVAL_DESC"
|
||||
default="5000"
|
||||
min="1000"
|
||||
step="500"
|
||||
showon="heroMode:images"
|
||||
/>
|
||||
<field
|
||||
name="videoFile"
|
||||
type="text"
|
||||
label="MOD_MOKOJOOMHERO_VIDEO_FILE_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_VIDEO_FILE_DESC"
|
||||
filter="string"
|
||||
showon="heroMode:video"
|
||||
/>
|
||||
<field
|
||||
name="localVideoFile"
|
||||
type="media"
|
||||
label="MOD_MOKOJOOMHERO_LOCAL_VIDEO_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_LOCAL_VIDEO_DESC"
|
||||
types="videos"
|
||||
showon="heroMode:localvideo"
|
||||
/>
|
||||
<field
|
||||
name="heroHeight"
|
||||
type="text"
|
||||
label="MOD_MOKOJOOMHERO_HERO_HEIGHT_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_HERO_HEIGHT_DESC"
|
||||
hint="MOD_MOKOJOOMHERO_HERO_HEIGHT_HINT"
|
||||
default="60vh"
|
||||
filter="string"
|
||||
/>
|
||||
<field
|
||||
name="showMuteToggle"
|
||||
type="radio"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
label="MOD_MOKOJOOMHERO_MUTE_TOGGLE_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_MUTE_TOGGLE_DESC"
|
||||
default="0"
|
||||
showon="heroMode:video,localvideo"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
<fieldset name="content"
|
||||
label="MOD_MOKOJOOMHERO_FIELDSET_CONTENT"
|
||||
>
|
||||
<field
|
||||
name="heroContent"
|
||||
type="editor"
|
||||
label="MOD_MOKOJOOMHERO_CONTENT_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_CONTENT_DESC"
|
||||
filter="safehtml"
|
||||
buttons="true"
|
||||
hide="readmore,pagebreak"
|
||||
/>
|
||||
<field
|
||||
name="showCard"
|
||||
type="radio"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
label="MOD_MOKOJOOMHERO_SHOW_CARD_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_SHOW_CARD_DESC"
|
||||
default="1"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
<field
|
||||
name="cardDelay"
|
||||
type="number"
|
||||
label="MOD_MOKOJOOMHERO_CARD_DELAY_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_CARD_DELAY_DESC"
|
||||
default="0"
|
||||
min="0"
|
||||
max="5000"
|
||||
step="250"
|
||||
showon="showCard:1"
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset name="advanced"
|
||||
label="MOD_MOKOJOOMHERO_FIELDSET_OVERLAY"
|
||||
>
|
||||
<field
|
||||
name="overlayColor"
|
||||
type="color"
|
||||
label="MOD_MOKOJOOMHERO_OVERLAY_COLOR_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_OVERLAY_COLOR_DESC"
|
||||
default="#000000"
|
||||
/>
|
||||
<field
|
||||
name="overlayOpacity"
|
||||
type="range"
|
||||
label="MOD_MOKOJOOMHERO_OVERLAY_OPACITY_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_OVERLAY_OPACITY_DESC"
|
||||
default="0.5"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.1"
|
||||
/>
|
||||
<field
|
||||
name="textAlign"
|
||||
type="list"
|
||||
label="MOD_MOKOJOOMHERO_TEXT_ALIGN_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_TEXT_ALIGN_DESC"
|
||||
default="center"
|
||||
>
|
||||
<option value="left">MOD_MOKOJOOMHERO_ALIGN_LEFT</option>
|
||||
<option value="center">MOD_MOKOJOOMHERO_ALIGN_CENTER</option>
|
||||
<option value="right">MOD_MOKOJOOMHERO_ALIGN_RIGHT</option>
|
||||
</field>
|
||||
<field
|
||||
name="textColor"
|
||||
type="color"
|
||||
label="MOD_MOKOJOOMHERO_TEXT_COLOR_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_TEXT_COLOR_DESC"
|
||||
default="#ffffff"
|
||||
/>
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
|
||||
<updateservers>
|
||||
<server type="extension" priority="1" name="MokoJoomHero Updates">
|
||||
https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/raw/branch/main/updates.xml
|
||||
</server>
|
||||
</updateservers>
|
||||
</extension>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<html><body bgcolor="#FFFFFF"></body></html>
|
||||
@@ -1 +0,0 @@
|
||||
<html><body bgcolor="#FFFFFF"></body></html>
|
||||
@@ -1 +0,0 @@
|
||||
<html><body bgcolor="#FFFFFF"></body></html>
|
||||
@@ -1,153 +0,0 @@
|
||||
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
; SPDX-License-Identifier: GPL-3.0-or-later
|
||||
;
|
||||
; FILE INFORMATION
|
||||
; DEFGROUP: MokoJoomHero.Module.Language
|
||||
; INGROUP: MokoJoomHero.Module
|
||||
; REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
||||
; PATH: /src/language/en-GB/mod_mokojoomhero.ini
|
||||
; VERSION: 01.08.00
|
||||
; BRIEF: Language strings for MokoJoomHero module (frontend + admin form fields)
|
||||
|
||||
MOD_MOKOJOOMHERO_NO_CONTENT="Add content to this module to display it over the hero image."
|
||||
|
||||
; Content fieldset
|
||||
MOD_MOKOJOOMHERO_FIELDSET_CONTENT="Hero Content"
|
||||
MOD_MOKOJOOMHERO_CONTENT_SOURCE_LABEL="Content Source"
|
||||
MOD_MOKOJOOMHERO_CONTENT_SOURCE_DESC="Choose whether to enter content manually or pull from a Joomla article."
|
||||
MOD_MOKOJOOMHERO_SOURCE_MANUAL="Manual Editor"
|
||||
MOD_MOKOJOOMHERO_SOURCE_ARTICLE="Joomla Article"
|
||||
MOD_MOKOJOOMHERO_CONTENT_LABEL="Content"
|
||||
MOD_MOKOJOOMHERO_CONTENT_DESC="HTML content displayed on the hero. Use the editor to add headings, text, buttons, or any HTML."
|
||||
MOD_MOKOJOOMHERO_ARTICLE_LABEL="Article"
|
||||
MOD_MOKOJOOMHERO_ARTICLE_DESC="Select a published article to use as the hero content. The article introtext (or fulltext) is displayed."
|
||||
MOD_MOKOJOOMHERO_ARTICLE_SELECT="- Select Article -"
|
||||
MOD_MOKOJOOMHERO_USE_ARTICLE_TITLE_LABEL="Use Article Title"
|
||||
MOD_MOKOJOOMHERO_USE_ARTICLE_TITLE_DESC="Replace the module title with the selected article's title."
|
||||
MOD_MOKOJOOMHERO_SHOW_CARD_LABEL="Show Card"
|
||||
MOD_MOKOJOOMHERO_SHOW_CARD_DESC="Wrap the content in a card with a white background and shadow."
|
||||
|
||||
; Hero mode
|
||||
MOD_MOKOJOOMHERO_MODE_LABEL="Hero Mode"
|
||||
MOD_MOKOJOOMHERO_MODE_DESC="Choose between a slideshow of images, an embedded video (YouTube/Vimeo), a local video file, a solid colour, or a gradient."
|
||||
MOD_MOKOJOOMHERO_MODE_IMAGES="Images"
|
||||
MOD_MOKOJOOMHERO_MODE_VIDEO="Video (YouTube/Vimeo)"
|
||||
MOD_MOKOJOOMHERO_MODE_LOCALVIDEO="Local Video"
|
||||
MOD_MOKOJOOMHERO_MODE_COLOR="Solid Colour"
|
||||
MOD_MOKOJOOMHERO_MODE_GRADIENT="Gradient"
|
||||
|
||||
; Transition type
|
||||
MOD_MOKOJOOMHERO_FADE_TYPE_LABEL="Transition Type"
|
||||
MOD_MOKOJOOMHERO_FADE_TYPE_DESC="How images transition between slides."
|
||||
MOD_MOKOJOOMHERO_FADE_CROSSFADE="Crossfade"
|
||||
MOD_MOKOJOOMHERO_FADE_SLIDE="Slide"
|
||||
MOD_MOKOJOOMHERO_FADE_BLACK="Fade to Black"
|
||||
MOD_MOKOJOOMHERO_FADE_ZOOM="Zoom (Ken Burns)"
|
||||
|
||||
; Image settings
|
||||
MOD_MOKOJOOMHERO_IMAGE_FOLDER_LABEL="Image Folder"
|
||||
MOD_MOKOJOOMHERO_IMAGE_FOLDER_DESC="Path to folder containing hero images, relative to Joomla root (e.g. images/heroes)."
|
||||
MOD_MOKOJOOMHERO_IMAGE_COUNT_LABEL="Number of Images"
|
||||
MOD_MOKOJOOMHERO_IMAGE_COUNT_DESC="How many random images to include in the slideshow (1–5)."
|
||||
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_LABEL="Slide Interval (ms)"
|
||||
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_DESC="Time between slides in milliseconds (e.g. 5000 = 5 seconds)."
|
||||
|
||||
; Per-slide content
|
||||
MOD_MOKOJOOMHERO_SLIDE_CONTENT_LABEL="Slide Content"
|
||||
MOD_MOKOJOOMHERO_SLIDE_CONTENT_DESC="Define individual slides with unique images and content. When populated, this overrides the random image folder. Leave empty to use the folder-based slideshow."
|
||||
MOD_MOKOJOOMHERO_SLIDE_IMAGE_LABEL="Image"
|
||||
MOD_MOKOJOOMHERO_SLIDE_HEADING_LABEL="Heading"
|
||||
MOD_MOKOJOOMHERO_SLIDE_BODY_LABEL="Body Text"
|
||||
MOD_MOKOJOOMHERO_SLIDE_LINK_LABEL="Link URL"
|
||||
MOD_MOKOJOOMHERO_SLIDE_LINK_TEXT_LABEL="Link Text"
|
||||
|
||||
; Video settings (embedded)
|
||||
MOD_MOKOJOOMHERO_VIDEO_FILE_LABEL="Video URL"
|
||||
MOD_MOKOJOOMHERO_VIDEO_FILE_DESC="YouTube or Vimeo URL. The module auto-detects the source."
|
||||
|
||||
; Local video settings
|
||||
MOD_MOKOJOOMHERO_LOCAL_VIDEO_LABEL="Video File"
|
||||
MOD_MOKOJOOMHERO_LOCAL_VIDEO_DESC="Select a video file from the Media Manager (mp4, webm, ogg)."
|
||||
|
||||
; Content animation
|
||||
MOD_MOKOJOOMHERO_CONTENT_ANIM_LABEL="Content Animation"
|
||||
MOD_MOKOJOOMHERO_CONTENT_ANIM_DESC="Entrance animation for the overlay content when the hero scrolls into view."
|
||||
MOD_MOKOJOOMHERO_CONTENT_ANIM_DELAY_LABEL="Animation Delay (ms)"
|
||||
MOD_MOKOJOOMHERO_CONTENT_ANIM_DELAY_DESC="Delay before the content animation starts."
|
||||
MOD_MOKOJOOMHERO_ANIM_NONE="None"
|
||||
MOD_MOKOJOOMHERO_ANIM_FADE_IN="Fade In"
|
||||
MOD_MOKOJOOMHERO_ANIM_SLIDE_UP="Slide Up"
|
||||
MOD_MOKOJOOMHERO_ANIM_SLIDE_LEFT="Slide from Right"
|
||||
MOD_MOKOJOOMHERO_ANIM_SLIDE_RIGHT="Slide from Left"
|
||||
|
||||
; Card delay
|
||||
MOD_MOKOJOOMHERO_CARD_DELAY_LABEL="Card Fade-in Delay (ms)"
|
||||
MOD_MOKOJOOMHERO_CARD_DELAY_DESC="Delay in milliseconds before the content card fades in. Set to 0 for no delay."
|
||||
|
||||
; Parallax
|
||||
MOD_MOKOJOOMHERO_PARALLAX_LABEL="Parallax Effect"
|
||||
MOD_MOKOJOOMHERO_PARALLAX_DESC="Background moves at a slower rate than page content on scroll, creating a depth effect."
|
||||
MOD_MOKOJOOMHERO_PARALLAX_SPEED_LABEL="Parallax Speed"
|
||||
MOD_MOKOJOOMHERO_PARALLAX_SPEED_DESC="How much the background moves relative to scroll (0.1 = subtle, 0.9 = dramatic)."
|
||||
|
||||
; Video poster
|
||||
MOD_MOKOJOOMHERO_VIDEO_POSTER_LABEL="Video Poster Image"
|
||||
MOD_MOKOJOOMHERO_VIDEO_POSTER_DESC="Fallback image displayed while the video loads. Prevents a blank hero on slow connections."
|
||||
|
||||
; Scroll indicator
|
||||
MOD_MOKOJOOMHERO_SCROLL_INDICATOR_LABEL="Show Scroll Indicator"
|
||||
MOD_MOKOJOOMHERO_SCROLL_INDICATOR_DESC="Show an animated down-arrow at the bottom of the hero prompting users to scroll."
|
||||
|
||||
; Mute toggle
|
||||
MOD_MOKOJOOMHERO_MUTE_TOGGLE_LABEL="Show Mute Toggle"
|
||||
MOD_MOKOJOOMHERO_MUTE_TOGGLE_DESC="Show a mute/unmute button on the hero video. Videos always start muted (required for autoplay)."
|
||||
|
||||
; Solid colour background
|
||||
MOD_MOKOJOOMHERO_BG_COLOR_LABEL="Background Colour"
|
||||
MOD_MOKOJOOMHERO_BG_COLOR_DESC="Solid background colour for the hero section."
|
||||
|
||||
; Gradient background
|
||||
MOD_MOKOJOOMHERO_GRADIENT_START_LABEL="Gradient Start Colour"
|
||||
MOD_MOKOJOOMHERO_GRADIENT_START_DESC="Starting colour of the gradient."
|
||||
MOD_MOKOJOOMHERO_GRADIENT_END_LABEL="Gradient End Colour"
|
||||
MOD_MOKOJOOMHERO_GRADIENT_END_DESC="Ending colour of the gradient."
|
||||
MOD_MOKOJOOMHERO_GRADIENT_ANGLE_LABEL="Gradient Angle"
|
||||
MOD_MOKOJOOMHERO_GRADIENT_ANGLE_DESC="Direction of the gradient in degrees (0 = bottom to top, 90 = left to right, 135 = diagonal)."
|
||||
|
||||
; Hero height
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_LABEL="Hero Height"
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_DESC="Height of the hero section. Use px for fixed pixels (e.g. 400px) or vh for viewport height (e.g. 60vh for 60%% of screen)."
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_HINT="e.g. 60vh or 400px"
|
||||
|
||||
; Hero height (mobile)
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_LABEL="Mobile Hero Height"
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_DESC="Height of the hero on mobile devices. Leave empty for auto height. Uses the same units as Hero Height."
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_HINT="e.g. 40vh or 300px (empty = auto)"
|
||||
|
||||
; Overlay fieldset
|
||||
MOD_MOKOJOOMHERO_FIELDSET_OVERLAY="Overlay & Text"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_TYPE_LABEL="Overlay Type"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_TYPE_DESC="How the overlay is applied. Solid fills evenly; gradient fades from transparent to opaque in the chosen direction."
|
||||
MOD_MOKOJOOMHERO_OVERLAY_SOLID="Solid"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_BOTTOM="Gradient (dark at bottom)"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_TOP="Gradient (dark at top)"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_LEFT="Gradient (dark at left)"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_RIGHT="Gradient (dark at right)"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_COLOR_LABEL="Overlay Colour"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_COLOR_DESC="Background colour of the overlay on top of the hero image."
|
||||
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_LABEL="Overlay Opacity"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_DESC="Transparency of the overlay (0 = fully transparent, 1 = fully opaque)."
|
||||
MOD_MOKOJOOMHERO_TEXT_ALIGN_LABEL="Text Alignment"
|
||||
MOD_MOKOJOOMHERO_TEXT_ALIGN_DESC="Horizontal alignment of the overlay text."
|
||||
MOD_MOKOJOOMHERO_VALIGN_LABEL="Vertical Alignment"
|
||||
MOD_MOKOJOOMHERO_VALIGN_DESC="Vertical position of the content within the hero."
|
||||
MOD_MOKOJOOMHERO_VALIGN_TOP="Top"
|
||||
MOD_MOKOJOOMHERO_VALIGN_CENTER="Centre"
|
||||
MOD_MOKOJOOMHERO_VALIGN_BOTTOM="Bottom"
|
||||
MOD_MOKOJOOMHERO_TEXT_COLOR_LABEL="Text Colour"
|
||||
MOD_MOKOJOOMHERO_TEXT_COLOR_DESC="Colour of the text displayed over the hero image."
|
||||
|
||||
; Horizontal alignment options
|
||||
MOD_MOKOJOOMHERO_ALIGN_LEFT="Left"
|
||||
MOD_MOKOJOOMHERO_ALIGN_CENTER="Centre"
|
||||
MOD_MOKOJOOMHERO_ALIGN_RIGHT="Right"
|
||||
@@ -1,154 +0,0 @@
|
||||
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
; SPDX-License-Identifier: GPL-3.0-or-later
|
||||
;
|
||||
; FILE INFORMATION
|
||||
; DEFGROUP: MokoJoomHero.Module.Language
|
||||
; INGROUP: MokoJoomHero.Module
|
||||
; REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
||||
; PATH: /src/language/en-GB/mod_mokojoomhero.sys.ini
|
||||
; VERSION: 01.08.00
|
||||
; BRIEF: System language strings — used in admin Extension Manager and Module Manager
|
||||
|
||||
MOD_MOKOJOOMHERO="Module - MokoJoomHero"
|
||||
MOD_MOKOJOOMHERO_DESCRIPTION="Displays a random hero image slideshow or background video with content overlaid. Designed for MokoOnyx template. By Moko Consulting."
|
||||
|
||||
; Content fieldset
|
||||
MOD_MOKOJOOMHERO_FIELDSET_CONTENT="Hero Content"
|
||||
MOD_MOKOJOOMHERO_CONTENT_SOURCE_LABEL="Content Source"
|
||||
MOD_MOKOJOOMHERO_CONTENT_SOURCE_DESC="Choose whether to enter content manually or pull from a Joomla article."
|
||||
MOD_MOKOJOOMHERO_SOURCE_MANUAL="Manual Editor"
|
||||
MOD_MOKOJOOMHERO_SOURCE_ARTICLE="Joomla Article"
|
||||
MOD_MOKOJOOMHERO_CONTENT_LABEL="Content"
|
||||
MOD_MOKOJOOMHERO_CONTENT_DESC="HTML content displayed on the hero. Use the editor to add headings, text, buttons, or any HTML."
|
||||
MOD_MOKOJOOMHERO_ARTICLE_LABEL="Article"
|
||||
MOD_MOKOJOOMHERO_ARTICLE_DESC="Select a published article to use as the hero content."
|
||||
MOD_MOKOJOOMHERO_ARTICLE_SELECT="- Select Article -"
|
||||
MOD_MOKOJOOMHERO_USE_ARTICLE_TITLE_LABEL="Use Article Title"
|
||||
MOD_MOKOJOOMHERO_USE_ARTICLE_TITLE_DESC="Replace the module title with the selected article's title."
|
||||
MOD_MOKOJOOMHERO_SHOW_CARD_LABEL="Show Card"
|
||||
MOD_MOKOJOOMHERO_SHOW_CARD_DESC="Wrap the content in a card with a white background and shadow."
|
||||
|
||||
; Hero mode
|
||||
MOD_MOKOJOOMHERO_MODE_LABEL="Hero Mode"
|
||||
MOD_MOKOJOOMHERO_MODE_DESC="Choose between a slideshow of images, an embedded video (YouTube/Vimeo), a local video file, a solid colour, or a gradient."
|
||||
MOD_MOKOJOOMHERO_MODE_IMAGES="Images"
|
||||
MOD_MOKOJOOMHERO_MODE_VIDEO="Video (YouTube/Vimeo)"
|
||||
MOD_MOKOJOOMHERO_MODE_LOCALVIDEO="Local Video"
|
||||
MOD_MOKOJOOMHERO_MODE_COLOR="Solid Colour"
|
||||
MOD_MOKOJOOMHERO_MODE_GRADIENT="Gradient"
|
||||
|
||||
; Transition type
|
||||
MOD_MOKOJOOMHERO_FADE_TYPE_LABEL="Transition Type"
|
||||
MOD_MOKOJOOMHERO_FADE_TYPE_DESC="How images transition between slides."
|
||||
MOD_MOKOJOOMHERO_FADE_CROSSFADE="Crossfade"
|
||||
MOD_MOKOJOOMHERO_FADE_SLIDE="Slide"
|
||||
MOD_MOKOJOOMHERO_FADE_BLACK="Fade to Black"
|
||||
MOD_MOKOJOOMHERO_FADE_ZOOM="Zoom (Ken Burns)"
|
||||
|
||||
; Image settings
|
||||
MOD_MOKOJOOMHERO_IMAGE_FOLDER_LABEL="Image Folder"
|
||||
MOD_MOKOJOOMHERO_IMAGE_FOLDER_DESC="Path to folder containing hero images, relative to Joomla root (e.g. images/heroes)."
|
||||
MOD_MOKOJOOMHERO_IMAGE_COUNT_LABEL="Number of Images"
|
||||
MOD_MOKOJOOMHERO_IMAGE_COUNT_DESC="How many random images to include in the slideshow (1–5)."
|
||||
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_LABEL="Slide Interval (ms)"
|
||||
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_DESC="Time between slides in milliseconds (e.g. 5000 = 5 seconds)."
|
||||
|
||||
; Per-slide content
|
||||
MOD_MOKOJOOMHERO_SLIDE_CONTENT_LABEL="Slide Content"
|
||||
MOD_MOKOJOOMHERO_SLIDE_CONTENT_DESC="Define individual slides with unique images and content."
|
||||
MOD_MOKOJOOMHERO_SLIDE_IMAGE_LABEL="Image"
|
||||
MOD_MOKOJOOMHERO_SLIDE_HEADING_LABEL="Heading"
|
||||
MOD_MOKOJOOMHERO_SLIDE_BODY_LABEL="Body Text"
|
||||
MOD_MOKOJOOMHERO_SLIDE_LINK_LABEL="Link URL"
|
||||
MOD_MOKOJOOMHERO_SLIDE_LINK_TEXT_LABEL="Link Text"
|
||||
|
||||
; Video settings (embedded)
|
||||
MOD_MOKOJOOMHERO_VIDEO_FILE_LABEL="Video URL"
|
||||
MOD_MOKOJOOMHERO_VIDEO_FILE_DESC="YouTube or Vimeo URL. The module auto-detects the source."
|
||||
|
||||
; Local video settings
|
||||
MOD_MOKOJOOMHERO_LOCAL_VIDEO_LABEL="Video File"
|
||||
MOD_MOKOJOOMHERO_LOCAL_VIDEO_DESC="Select a video file from the Media Manager (mp4, webm, ogg)."
|
||||
|
||||
; Content animation
|
||||
MOD_MOKOJOOMHERO_CONTENT_ANIM_LABEL="Content Animation"
|
||||
MOD_MOKOJOOMHERO_CONTENT_ANIM_DESC="Entrance animation for the overlay content when the hero scrolls into view."
|
||||
MOD_MOKOJOOMHERO_CONTENT_ANIM_DELAY_LABEL="Animation Delay (ms)"
|
||||
MOD_MOKOJOOMHERO_CONTENT_ANIM_DELAY_DESC="Delay before the content animation starts."
|
||||
MOD_MOKOJOOMHERO_ANIM_NONE="None"
|
||||
MOD_MOKOJOOMHERO_ANIM_FADE_IN="Fade In"
|
||||
MOD_MOKOJOOMHERO_ANIM_SLIDE_UP="Slide Up"
|
||||
MOD_MOKOJOOMHERO_ANIM_SLIDE_LEFT="Slide from Right"
|
||||
MOD_MOKOJOOMHERO_ANIM_SLIDE_RIGHT="Slide from Left"
|
||||
|
||||
; Card delay
|
||||
MOD_MOKOJOOMHERO_CARD_DELAY_LABEL="Card Fade-in Delay (ms)"
|
||||
MOD_MOKOJOOMHERO_CARD_DELAY_DESC="Delay in milliseconds before the content card fades in. Set to 0 for no delay."
|
||||
|
||||
; Parallax
|
||||
MOD_MOKOJOOMHERO_PARALLAX_LABEL="Parallax Effect"
|
||||
MOD_MOKOJOOMHERO_PARALLAX_DESC="Background moves at a slower rate than page content on scroll."
|
||||
MOD_MOKOJOOMHERO_PARALLAX_SPEED_LABEL="Parallax Speed"
|
||||
MOD_MOKOJOOMHERO_PARALLAX_SPEED_DESC="How much the background moves relative to scroll (0.1 = subtle, 0.9 = dramatic)."
|
||||
|
||||
; Video poster
|
||||
MOD_MOKOJOOMHERO_VIDEO_POSTER_LABEL="Video Poster Image"
|
||||
MOD_MOKOJOOMHERO_VIDEO_POSTER_DESC="Fallback image displayed while the video loads."
|
||||
|
||||
; Scroll indicator
|
||||
MOD_MOKOJOOMHERO_SCROLL_INDICATOR_LABEL="Show Scroll Indicator"
|
||||
MOD_MOKOJOOMHERO_SCROLL_INDICATOR_DESC="Show an animated down-arrow at the bottom of the hero prompting users to scroll."
|
||||
|
||||
; Mute toggle
|
||||
MOD_MOKOJOOMHERO_MUTE_TOGGLE_LABEL="Show Mute Toggle"
|
||||
MOD_MOKOJOOMHERO_MUTE_TOGGLE_DESC="Show a mute/unmute button on the hero video. Videos always start muted (required for autoplay)."
|
||||
|
||||
; Solid colour background
|
||||
MOD_MOKOJOOMHERO_BG_COLOR_LABEL="Background Colour"
|
||||
MOD_MOKOJOOMHERO_BG_COLOR_DESC="Solid background colour for the hero section."
|
||||
|
||||
; Gradient background
|
||||
MOD_MOKOJOOMHERO_GRADIENT_START_LABEL="Gradient Start Colour"
|
||||
MOD_MOKOJOOMHERO_GRADIENT_START_DESC="Starting colour of the gradient."
|
||||
MOD_MOKOJOOMHERO_GRADIENT_END_LABEL="Gradient End Colour"
|
||||
MOD_MOKOJOOMHERO_GRADIENT_END_DESC="Ending colour of the gradient."
|
||||
MOD_MOKOJOOMHERO_GRADIENT_ANGLE_LABEL="Gradient Angle"
|
||||
MOD_MOKOJOOMHERO_GRADIENT_ANGLE_DESC="Direction of the gradient in degrees (0 = bottom to top, 90 = left to right, 135 = diagonal)."
|
||||
|
||||
; Hero height
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_LABEL="Hero Height"
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_DESC="Height of the hero section. Use px for fixed pixels (e.g. 400px) or vh for viewport height (e.g. 60vh for 60%% of screen)."
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_HINT="e.g. 60vh or 400px"
|
||||
|
||||
; Hero height (mobile)
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_LABEL="Mobile Hero Height"
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_DESC="Height of the hero on mobile devices. Leave empty for auto height."
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_HINT="e.g. 40vh or 300px (empty = auto)"
|
||||
|
||||
; Overlay fieldset
|
||||
MOD_MOKOJOOMHERO_FIELDSET_OVERLAY="Overlay & Text"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_TYPE_LABEL="Overlay Type"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_TYPE_DESC="How the overlay is applied. Solid fills evenly; gradient fades from transparent to opaque in the chosen direction."
|
||||
MOD_MOKOJOOMHERO_OVERLAY_SOLID="Solid"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_BOTTOM="Gradient (dark at bottom)"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_TOP="Gradient (dark at top)"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_LEFT="Gradient (dark at left)"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_RIGHT="Gradient (dark at right)"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_COLOR_LABEL="Overlay Colour"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_COLOR_DESC="Background colour of the overlay on top of the hero image."
|
||||
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_LABEL="Overlay Opacity"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_DESC="Transparency of the overlay (0 = fully transparent, 1 = fully opaque)."
|
||||
MOD_MOKOJOOMHERO_TEXT_ALIGN_LABEL="Text Alignment"
|
||||
MOD_MOKOJOOMHERO_TEXT_ALIGN_DESC="Horizontal alignment of the overlay text."
|
||||
MOD_MOKOJOOMHERO_VALIGN_LABEL="Vertical Alignment"
|
||||
MOD_MOKOJOOMHERO_VALIGN_DESC="Vertical position of the content within the hero."
|
||||
MOD_MOKOJOOMHERO_VALIGN_TOP="Top"
|
||||
MOD_MOKOJOOMHERO_VALIGN_CENTER="Centre"
|
||||
MOD_MOKOJOOMHERO_VALIGN_BOTTOM="Bottom"
|
||||
MOD_MOKOJOOMHERO_TEXT_COLOR_LABEL="Text Colour"
|
||||
MOD_MOKOJOOMHERO_TEXT_COLOR_DESC="Colour of the text displayed over the hero image."
|
||||
|
||||
; Horizontal alignment options
|
||||
MOD_MOKOJOOMHERO_ALIGN_LEFT="Left"
|
||||
MOD_MOKOJOOMHERO_ALIGN_CENTER="Centre"
|
||||
MOD_MOKOJOOMHERO_ALIGN_RIGHT="Right"
|
||||
@@ -1 +0,0 @@
|
||||
<html><body bgcolor="#FFFFFF"></body></html>
|
||||
@@ -1,153 +0,0 @@
|
||||
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
; SPDX-License-Identifier: GPL-3.0-or-later
|
||||
;
|
||||
; FILE INFORMATION
|
||||
; DEFGROUP: MokoJoomHero.Module.Language
|
||||
; INGROUP: MokoJoomHero.Module
|
||||
; REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
||||
; PATH: /src/language/en-US/mod_mokojoomhero.ini
|
||||
; VERSION: 01.08.00
|
||||
; BRIEF: Language strings for MokoJoomHero module (en-US, frontend + admin form fields)
|
||||
|
||||
MOD_MOKOJOOMHERO_NO_CONTENT="Add content to this module to display it over the hero image."
|
||||
|
||||
; Content fieldset
|
||||
MOD_MOKOJOOMHERO_FIELDSET_CONTENT="Hero Content"
|
||||
MOD_MOKOJOOMHERO_CONTENT_SOURCE_LABEL="Content Source"
|
||||
MOD_MOKOJOOMHERO_CONTENT_SOURCE_DESC="Choose whether to enter content manually or pull from a Joomla article."
|
||||
MOD_MOKOJOOMHERO_SOURCE_MANUAL="Manual Editor"
|
||||
MOD_MOKOJOOMHERO_SOURCE_ARTICLE="Joomla Article"
|
||||
MOD_MOKOJOOMHERO_CONTENT_LABEL="Content"
|
||||
MOD_MOKOJOOMHERO_CONTENT_DESC="HTML content displayed on the hero. Use the editor to add headings, text, buttons, or any HTML."
|
||||
MOD_MOKOJOOMHERO_ARTICLE_LABEL="Article"
|
||||
MOD_MOKOJOOMHERO_ARTICLE_DESC="Select a published article to use as the hero content. The article introtext (or fulltext) is displayed."
|
||||
MOD_MOKOJOOMHERO_ARTICLE_SELECT="- Select Article -"
|
||||
MOD_MOKOJOOMHERO_USE_ARTICLE_TITLE_LABEL="Use Article Title"
|
||||
MOD_MOKOJOOMHERO_USE_ARTICLE_TITLE_DESC="Replace the module title with the selected article's title."
|
||||
MOD_MOKOJOOMHERO_SHOW_CARD_LABEL="Show Card"
|
||||
MOD_MOKOJOOMHERO_SHOW_CARD_DESC="Wrap the content in a card with a white background and shadow."
|
||||
|
||||
; Hero mode
|
||||
MOD_MOKOJOOMHERO_MODE_LABEL="Hero Mode"
|
||||
MOD_MOKOJOOMHERO_MODE_DESC="Choose between a slideshow of images, an embedded video (YouTube/Vimeo), a local video file, a solid color, or a gradient."
|
||||
MOD_MOKOJOOMHERO_MODE_IMAGES="Images"
|
||||
MOD_MOKOJOOMHERO_MODE_VIDEO="Video (YouTube/Vimeo)"
|
||||
MOD_MOKOJOOMHERO_MODE_LOCALVIDEO="Local Video"
|
||||
MOD_MOKOJOOMHERO_MODE_COLOR="Solid Color"
|
||||
MOD_MOKOJOOMHERO_MODE_GRADIENT="Gradient"
|
||||
|
||||
; Transition type
|
||||
MOD_MOKOJOOMHERO_FADE_TYPE_LABEL="Transition Type"
|
||||
MOD_MOKOJOOMHERO_FADE_TYPE_DESC="How images transition between slides."
|
||||
MOD_MOKOJOOMHERO_FADE_CROSSFADE="Crossfade"
|
||||
MOD_MOKOJOOMHERO_FADE_SLIDE="Slide"
|
||||
MOD_MOKOJOOMHERO_FADE_BLACK="Fade to Black"
|
||||
MOD_MOKOJOOMHERO_FADE_ZOOM="Zoom (Ken Burns)"
|
||||
|
||||
; Image settings
|
||||
MOD_MOKOJOOMHERO_IMAGE_FOLDER_LABEL="Image Folder"
|
||||
MOD_MOKOJOOMHERO_IMAGE_FOLDER_DESC="Path to folder containing hero images, relative to Joomla root (e.g. images/heroes)."
|
||||
MOD_MOKOJOOMHERO_IMAGE_COUNT_LABEL="Number of Images"
|
||||
MOD_MOKOJOOMHERO_IMAGE_COUNT_DESC="How many random images to include in the slideshow (1-5)."
|
||||
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_LABEL="Slide Interval (ms)"
|
||||
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_DESC="Time between slides in milliseconds (e.g. 5000 = 5 seconds)."
|
||||
|
||||
; Per-slide content
|
||||
MOD_MOKOJOOMHERO_SLIDE_CONTENT_LABEL="Slide Content"
|
||||
MOD_MOKOJOOMHERO_SLIDE_CONTENT_DESC="Define individual slides with unique images and content. When populated, this overrides the random image folder. Leave empty to use the folder-based slideshow."
|
||||
MOD_MOKOJOOMHERO_SLIDE_IMAGE_LABEL="Image"
|
||||
MOD_MOKOJOOMHERO_SLIDE_HEADING_LABEL="Heading"
|
||||
MOD_MOKOJOOMHERO_SLIDE_BODY_LABEL="Body Text"
|
||||
MOD_MOKOJOOMHERO_SLIDE_LINK_LABEL="Link URL"
|
||||
MOD_MOKOJOOMHERO_SLIDE_LINK_TEXT_LABEL="Link Text"
|
||||
|
||||
; Video settings (embedded)
|
||||
MOD_MOKOJOOMHERO_VIDEO_FILE_LABEL="Video URL"
|
||||
MOD_MOKOJOOMHERO_VIDEO_FILE_DESC="YouTube or Vimeo URL. The module auto-detects the source."
|
||||
|
||||
; Local video settings
|
||||
MOD_MOKOJOOMHERO_LOCAL_VIDEO_LABEL="Video File"
|
||||
MOD_MOKOJOOMHERO_LOCAL_VIDEO_DESC="Select a video file from the Media Manager (mp4, webm, ogg)."
|
||||
|
||||
; Solid color background
|
||||
MOD_MOKOJOOMHERO_BG_COLOR_LABEL="Background Color"
|
||||
MOD_MOKOJOOMHERO_BG_COLOR_DESC="Solid background color for the hero section."
|
||||
|
||||
; Gradient background
|
||||
MOD_MOKOJOOMHERO_GRADIENT_START_LABEL="Gradient Start Color"
|
||||
MOD_MOKOJOOMHERO_GRADIENT_START_DESC="Starting color of the gradient."
|
||||
MOD_MOKOJOOMHERO_GRADIENT_END_LABEL="Gradient End Color"
|
||||
MOD_MOKOJOOMHERO_GRADIENT_END_DESC="Ending color of the gradient."
|
||||
MOD_MOKOJOOMHERO_GRADIENT_ANGLE_LABEL="Gradient Angle"
|
||||
MOD_MOKOJOOMHERO_GRADIENT_ANGLE_DESC="Direction of the gradient in degrees (0 = bottom to top, 90 = left to right, 135 = diagonal)."
|
||||
|
||||
; Hero height
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_LABEL="Hero Height"
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_DESC="Height of the hero section. Use px for fixed pixels (e.g. 400px) or vh for viewport height (e.g. 60vh for 60% of screen)."
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_HINT="e.g. 60vh or 400px"
|
||||
|
||||
; Card delay
|
||||
MOD_MOKOJOOMHERO_CARD_DELAY_LABEL="Card Fade-in Delay (ms)"
|
||||
MOD_MOKOJOOMHERO_CARD_DELAY_DESC="Delay in milliseconds before the content card fades in. Set to 0 for no delay."
|
||||
|
||||
; Content animation
|
||||
MOD_MOKOJOOMHERO_CONTENT_ANIM_LABEL="Content Animation"
|
||||
MOD_MOKOJOOMHERO_CONTENT_ANIM_DESC="Entrance animation for the overlay content when the hero scrolls into view."
|
||||
MOD_MOKOJOOMHERO_CONTENT_ANIM_DELAY_LABEL="Animation Delay (ms)"
|
||||
MOD_MOKOJOOMHERO_CONTENT_ANIM_DELAY_DESC="Delay before the content animation starts."
|
||||
MOD_MOKOJOOMHERO_ANIM_NONE="None"
|
||||
MOD_MOKOJOOMHERO_ANIM_FADE_IN="Fade In"
|
||||
MOD_MOKOJOOMHERO_ANIM_SLIDE_UP="Slide Up"
|
||||
MOD_MOKOJOOMHERO_ANIM_SLIDE_LEFT="Slide from Right"
|
||||
MOD_MOKOJOOMHERO_ANIM_SLIDE_RIGHT="Slide from Left"
|
||||
|
||||
; Parallax
|
||||
MOD_MOKOJOOMHERO_PARALLAX_LABEL="Parallax Effect"
|
||||
MOD_MOKOJOOMHERO_PARALLAX_DESC="Background moves at a slower rate than page content on scroll, creating a depth effect."
|
||||
MOD_MOKOJOOMHERO_PARALLAX_SPEED_LABEL="Parallax Speed"
|
||||
MOD_MOKOJOOMHERO_PARALLAX_SPEED_DESC="How much the background moves relative to scroll (0.1 = subtle, 0.9 = dramatic)."
|
||||
|
||||
; Video poster
|
||||
MOD_MOKOJOOMHERO_VIDEO_POSTER_LABEL="Video Poster Image"
|
||||
MOD_MOKOJOOMHERO_VIDEO_POSTER_DESC="Fallback image displayed while the video loads. Prevents a blank hero on slow connections."
|
||||
|
||||
; Scroll indicator
|
||||
MOD_MOKOJOOMHERO_SCROLL_INDICATOR_LABEL="Show Scroll Indicator"
|
||||
MOD_MOKOJOOMHERO_SCROLL_INDICATOR_DESC="Show an animated down-arrow at the bottom of the hero prompting users to scroll."
|
||||
|
||||
; Mute toggle
|
||||
MOD_MOKOJOOMHERO_MUTE_TOGGLE_LABEL="Show Mute Toggle"
|
||||
MOD_MOKOJOOMHERO_MUTE_TOGGLE_DESC="Show a mute/unmute button on the hero video. Videos always start muted (required for autoplay)."
|
||||
|
||||
; Hero height (mobile)
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_LABEL="Mobile Hero Height"
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_DESC="Height of the hero on mobile devices. Leave empty for auto height. Uses the same units as Hero Height."
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_HINT="e.g. 40vh or 300px (empty = auto)"
|
||||
|
||||
; Overlay fieldset
|
||||
MOD_MOKOJOOMHERO_FIELDSET_OVERLAY="Overlay & Text"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_TYPE_LABEL="Overlay Type"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_TYPE_DESC="How the overlay is applied. Solid fills evenly; gradient fades from transparent to opaque in the chosen direction."
|
||||
MOD_MOKOJOOMHERO_OVERLAY_SOLID="Solid"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_BOTTOM="Gradient (dark at bottom)"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_TOP="Gradient (dark at top)"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_LEFT="Gradient (dark at left)"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_RIGHT="Gradient (dark at right)"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_COLOR_LABEL="Overlay Color"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_COLOR_DESC="Background color of the overlay on top of the hero image."
|
||||
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_LABEL="Overlay Opacity"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_DESC="Transparency of the overlay (0 = fully transparent, 1 = fully opaque)."
|
||||
MOD_MOKOJOOMHERO_TEXT_ALIGN_LABEL="Text Alignment"
|
||||
MOD_MOKOJOOMHERO_TEXT_ALIGN_DESC="Horizontal alignment of the overlay text."
|
||||
MOD_MOKOJOOMHERO_VALIGN_LABEL="Vertical Alignment"
|
||||
MOD_MOKOJOOMHERO_VALIGN_DESC="Vertical position of the content within the hero."
|
||||
MOD_MOKOJOOMHERO_VALIGN_TOP="Top"
|
||||
MOD_MOKOJOOMHERO_VALIGN_CENTER="Center"
|
||||
MOD_MOKOJOOMHERO_VALIGN_BOTTOM="Bottom"
|
||||
MOD_MOKOJOOMHERO_TEXT_COLOR_LABEL="Text Color"
|
||||
MOD_MOKOJOOMHERO_TEXT_COLOR_DESC="Color of the text displayed over the hero image."
|
||||
|
||||
; Horizontal alignment options
|
||||
MOD_MOKOJOOMHERO_ALIGN_LEFT="Left"
|
||||
MOD_MOKOJOOMHERO_ALIGN_CENTER="Center"
|
||||
MOD_MOKOJOOMHERO_ALIGN_RIGHT="Right"
|
||||
@@ -1,154 +0,0 @@
|
||||
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
; SPDX-License-Identifier: GPL-3.0-or-later
|
||||
;
|
||||
; FILE INFORMATION
|
||||
; DEFGROUP: MokoJoomHero.Module.Language
|
||||
; INGROUP: MokoJoomHero.Module
|
||||
; REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
||||
; PATH: /src/language/en-US/mod_mokojoomhero.sys.ini
|
||||
; VERSION: 01.08.00
|
||||
; BRIEF: System language strings — used in admin Extension Manager and Module Manager (en-US)
|
||||
|
||||
MOD_MOKOJOOMHERO="Module - MokoJoomHero"
|
||||
MOD_MOKOJOOMHERO_DESCRIPTION="Displays a random hero image slideshow or background video with content overlaid. Designed for MokoOnyx template. By Moko Consulting."
|
||||
|
||||
; Content fieldset
|
||||
MOD_MOKOJOOMHERO_FIELDSET_CONTENT="Hero Content"
|
||||
MOD_MOKOJOOMHERO_CONTENT_SOURCE_LABEL="Content Source"
|
||||
MOD_MOKOJOOMHERO_CONTENT_SOURCE_DESC="Choose whether to enter content manually or pull from a Joomla article."
|
||||
MOD_MOKOJOOMHERO_SOURCE_MANUAL="Manual Editor"
|
||||
MOD_MOKOJOOMHERO_SOURCE_ARTICLE="Joomla Article"
|
||||
MOD_MOKOJOOMHERO_CONTENT_LABEL="Content"
|
||||
MOD_MOKOJOOMHERO_CONTENT_DESC="HTML content displayed on the hero. Use the editor to add headings, text, buttons, or any HTML."
|
||||
MOD_MOKOJOOMHERO_ARTICLE_LABEL="Article"
|
||||
MOD_MOKOJOOMHERO_ARTICLE_DESC="Select a published article to use as the hero content."
|
||||
MOD_MOKOJOOMHERO_ARTICLE_SELECT="- Select Article -"
|
||||
MOD_MOKOJOOMHERO_USE_ARTICLE_TITLE_LABEL="Use Article Title"
|
||||
MOD_MOKOJOOMHERO_USE_ARTICLE_TITLE_DESC="Replace the module title with the selected article's title."
|
||||
MOD_MOKOJOOMHERO_SHOW_CARD_LABEL="Show Card"
|
||||
MOD_MOKOJOOMHERO_SHOW_CARD_DESC="Wrap the content in a card with a white background and shadow."
|
||||
|
||||
; Hero mode
|
||||
MOD_MOKOJOOMHERO_MODE_LABEL="Hero Mode"
|
||||
MOD_MOKOJOOMHERO_MODE_DESC="Choose between a slideshow of images, an embedded video (YouTube/Vimeo), a local video file, a solid color, or a gradient."
|
||||
MOD_MOKOJOOMHERO_MODE_IMAGES="Images"
|
||||
MOD_MOKOJOOMHERO_MODE_VIDEO="Video (YouTube/Vimeo)"
|
||||
MOD_MOKOJOOMHERO_MODE_LOCALVIDEO="Local Video"
|
||||
MOD_MOKOJOOMHERO_MODE_COLOR="Solid Color"
|
||||
MOD_MOKOJOOMHERO_MODE_GRADIENT="Gradient"
|
||||
|
||||
; Transition type
|
||||
MOD_MOKOJOOMHERO_FADE_TYPE_LABEL="Transition Type"
|
||||
MOD_MOKOJOOMHERO_FADE_TYPE_DESC="How images transition between slides."
|
||||
MOD_MOKOJOOMHERO_FADE_CROSSFADE="Crossfade"
|
||||
MOD_MOKOJOOMHERO_FADE_SLIDE="Slide"
|
||||
MOD_MOKOJOOMHERO_FADE_BLACK="Fade to Black"
|
||||
MOD_MOKOJOOMHERO_FADE_ZOOM="Zoom (Ken Burns)"
|
||||
|
||||
; Image settings
|
||||
MOD_MOKOJOOMHERO_IMAGE_FOLDER_LABEL="Image Folder"
|
||||
MOD_MOKOJOOMHERO_IMAGE_FOLDER_DESC="Path to folder containing hero images, relative to Joomla root (e.g. images/heroes)."
|
||||
MOD_MOKOJOOMHERO_IMAGE_COUNT_LABEL="Number of Images"
|
||||
MOD_MOKOJOOMHERO_IMAGE_COUNT_DESC="How many random images to include in the slideshow (1-5)."
|
||||
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_LABEL="Slide Interval (ms)"
|
||||
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_DESC="Time between slides in milliseconds (e.g. 5000 = 5 seconds)."
|
||||
|
||||
; Per-slide content
|
||||
MOD_MOKOJOOMHERO_SLIDE_CONTENT_LABEL="Slide Content"
|
||||
MOD_MOKOJOOMHERO_SLIDE_CONTENT_DESC="Define individual slides with unique images and content."
|
||||
MOD_MOKOJOOMHERO_SLIDE_IMAGE_LABEL="Image"
|
||||
MOD_MOKOJOOMHERO_SLIDE_HEADING_LABEL="Heading"
|
||||
MOD_MOKOJOOMHERO_SLIDE_BODY_LABEL="Body Text"
|
||||
MOD_MOKOJOOMHERO_SLIDE_LINK_LABEL="Link URL"
|
||||
MOD_MOKOJOOMHERO_SLIDE_LINK_TEXT_LABEL="Link Text"
|
||||
|
||||
; Video settings (embedded)
|
||||
MOD_MOKOJOOMHERO_VIDEO_FILE_LABEL="Video URL"
|
||||
MOD_MOKOJOOMHERO_VIDEO_FILE_DESC="YouTube or Vimeo URL. The module auto-detects the source."
|
||||
|
||||
; Local video settings
|
||||
MOD_MOKOJOOMHERO_LOCAL_VIDEO_LABEL="Video File"
|
||||
MOD_MOKOJOOMHERO_LOCAL_VIDEO_DESC="Select a video file from the Media Manager (mp4, webm, ogg)."
|
||||
|
||||
; Content animation
|
||||
MOD_MOKOJOOMHERO_CONTENT_ANIM_LABEL="Content Animation"
|
||||
MOD_MOKOJOOMHERO_CONTENT_ANIM_DESC="Entrance animation for the overlay content when the hero scrolls into view."
|
||||
MOD_MOKOJOOMHERO_CONTENT_ANIM_DELAY_LABEL="Animation Delay (ms)"
|
||||
MOD_MOKOJOOMHERO_CONTENT_ANIM_DELAY_DESC="Delay before the content animation starts."
|
||||
MOD_MOKOJOOMHERO_ANIM_NONE="None"
|
||||
MOD_MOKOJOOMHERO_ANIM_FADE_IN="Fade In"
|
||||
MOD_MOKOJOOMHERO_ANIM_SLIDE_UP="Slide Up"
|
||||
MOD_MOKOJOOMHERO_ANIM_SLIDE_LEFT="Slide from Right"
|
||||
MOD_MOKOJOOMHERO_ANIM_SLIDE_RIGHT="Slide from Left"
|
||||
|
||||
; Card delay
|
||||
MOD_MOKOJOOMHERO_CARD_DELAY_LABEL="Card Fade-in Delay (ms)"
|
||||
MOD_MOKOJOOMHERO_CARD_DELAY_DESC="Delay in milliseconds before the content card fades in. Set to 0 for no delay."
|
||||
|
||||
; Parallax
|
||||
MOD_MOKOJOOMHERO_PARALLAX_LABEL="Parallax Effect"
|
||||
MOD_MOKOJOOMHERO_PARALLAX_DESC="Background moves at a slower rate than page content on scroll."
|
||||
MOD_MOKOJOOMHERO_PARALLAX_SPEED_LABEL="Parallax Speed"
|
||||
MOD_MOKOJOOMHERO_PARALLAX_SPEED_DESC="How much the background moves relative to scroll (0.1 = subtle, 0.9 = dramatic)."
|
||||
|
||||
; Video poster
|
||||
MOD_MOKOJOOMHERO_VIDEO_POSTER_LABEL="Video Poster Image"
|
||||
MOD_MOKOJOOMHERO_VIDEO_POSTER_DESC="Fallback image displayed while the video loads."
|
||||
|
||||
; Scroll indicator
|
||||
MOD_MOKOJOOMHERO_SCROLL_INDICATOR_LABEL="Show Scroll Indicator"
|
||||
MOD_MOKOJOOMHERO_SCROLL_INDICATOR_DESC="Show an animated down-arrow at the bottom of the hero prompting users to scroll."
|
||||
|
||||
; Mute toggle
|
||||
MOD_MOKOJOOMHERO_MUTE_TOGGLE_LABEL="Show Mute Toggle"
|
||||
MOD_MOKOJOOMHERO_MUTE_TOGGLE_DESC="Show a mute/unmute button on the hero video. Videos always start muted (required for autoplay)."
|
||||
|
||||
; Solid color background
|
||||
MOD_MOKOJOOMHERO_BG_COLOR_LABEL="Background Color"
|
||||
MOD_MOKOJOOMHERO_BG_COLOR_DESC="Solid background color for the hero section."
|
||||
|
||||
; Gradient background
|
||||
MOD_MOKOJOOMHERO_GRADIENT_START_LABEL="Gradient Start Color"
|
||||
MOD_MOKOJOOMHERO_GRADIENT_START_DESC="Starting color of the gradient."
|
||||
MOD_MOKOJOOMHERO_GRADIENT_END_LABEL="Gradient End Color"
|
||||
MOD_MOKOJOOMHERO_GRADIENT_END_DESC="Ending color of the gradient."
|
||||
MOD_MOKOJOOMHERO_GRADIENT_ANGLE_LABEL="Gradient Angle"
|
||||
MOD_MOKOJOOMHERO_GRADIENT_ANGLE_DESC="Direction of the gradient in degrees (0 = bottom to top, 90 = left to right, 135 = diagonal)."
|
||||
|
||||
; Hero height
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_LABEL="Hero Height"
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_DESC="Height of the hero section. Use px for fixed pixels (e.g. 400px) or vh for viewport height (e.g. 60vh for 60% of screen)."
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_HINT="e.g. 60vh or 400px"
|
||||
|
||||
; Hero height (mobile)
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_LABEL="Mobile Hero Height"
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_DESC="Height of the hero on mobile devices. Leave empty for auto height."
|
||||
MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_HINT="e.g. 40vh or 300px (empty = auto)"
|
||||
|
||||
; Overlay fieldset
|
||||
MOD_MOKOJOOMHERO_FIELDSET_OVERLAY="Overlay & Text"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_TYPE_LABEL="Overlay Type"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_TYPE_DESC="How the overlay is applied. Solid fills evenly; gradient fades from transparent to opaque in the chosen direction."
|
||||
MOD_MOKOJOOMHERO_OVERLAY_SOLID="Solid"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_BOTTOM="Gradient (dark at bottom)"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_TOP="Gradient (dark at top)"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_LEFT="Gradient (dark at left)"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_RIGHT="Gradient (dark at right)"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_COLOR_LABEL="Overlay Color"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_COLOR_DESC="Background color of the overlay on top of the hero image."
|
||||
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_LABEL="Overlay Opacity"
|
||||
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_DESC="Transparency of the overlay (0 = fully transparent, 1 = fully opaque)."
|
||||
MOD_MOKOJOOMHERO_TEXT_ALIGN_LABEL="Text Alignment"
|
||||
MOD_MOKOJOOMHERO_TEXT_ALIGN_DESC="Horizontal alignment of the overlay text."
|
||||
MOD_MOKOJOOMHERO_VALIGN_LABEL="Vertical Alignment"
|
||||
MOD_MOKOJOOMHERO_VALIGN_DESC="Vertical position of the content within the hero."
|
||||
MOD_MOKOJOOMHERO_VALIGN_TOP="Top"
|
||||
MOD_MOKOJOOMHERO_VALIGN_CENTER="Center"
|
||||
MOD_MOKOJOOMHERO_VALIGN_BOTTOM="Bottom"
|
||||
MOD_MOKOJOOMHERO_TEXT_COLOR_LABEL="Text Color"
|
||||
MOD_MOKOJOOMHERO_TEXT_COLOR_DESC="Color of the text displayed over the hero image."
|
||||
|
||||
; Horizontal alignment options
|
||||
MOD_MOKOJOOMHERO_ALIGN_LEFT="Left"
|
||||
MOD_MOKOJOOMHERO_ALIGN_CENTER="Center"
|
||||
MOD_MOKOJOOMHERO_ALIGN_RIGHT="Right"
|
||||
@@ -1 +0,0 @@
|
||||
<html><body bgcolor="#FFFFFF"></body></html>
|
||||
@@ -1 +0,0 @@
|
||||
<html><body bgcolor="#FFFFFF"></body></html>
|
||||
@@ -1,382 +0,0 @@
|
||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: MokoJoomHero.Module.Assets
|
||||
* INGROUP: MokoJoomHero.Module
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
||||
* PATH: /src/packages/mod_mokojoomhero/media/css/mod_mokojoomhero.css
|
||||
* VERSION: 01.08.00
|
||||
* BRIEF: Hero module stylesheet — slideshow, video, colour/gradient, overlay, card, mute toggle, responsive
|
||||
*/
|
||||
|
||||
/* ============================================================
|
||||
Hero container
|
||||
============================================================ */
|
||||
.mokojoomhero {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Solid colour / gradient background
|
||||
============================================================ */
|
||||
.mokojoomhero__color {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Image slides — base
|
||||
============================================================ */
|
||||
.mokojoomhero__slide {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.mokojoomhero__slide--active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* ── Crossfade (default) ── */
|
||||
.mokojoomhero[data-transition="crossfade"] .mokojoomhero__slide {
|
||||
transition: opacity 1s ease;
|
||||
}
|
||||
|
||||
/* ── Slide ── */
|
||||
.mokojoomhero[data-transition="slide"] .mokojoomhero__slide {
|
||||
opacity: 1;
|
||||
transform: translateX(100%);
|
||||
transition: transform 0.8s ease;
|
||||
}
|
||||
|
||||
.mokojoomhero[data-transition="slide"] .mokojoomhero__slide--active {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.mokojoomhero[data-transition="slide"] .mokojoomhero__slide--exit {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
/* ── Fade to black ── */
|
||||
.mokojoomhero[data-transition="fade-black"] .mokojoomhero__slide {
|
||||
transition: opacity 0.6s ease;
|
||||
}
|
||||
|
||||
/* ── Zoom (Ken Burns) ── */
|
||||
.mokojoomhero[data-transition="zoom"] .mokojoomhero__slide {
|
||||
transition: opacity 1s ease;
|
||||
}
|
||||
|
||||
.mokojoomhero[data-transition="zoom"] .mokojoomhero__slide--active {
|
||||
animation: mokojoomhero-zoom 8s ease forwards;
|
||||
}
|
||||
|
||||
@keyframes mokojoomhero-zoom {
|
||||
from { transform: scale(1); }
|
||||
to { transform: scale(1.08); }
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Video background
|
||||
============================================================ */
|
||||
/* Native <video> elements: object-fit works directly */
|
||||
video.mokojoomhero__video {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Embedded <iframe> (YouTube/Vimeo): object-fit doesn't apply to iframes,
|
||||
so we oversize the iframe and centre-crop via the parent's overflow:hidden */
|
||||
iframe.mokojoomhero__video {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 100vw;
|
||||
height: 56.25vw; /* 16:9 aspect ratio */
|
||||
min-height: 100%;
|
||||
min-width: 177.78vh; /* 100 × 16/9 — ensures cover in portrait viewports */
|
||||
transform: translate(-50%, -50%);
|
||||
border: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Overlay
|
||||
============================================================ */
|
||||
.mokojoomhero__overlay {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Content
|
||||
============================================================ */
|
||||
.mokojoomhero__content {
|
||||
max-width: 900px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mokojoomhero__content h1,
|
||||
.mokojoomhero__content h2,
|
||||
.mokojoomhero__content h3 {
|
||||
margin-top: 0;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.mokojoomhero__content p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Card
|
||||
============================================================ */
|
||||
.mokojoomhero__card {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
color: #333;
|
||||
border-radius: 8px;
|
||||
padding: 2rem 2.5rem;
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.mokojoomhero__card h1,
|
||||
.mokojoomhero__card h2,
|
||||
.mokojoomhero__card h3 {
|
||||
color: #222;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.mokojoomhero__card a:not(.btn) {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Card fade-in delay
|
||||
============================================================ */
|
||||
.mokojoomhero__card[data-card-delay] {
|
||||
opacity: 0;
|
||||
animation: mokojoomhero-fadein 0.6s ease forwards;
|
||||
}
|
||||
|
||||
@keyframes mokojoomhero-fadein {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Mute toggle
|
||||
============================================================ */
|
||||
.mokojoomhero__mute-toggle {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
right: 1rem;
|
||||
z-index: 2;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.mokojoomhero__mute-toggle:hover {
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Video poster image
|
||||
============================================================ */
|
||||
.mokojoomhero__poster {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Scroll-down indicator
|
||||
============================================================ */
|
||||
.mokojoomhero__scroll-indicator {
|
||||
position: absolute;
|
||||
bottom: 1.5rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 2;
|
||||
background: none;
|
||||
border: none;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.3s;
|
||||
animation: mokojoomhero-bounce 2s infinite;
|
||||
}
|
||||
|
||||
.mokojoomhero__scroll-indicator:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.mokojoomhero__scroll-indicator--hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@keyframes mokojoomhero-bounce {
|
||||
0%, 20%, 50%, 80%, 100% { transform: translateX(-50%) translateY(0); }
|
||||
40% { transform: translateX(-50%) translateY(-8px); }
|
||||
60% { transform: translateX(-50%) translateY(-4px); }
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Content entrance animations
|
||||
============================================================ */
|
||||
.mokojoomhero__content[class*="mokojoomhero__content--anim-"] {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.mokojoomhero__content--anim-fade-in.mokojoomhero__content--visible {
|
||||
animation: mokojoomhero-anim-fade-in 0.8s ease forwards;
|
||||
}
|
||||
|
||||
.mokojoomhero__content--anim-slide-up.mokojoomhero__content--visible {
|
||||
animation: mokojoomhero-anim-slide-up 0.8s ease forwards;
|
||||
}
|
||||
|
||||
.mokojoomhero__content--anim-slide-left.mokojoomhero__content--visible {
|
||||
animation: mokojoomhero-anim-slide-left 0.8s ease forwards;
|
||||
}
|
||||
|
||||
.mokojoomhero__content--anim-slide-right.mokojoomhero__content--visible {
|
||||
animation: mokojoomhero-anim-slide-right 0.8s ease forwards;
|
||||
}
|
||||
|
||||
@keyframes mokojoomhero-anim-fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes mokojoomhero-anim-slide-up {
|
||||
from { opacity: 0; transform: translateY(30px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes mokojoomhero-anim-slide-left {
|
||||
from { opacity: 0; transform: translateX(30px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
|
||||
@keyframes mokojoomhero-anim-slide-right {
|
||||
from { opacity: 0; transform: translateX(-30px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Parallax
|
||||
============================================================ */
|
||||
.mokojoomhero[data-parallax] .mokojoomhero__slide,
|
||||
.mokojoomhero[data-parallax] .mokojoomhero__color,
|
||||
.mokojoomhero[data-parallax] .mokojoomhero__poster,
|
||||
.mokojoomhero[data-parallax] video.mokojoomhero__video,
|
||||
.mokojoomhero[data-parallax] iframe.mokojoomhero__video {
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Reduced motion — WCAG 2.1 AA (SC 2.3.3)
|
||||
============================================================ */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.mokojoomhero__slide {
|
||||
transition: none !important;
|
||||
animation: none !important;
|
||||
}
|
||||
|
||||
.mokojoomhero__card[data-card-delay] {
|
||||
opacity: 1;
|
||||
animation: none !important;
|
||||
}
|
||||
|
||||
.mokojoomhero__scroll-indicator {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.mokojoomhero[data-transition="zoom"] .mokojoomhero__slide--active {
|
||||
animation: none !important;
|
||||
}
|
||||
|
||||
.mokojoomhero__content[class*="mokojoomhero__content--anim-"] {
|
||||
opacity: 1;
|
||||
animation: none !important;
|
||||
}
|
||||
|
||||
.mokojoomhero[data-parallax] .mokojoomhero__slide,
|
||||
.mokojoomhero[data-parallax] .mokojoomhero__color,
|
||||
.mokojoomhero[data-parallax] .mokojoomhero__poster,
|
||||
.mokojoomhero[data-parallax] video.mokojoomhero__video,
|
||||
.mokojoomhero[data-parallax] iframe.mokojoomhero__video {
|
||||
will-change: auto;
|
||||
transform: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Responsive
|
||||
============================================================ */
|
||||
@media (max-width: 768px) {
|
||||
.mokojoomhero {
|
||||
height: var(--mokojoomhero-mobile-height, auto) !important;
|
||||
}
|
||||
|
||||
.mokojoomhero__video,
|
||||
.mokojoomhero__slide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Keep colour/gradient backgrounds visible on mobile */
|
||||
.mokojoomhero__color {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mokojoomhero__overlay {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
/* Only clear overlay background when media is hidden (image/video modes) */
|
||||
.mokojoomhero:not(:has(.mokojoomhero__color)) .mokojoomhero__overlay {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.mokojoomhero__content {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.mokojoomhero__card {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<html><body bgcolor="#FFFFFF"></body></html>
|
||||
@@ -1 +0,0 @@
|
||||
<html><body bgcolor="#FFFFFF"></body></html>
|
||||
@@ -1,298 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: MokoJoomHero.Module.Assets
|
||||
* INGROUP: MokoJoomHero.Module
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
||||
* PATH: /src/packages/mod_mokojoomhero/media/js/mod_mokojoomhero.js
|
||||
* VERSION: 01.08.00
|
||||
* BRIEF: Hero module JavaScript — slideshow crossfade, video viewport control, mute toggle
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Skip slideshow on mobile — video/images are hidden by CSS
|
||||
if (window.matchMedia('(max-width: 768px)').matches) {
|
||||
return;
|
||||
}
|
||||
|
||||
var prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
|
||||
// ── Image slideshow ──
|
||||
document.querySelectorAll('.mokojoomhero[data-slides]').forEach(function (hero) {
|
||||
var slides = hero.querySelectorAll('.mokojoomhero__slide');
|
||||
var interval = parseInt(hero.dataset.interval, 10) || 5000;
|
||||
var transition = hero.dataset.transition || 'crossfade';
|
||||
var current = 0;
|
||||
|
||||
// Per-slide content data
|
||||
var slideContentData = null;
|
||||
var contentEl = hero.querySelector('.mokojoomhero__content');
|
||||
|
||||
if (hero.dataset.slideContent) {
|
||||
try {
|
||||
slideContentData = JSON.parse(hero.dataset.slideContent);
|
||||
} catch (e) {
|
||||
console.warn('MokoJoomHero: Failed to parse slide content data:', e.message);
|
||||
slideContentData = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (slides.length < 2 || prefersReducedMotion) {
|
||||
return;
|
||||
}
|
||||
|
||||
function updateSlideContent(index) {
|
||||
if (!slideContentData || !slideContentData[index] || !contentEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
var data = slideContentData[index];
|
||||
var card = contentEl.querySelector('.mokojoomhero__card');
|
||||
var target = card || contentEl;
|
||||
|
||||
// Clear existing content safely
|
||||
while (target.firstChild) {
|
||||
target.removeChild(target.firstChild);
|
||||
}
|
||||
|
||||
if (data.heading) {
|
||||
var h2 = document.createElement('h2');
|
||||
h2.className = 'mokojoomhero__title';
|
||||
h2.textContent = data.heading;
|
||||
target.appendChild(h2);
|
||||
}
|
||||
|
||||
if (data.body) {
|
||||
var p = document.createElement('p');
|
||||
p.textContent = data.body;
|
||||
target.appendChild(p);
|
||||
}
|
||||
|
||||
if (data.link && data.linkText) {
|
||||
var linkP = document.createElement('p');
|
||||
var a = document.createElement('a');
|
||||
a.href = data.link;
|
||||
a.className = 'btn btn-primary';
|
||||
a.textContent = data.linkText;
|
||||
linkP.appendChild(a);
|
||||
target.appendChild(linkP);
|
||||
}
|
||||
}
|
||||
|
||||
function advanceSlide() {
|
||||
var prev = current;
|
||||
current = (current + 1) % slides.length;
|
||||
|
||||
if (transition === 'slide') {
|
||||
slides[prev].classList.add('mokojoomhero__slide--exit');
|
||||
slides[prev].classList.remove('mokojoomhero__slide--active');
|
||||
slides[prev].setAttribute('aria-hidden', 'true');
|
||||
|
||||
slides[current].classList.add('mokojoomhero__slide--active');
|
||||
slides[current].setAttribute('aria-hidden', 'false');
|
||||
|
||||
// Reset exiting slide after transition completes
|
||||
setTimeout(function () {
|
||||
slides[prev].classList.remove('mokojoomhero__slide--exit');
|
||||
}, 800);
|
||||
|
||||
} else if (transition === 'fade-black') {
|
||||
// Phase 1: fade out current
|
||||
slides[prev].classList.remove('mokojoomhero__slide--active');
|
||||
slides[prev].setAttribute('aria-hidden', 'true');
|
||||
|
||||
// Phase 2: fade in next after a brief black gap
|
||||
setTimeout(function () {
|
||||
slides[current].classList.add('mokojoomhero__slide--active');
|
||||
slides[current].setAttribute('aria-hidden', 'false');
|
||||
}, 600);
|
||||
|
||||
} else {
|
||||
// Crossfade and zoom use the same JS logic
|
||||
slides[prev].classList.remove('mokojoomhero__slide--active');
|
||||
slides[prev].setAttribute('aria-hidden', 'true');
|
||||
|
||||
slides[current].classList.add('mokojoomhero__slide--active');
|
||||
slides[current].setAttribute('aria-hidden', 'false');
|
||||
}
|
||||
|
||||
updateSlideContent(current);
|
||||
}
|
||||
|
||||
// Set initial slide content
|
||||
updateSlideContent(0);
|
||||
|
||||
setInterval(advanceSlide, interval);
|
||||
});
|
||||
|
||||
// ── Pause/resume videos when out of viewport ──
|
||||
if (!('IntersectionObserver' in window)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var observer = new IntersectionObserver(function (entries) {
|
||||
entries.forEach(function (entry) {
|
||||
var hero = entry.target;
|
||||
var video = hero.querySelector('video.mokojoomhero__video');
|
||||
var iframe = hero.querySelector('iframe.mokojoomhero__video');
|
||||
|
||||
if (entry.isIntersecting) {
|
||||
// Resume
|
||||
if (video) {
|
||||
var playPromise = video.play();
|
||||
if (playPromise !== undefined) {
|
||||
playPromise.catch(function () {
|
||||
// Autoplay blocked by browser policy — not actionable
|
||||
});
|
||||
}
|
||||
}
|
||||
if (iframe && iframe.contentWindow) {
|
||||
var src = iframe.src || '';
|
||||
if (src.indexOf('youtube') !== -1) {
|
||||
iframe.contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}', '*');
|
||||
} else if (src.indexOf('vimeo') !== -1) {
|
||||
iframe.contentWindow.postMessage('{"method":"play"}', '*');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Pause
|
||||
if (video) {
|
||||
video.pause();
|
||||
}
|
||||
if (iframe && iframe.contentWindow) {
|
||||
var src = iframe.src || '';
|
||||
if (src.indexOf('youtube') !== -1) {
|
||||
iframe.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}', '*');
|
||||
} else if (src.indexOf('vimeo') !== -1) {
|
||||
iframe.contentWindow.postMessage('{"method":"pause"}', '*');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.1 });
|
||||
|
||||
document.querySelectorAll('.mokojoomhero').forEach(function (hero) {
|
||||
observer.observe(hero);
|
||||
});
|
||||
|
||||
// ── Content entrance animations ──
|
||||
if (!prefersReducedMotion) {
|
||||
var animObserver = new IntersectionObserver(function (entries) {
|
||||
entries.forEach(function (entry) {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('mokojoomhero__content--visible');
|
||||
animObserver.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.2 });
|
||||
|
||||
document.querySelectorAll('.mokojoomhero__content[class*="mokojoomhero__content--anim-"]').forEach(function (el) {
|
||||
animObserver.observe(el);
|
||||
});
|
||||
}
|
||||
|
||||
// ── Parallax scroll ──
|
||||
if (!prefersReducedMotion) {
|
||||
var parallaxHeroes = document.querySelectorAll('.mokojoomhero[data-parallax]');
|
||||
|
||||
if (parallaxHeroes.length) {
|
||||
var onScroll = function () {
|
||||
parallaxHeroes.forEach(function (hero) {
|
||||
var rect = hero.getBoundingClientRect();
|
||||
var speed = parseFloat(hero.dataset.parallax) || 0.5;
|
||||
|
||||
if (rect.bottom > 0 && rect.top < window.innerHeight) {
|
||||
var offset = Math.round(rect.top * speed * -1);
|
||||
var bg = hero.querySelector('.mokojoomhero__slide, .mokojoomhero__color, .mokojoomhero__poster, video.mokojoomhero__video');
|
||||
|
||||
if (bg) {
|
||||
bg.style.transform = 'translateY(' + offset + 'px)';
|
||||
}
|
||||
|
||||
var iframe = hero.querySelector('iframe.mokojoomhero__video');
|
||||
|
||||
if (iframe) {
|
||||
iframe.style.transform = 'translate(-50%, calc(-50% + ' + offset + 'px))';
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', onScroll, { passive: true });
|
||||
onScroll();
|
||||
}
|
||||
}
|
||||
|
||||
// ── Scroll-down indicator ──
|
||||
document.querySelectorAll('.mokojoomhero__scroll-indicator').forEach(function (btn) {
|
||||
var hero = btn.closest('.mokojoomhero');
|
||||
|
||||
if (!hero) {
|
||||
return;
|
||||
}
|
||||
|
||||
btn.addEventListener('click', function () {
|
||||
var nextEl = hero.nextElementSibling || hero.parentElement.nextElementSibling;
|
||||
|
||||
if (nextEl) {
|
||||
nextEl.scrollIntoView({ behavior: prefersReducedMotion ? 'auto' : 'smooth' });
|
||||
}
|
||||
});
|
||||
|
||||
// Hide indicator once hero scrolls out of view
|
||||
var scrollObserver = new IntersectionObserver(function (entries) {
|
||||
entries.forEach(function (entry) {
|
||||
if (!entry.isIntersecting) {
|
||||
btn.classList.add('mokojoomhero__scroll-indicator--hidden');
|
||||
} else {
|
||||
btn.classList.remove('mokojoomhero__scroll-indicator--hidden');
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.1 });
|
||||
|
||||
scrollObserver.observe(hero);
|
||||
});
|
||||
|
||||
// ── Mute/unmute toggle ──
|
||||
document.querySelectorAll('.mokojoomhero__mute-toggle').forEach(function (btn) {
|
||||
var hero = btn.closest('.mokojoomhero');
|
||||
|
||||
if (!hero) {
|
||||
return;
|
||||
}
|
||||
|
||||
var video = hero.querySelector('video.mokojoomhero__video');
|
||||
var iframe = hero.querySelector('iframe.mokojoomhero__video');
|
||||
var icon = btn.querySelector('.mokojoomhero__mute-icon');
|
||||
|
||||
btn.addEventListener('click', function () {
|
||||
var muted = btn.getAttribute('data-muted') === 'true';
|
||||
|
||||
if (video) {
|
||||
video.muted = !muted;
|
||||
}
|
||||
if (iframe && iframe.contentWindow) {
|
||||
var src = iframe.src || '';
|
||||
if (src.indexOf('youtube') !== -1) {
|
||||
var func = muted ? 'unMute' : 'mute';
|
||||
iframe.contentWindow.postMessage('{"event":"command","func":"' + func + '","args":""}', '*');
|
||||
} else if (src.indexOf('vimeo') !== -1) {
|
||||
var vol = muted ? 1 : 0;
|
||||
iframe.contentWindow.postMessage('{"method":"setVolume","value":' + vol + '}', '*');
|
||||
}
|
||||
}
|
||||
|
||||
btn.setAttribute('data-muted', muted ? 'false' : 'true');
|
||||
btn.setAttribute('aria-label', muted ? 'Mute video' : 'Unmute video');
|
||||
|
||||
if (icon) {
|
||||
icon.textContent = muted ? '\u{1F50A}' : '\u{1F507}';
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,242 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Site
|
||||
* @subpackage mod_mokojoomhero
|
||||
*
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GPL-3.0-or-later
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Helper\ModuleHelper;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
|
||||
/** @var \Joomla\CMS\Application\SiteApplication $app */
|
||||
/** @var \stdClass $module */
|
||||
/** @var \Joomla\Registry\Registry $params */
|
||||
|
||||
// Load module assets via Web Asset Manager (registry in media/mod_mokojoomhero/)
|
||||
$wa = $app->getDocument()->getWebAssetManager();
|
||||
$wa->getRegistry()->addExtensionRegistryFile('mod_mokojoomhero');
|
||||
$wa->usePreset('mod_mokojoomhero');
|
||||
|
||||
// Module parameters
|
||||
$heroMode = $params->get('heroMode', 'images');
|
||||
$imageFolder = $params->get('imageFolder', 'images/heroes');
|
||||
$imageCount = (int) $params->get('imageCount', 5);
|
||||
$slideInterval = (int) $params->get('slideInterval', 5000);
|
||||
$fadeType = $params->get('fadeType', 'crossfade');
|
||||
$videoFile = $params->get('videoFile', '');
|
||||
$heroHeight = $params->get('heroHeight', '60vh');
|
||||
$heroHeightMobile = $params->get('heroHeightMobile', '');
|
||||
$overlayColor = $params->get('overlayColor', '#000000');
|
||||
$overlayType = $params->get('overlayType', 'solid');
|
||||
$overlayOpacity = (float) $params->get('overlayOpacity', 0.5);
|
||||
$textAlign = $params->get('textAlign', 'center');
|
||||
$verticalAlign = $params->get('verticalAlign', 'center');
|
||||
$textColor = $params->get('textColor', '#ffffff');
|
||||
$contentSource = $params->get('contentSource', 'manual');
|
||||
$articleId = (int) $params->get('articleId', 0);
|
||||
$useArticleTitle = (bool) $params->get('useArticleTitle', 0);
|
||||
$heroContent = $params->get('heroContent', '');
|
||||
$slideContent = $params->get('slideContent', '');
|
||||
$showCard = (bool) $params->get('showCard', 1);
|
||||
$cardDelay = (int) $params->get('cardDelay', 0);
|
||||
$contentAnimation = $params->get('contentAnimation', 'none');
|
||||
$contentAnimationDelay = (int) $params->get('contentAnimationDelay', 0);
|
||||
$parallaxEnabled = (bool) $params->get('parallaxEnabled', 0);
|
||||
$parallaxSpeed = (float) $params->get('parallaxSpeed', 0.5);
|
||||
$showMuteToggle = (bool) $params->get('showMuteToggle', 0);
|
||||
$videoPoster = $params->get('videoPoster', '');
|
||||
$showScrollIndicator = (bool) $params->get('showScrollIndicator', 0);
|
||||
$localVideoFile = $params->get('localVideoFile', '');
|
||||
$bgColor = $params->get('bgColor', '#003366');
|
||||
$gradientStart = $params->get('gradientStart', '#003366');
|
||||
$gradientEnd = $params->get('gradientEnd', '#006699');
|
||||
$gradientAngle = (int) $params->get('gradientAngle', 135);
|
||||
|
||||
// Validate CSS height values to prevent injection
|
||||
if (!preg_match('/^\d+(\.\d+)?(px|vh|vw|em|rem|%)$/', $heroHeight)) {
|
||||
$heroHeight = '60vh';
|
||||
}
|
||||
|
||||
if ($heroHeightMobile && !preg_match('/^\d+(\.\d+)?(px|vh|vw|em|rem|%)$/', $heroHeightMobile)) {
|
||||
$heroHeightMobile = '';
|
||||
}
|
||||
|
||||
// Validate hex colour values
|
||||
$hexColorPattern = '/^#[0-9a-fA-F]{6}$/';
|
||||
|
||||
if (!preg_match($hexColorPattern, $overlayColor)) {
|
||||
$overlayColor = '#000000';
|
||||
}
|
||||
|
||||
if (!preg_match($hexColorPattern, $textColor)) {
|
||||
$textColor = '#ffffff';
|
||||
}
|
||||
|
||||
if (!preg_match($hexColorPattern, $bgColor)) {
|
||||
$bgColor = '#003366';
|
||||
}
|
||||
|
||||
if (!preg_match($hexColorPattern, $gradientStart)) {
|
||||
$gradientStart = '#003366';
|
||||
}
|
||||
|
||||
if (!preg_match($hexColorPattern, $gradientEnd)) {
|
||||
$gradientEnd = '#006699';
|
||||
}
|
||||
|
||||
// Validate allowlist values
|
||||
$allowedTextAlign = ['left', 'center', 'right'];
|
||||
|
||||
if (!in_array($textAlign, $allowedTextAlign, true)) {
|
||||
$textAlign = 'center';
|
||||
}
|
||||
|
||||
$allowedFadeTypes = ['crossfade', 'slide', 'fade-black', 'zoom'];
|
||||
|
||||
if (!in_array($fadeType, $allowedFadeTypes, true)) {
|
||||
$fadeType = 'crossfade';
|
||||
}
|
||||
|
||||
$allowedOverlayTypes = ['solid', 'gradient-bottom', 'gradient-top', 'gradient-left', 'gradient-right'];
|
||||
|
||||
if (!in_array($overlayType, $allowedOverlayTypes, true)) {
|
||||
$overlayType = 'solid';
|
||||
}
|
||||
|
||||
$allowedContentAnimations = ['none', 'fade-in', 'slide-up', 'slide-left', 'slide-right'];
|
||||
|
||||
if (!in_array($contentAnimation, $allowedContentAnimations, true)) {
|
||||
$contentAnimation = 'none';
|
||||
}
|
||||
|
||||
$parallaxSpeed = max(0.1, min(0.9, $parallaxSpeed));
|
||||
$gradientAngle = max(0, min(360, $gradientAngle));
|
||||
|
||||
// Collect hero images
|
||||
$heroImages = [];
|
||||
|
||||
if ($heroMode === 'images') {
|
||||
$folderPath = JPATH_ROOT . '/' . ltrim($imageFolder, '/');
|
||||
|
||||
if (is_dir($folderPath)) {
|
||||
try {
|
||||
$allowed = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif', 'svg'];
|
||||
$all = [];
|
||||
|
||||
foreach (new DirectoryIterator($folderPath) as $file) {
|
||||
if ($file->isFile() && in_array(strtolower($file->getExtension()), $allowed, true)) {
|
||||
$all[] = $file->getFilename();
|
||||
}
|
||||
}
|
||||
|
||||
if ($all) {
|
||||
shuffle($all);
|
||||
$picked = array_slice($all, 0, min($imageCount, 5));
|
||||
|
||||
foreach ($picked as $filename) {
|
||||
$heroImages[] = Uri::root() . $imageFolder . '/' . $filename;
|
||||
}
|
||||
}
|
||||
} catch (\UnexpectedValueException $e) {
|
||||
\Joomla\CMS\Log\Log::add(
|
||||
'MokoJoomHero: Cannot read image folder "' . $folderPath . '": ' . $e->getMessage(),
|
||||
\Joomla\CMS\Log\Log::WARNING,
|
||||
'mod_mokojoomhero'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build video URL — smartly detect YouTube, Vimeo, or local/direct file
|
||||
$videoUrl = '';
|
||||
$youtubeId = '';
|
||||
$vimeoId = '';
|
||||
|
||||
if ($heroMode === 'localvideo' && $localVideoFile) {
|
||||
$videoUrl = Uri::root() . ltrim($localVideoFile, '/');
|
||||
} elseif ($heroMode === 'video' && $videoFile) {
|
||||
// YouTube: watch, embed, shorts, youtu.be, with optional timestamps/params
|
||||
if (preg_match('/(?:youtube\.com\/(?:watch\?.*v=|embed\/|shorts\/|v\/)|youtu\.be\/)([\w-]{11})/', $videoFile, $m)) {
|
||||
$youtubeId = $m[1];
|
||||
// Vimeo: vimeo.com/123456 or player.vimeo.com/video/123456
|
||||
} elseif (preg_match('/vimeo\.com\/(?:video\/)?(\d+)/', $videoFile, $m)) {
|
||||
$vimeoId = $m[1];
|
||||
} else {
|
||||
// Direct URL or local file path
|
||||
$videoUrl = (strpos($videoFile, '://') !== false)
|
||||
? $videoFile
|
||||
: Uri::root() . ltrim($videoFile, '/');
|
||||
}
|
||||
}
|
||||
|
||||
// Load content from article if configured
|
||||
$articleTitle = '';
|
||||
|
||||
if ($contentSource === 'article' && $articleId > 0) {
|
||||
try {
|
||||
$db = \Joomla\CMS\Factory::getDbo();
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->quoteName(['title', 'introtext', 'fulltext']))
|
||||
->from($db->quoteName('#__content'))
|
||||
->where($db->quoteName('id') . ' = ' . $articleId)
|
||||
->where($db->quoteName('state') . ' = 1');
|
||||
$db->setQuery($query);
|
||||
$article = $db->loadObject();
|
||||
|
||||
if ($article) {
|
||||
$rawContent = $article->introtext ?: $article->fulltext;
|
||||
$heroContent = \Joomla\CMS\HTML\HTMLHelper::_('content.prepare', $rawContent);
|
||||
$articleTitle = $article->title;
|
||||
}
|
||||
} catch (\RuntimeException $e) {
|
||||
\Joomla\CMS\Log\Log::add(
|
||||
'MokoJoomHero: Failed to load article ID ' . $articleId . ': ' . $e->getMessage(),
|
||||
\Joomla\CMS\Log\Log::WARNING,
|
||||
'mod_mokojoomhero'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Process per-slide content — overrides folder-based images when populated
|
||||
$slides = [];
|
||||
|
||||
if ($heroMode === 'images' && !empty($slideContent)) {
|
||||
$slideData = is_string($slideContent) ? json_decode($slideContent, true) : (array) $slideContent;
|
||||
|
||||
if ($slideData === null && json_last_error() !== JSON_ERROR_NONE) {
|
||||
\Joomla\CMS\Log\Log::add(
|
||||
'MokoJoomHero: Failed to decode slideContent JSON: ' . json_last_error_msg(),
|
||||
\Joomla\CMS\Log\Log::WARNING,
|
||||
'mod_mokojoomhero'
|
||||
);
|
||||
}
|
||||
|
||||
if (is_array($slideData)) {
|
||||
foreach ($slideData as $item) {
|
||||
$item = (array) $item;
|
||||
|
||||
if (!empty($item['image'])) {
|
||||
$slides[] = [
|
||||
'image' => Uri::root() . ltrim($item['image'], '/'),
|
||||
'heading' => $item['heading'] ?? '',
|
||||
'body' => $item['body'] ?? '',
|
||||
'link' => $item['link'] ?? '',
|
||||
'linkText' => $item['linkText'] ?? 'Learn More',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Per-slide content overrides folder-based random images
|
||||
if ($slides) {
|
||||
$heroImages = array_column($slides, 'image');
|
||||
}
|
||||
}
|
||||
|
||||
require ModuleHelper::getLayoutPath('mod_mokojoomhero', $params->get('layout', 'default'));
|
||||
@@ -1,437 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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
|
||||
|
||||
FILE INFORMATION
|
||||
DEFGROUP: MokoJoomHero.Module
|
||||
INGROUP: MokoJoomHero
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
||||
PATH: /src/packages/mod_mokojoomhero/mod_mokojoomhero.xml
|
||||
VERSION: 01.00.20
|
||||
BRIEF: Joomla module manifest — random hero image with content overlay
|
||||
-->
|
||||
<extension type="module" client="site" method="upgrade">
|
||||
<name>Module - MokoJoomHero</name>
|
||||
<creationDate>2026-05</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<version>01.08.00-rc</version>
|
||||
<description>Displays a random hero image slideshow or background video with content overlaid. Designed for MokoOnyx template. By Moko Consulting.</description>
|
||||
|
||||
<scriptfile>script.php</scriptfile>
|
||||
|
||||
<files>
|
||||
<filename module="mod_mokojoomhero">mod_mokojoomhero.php</filename>
|
||||
<filename>mod_mokojoomhero.xml</filename>
|
||||
<filename>script.php</filename>
|
||||
<folder>tmpl</folder>
|
||||
<folder>language</folder>
|
||||
</files>
|
||||
|
||||
<media destination="mod_mokojoomhero" folder="media">
|
||||
<filename>joomla.asset.json</filename>
|
||||
<folder>css</folder>
|
||||
<folder>js</folder>
|
||||
</media>
|
||||
|
||||
<languages folder="language">
|
||||
<language tag="en-GB">en-GB/mod_mokojoomhero.ini</language>
|
||||
<language tag="en-GB">en-GB/mod_mokojoomhero.sys.ini</language>
|
||||
<language tag="en-US">en-US/mod_mokojoomhero.ini</language>
|
||||
<language tag="en-US">en-US/mod_mokojoomhero.sys.ini</language>
|
||||
</languages>
|
||||
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="basic">
|
||||
<field
|
||||
name="heroMode"
|
||||
type="list"
|
||||
label="MOD_MOKOJOOMHERO_MODE_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_MODE_DESC"
|
||||
default="images"
|
||||
>
|
||||
<option value="images">MOD_MOKOJOOMHERO_MODE_IMAGES</option>
|
||||
<option value="video">MOD_MOKOJOOMHERO_MODE_VIDEO</option>
|
||||
<option value="localvideo">MOD_MOKOJOOMHERO_MODE_LOCALVIDEO</option>
|
||||
<option value="color">MOD_MOKOJOOMHERO_MODE_COLOR</option>
|
||||
<option value="gradient">MOD_MOKOJOOMHERO_MODE_GRADIENT</option>
|
||||
</field>
|
||||
<field
|
||||
name="bgColor"
|
||||
type="color"
|
||||
label="MOD_MOKOJOOMHERO_BG_COLOR_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_BG_COLOR_DESC"
|
||||
default="#003366"
|
||||
showon="heroMode:color"
|
||||
/>
|
||||
<field
|
||||
name="gradientStart"
|
||||
type="color"
|
||||
label="MOD_MOKOJOOMHERO_GRADIENT_START_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_GRADIENT_START_DESC"
|
||||
default="#003366"
|
||||
showon="heroMode:gradient"
|
||||
/>
|
||||
<field
|
||||
name="gradientEnd"
|
||||
type="color"
|
||||
label="MOD_MOKOJOOMHERO_GRADIENT_END_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_GRADIENT_END_DESC"
|
||||
default="#006699"
|
||||
showon="heroMode:gradient"
|
||||
/>
|
||||
<field
|
||||
name="gradientAngle"
|
||||
type="number"
|
||||
label="MOD_MOKOJOOMHERO_GRADIENT_ANGLE_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_GRADIENT_ANGLE_DESC"
|
||||
default="135"
|
||||
min="0"
|
||||
max="360"
|
||||
step="15"
|
||||
showon="heroMode:gradient"
|
||||
/>
|
||||
<field
|
||||
name="fadeType"
|
||||
type="list"
|
||||
label="MOD_MOKOJOOMHERO_FADE_TYPE_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_FADE_TYPE_DESC"
|
||||
default="crossfade"
|
||||
showon="heroMode:images"
|
||||
>
|
||||
<option value="crossfade">MOD_MOKOJOOMHERO_FADE_CROSSFADE</option>
|
||||
<option value="slide">MOD_MOKOJOOMHERO_FADE_SLIDE</option>
|
||||
<option value="fade-black">MOD_MOKOJOOMHERO_FADE_BLACK</option>
|
||||
<option value="zoom">MOD_MOKOJOOMHERO_FADE_ZOOM</option>
|
||||
</field>
|
||||
<field
|
||||
name="imageFolder"
|
||||
type="text"
|
||||
label="MOD_MOKOJOOMHERO_IMAGE_FOLDER_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_IMAGE_FOLDER_DESC"
|
||||
default="images/heroes"
|
||||
filter="path"
|
||||
showon="heroMode:images"
|
||||
/>
|
||||
<field
|
||||
name="imageCount"
|
||||
type="number"
|
||||
label="MOD_MOKOJOOMHERO_IMAGE_COUNT_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_IMAGE_COUNT_DESC"
|
||||
default="5"
|
||||
min="1"
|
||||
max="5"
|
||||
showon="heroMode:images"
|
||||
/>
|
||||
<field
|
||||
name="slideInterval"
|
||||
type="number"
|
||||
label="MOD_MOKOJOOMHERO_SLIDE_INTERVAL_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_SLIDE_INTERVAL_DESC"
|
||||
default="5000"
|
||||
min="1000"
|
||||
step="500"
|
||||
showon="heroMode:images"
|
||||
/>
|
||||
<field
|
||||
name="slideContent"
|
||||
type="subform"
|
||||
label="MOD_MOKOJOOMHERO_SLIDE_CONTENT_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_SLIDE_CONTENT_DESC"
|
||||
multiple="true"
|
||||
layout="joomla.form.field.subform.repeatable-table"
|
||||
showon="heroMode:images"
|
||||
max="5"
|
||||
>
|
||||
<form>
|
||||
<field
|
||||
name="image"
|
||||
type="media"
|
||||
label="MOD_MOKOJOOMHERO_SLIDE_IMAGE_LABEL"
|
||||
types="images"
|
||||
/>
|
||||
<field
|
||||
name="heading"
|
||||
type="text"
|
||||
label="MOD_MOKOJOOMHERO_SLIDE_HEADING_LABEL"
|
||||
filter="string"
|
||||
/>
|
||||
<field
|
||||
name="body"
|
||||
type="textarea"
|
||||
label="MOD_MOKOJOOMHERO_SLIDE_BODY_LABEL"
|
||||
filter="safehtml"
|
||||
rows="3"
|
||||
/>
|
||||
<field
|
||||
name="link"
|
||||
type="url"
|
||||
label="MOD_MOKOJOOMHERO_SLIDE_LINK_LABEL"
|
||||
filter="url"
|
||||
/>
|
||||
<field
|
||||
name="linkText"
|
||||
type="text"
|
||||
label="MOD_MOKOJOOMHERO_SLIDE_LINK_TEXT_LABEL"
|
||||
filter="string"
|
||||
default="Learn More"
|
||||
/>
|
||||
</form>
|
||||
</field>
|
||||
<field
|
||||
name="videoFile"
|
||||
type="text"
|
||||
label="MOD_MOKOJOOMHERO_VIDEO_FILE_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_VIDEO_FILE_DESC"
|
||||
filter="string"
|
||||
showon="heroMode:video"
|
||||
/>
|
||||
<field
|
||||
name="localVideoFile"
|
||||
type="media"
|
||||
label="MOD_MOKOJOOMHERO_LOCAL_VIDEO_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_LOCAL_VIDEO_DESC"
|
||||
types="videos"
|
||||
showon="heroMode:localvideo"
|
||||
/>
|
||||
<field
|
||||
name="heroHeight"
|
||||
type="text"
|
||||
label="MOD_MOKOJOOMHERO_HERO_HEIGHT_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_HERO_HEIGHT_DESC"
|
||||
hint="MOD_MOKOJOOMHERO_HERO_HEIGHT_HINT"
|
||||
default="60vh"
|
||||
filter="string"
|
||||
/>
|
||||
<field
|
||||
name="heroHeightMobile"
|
||||
type="text"
|
||||
label="MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_DESC"
|
||||
hint="MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_HINT"
|
||||
default=""
|
||||
filter="string"
|
||||
/>
|
||||
<field
|
||||
name="videoPoster"
|
||||
type="media"
|
||||
label="MOD_MOKOJOOMHERO_VIDEO_POSTER_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_VIDEO_POSTER_DESC"
|
||||
types="images"
|
||||
showon="heroMode:video,localvideo"
|
||||
/>
|
||||
<field
|
||||
name="showScrollIndicator"
|
||||
type="radio"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
label="MOD_MOKOJOOMHERO_SCROLL_INDICATOR_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_SCROLL_INDICATOR_DESC"
|
||||
default="0"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
<field
|
||||
name="parallaxEnabled"
|
||||
type="radio"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
label="MOD_MOKOJOOMHERO_PARALLAX_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_PARALLAX_DESC"
|
||||
default="0"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
<field
|
||||
name="parallaxSpeed"
|
||||
type="range"
|
||||
label="MOD_MOKOJOOMHERO_PARALLAX_SPEED_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_PARALLAX_SPEED_DESC"
|
||||
default="0.5"
|
||||
min="0.1"
|
||||
max="0.9"
|
||||
step="0.1"
|
||||
showon="parallaxEnabled:1"
|
||||
/>
|
||||
<field
|
||||
name="showMuteToggle"
|
||||
type="radio"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
label="MOD_MOKOJOOMHERO_MUTE_TOGGLE_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_MUTE_TOGGLE_DESC"
|
||||
default="0"
|
||||
showon="heroMode:video,localvideo"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
<fieldset name="content"
|
||||
label="MOD_MOKOJOOMHERO_FIELDSET_CONTENT"
|
||||
>
|
||||
<field
|
||||
name="contentSource"
|
||||
type="list"
|
||||
label="MOD_MOKOJOOMHERO_CONTENT_SOURCE_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_CONTENT_SOURCE_DESC"
|
||||
default="manual"
|
||||
>
|
||||
<option value="manual">MOD_MOKOJOOMHERO_SOURCE_MANUAL</option>
|
||||
<option value="article">MOD_MOKOJOOMHERO_SOURCE_ARTICLE</option>
|
||||
</field>
|
||||
<field
|
||||
name="heroContent"
|
||||
type="editor"
|
||||
label="MOD_MOKOJOOMHERO_CONTENT_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_CONTENT_DESC"
|
||||
filter="safehtml"
|
||||
buttons="true"
|
||||
hide="readmore,pagebreak"
|
||||
showon="contentSource:manual"
|
||||
/>
|
||||
<field
|
||||
name="articleId"
|
||||
type="sql"
|
||||
label="MOD_MOKOJOOMHERO_ARTICLE_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_ARTICLE_DESC"
|
||||
query="SELECT id, title FROM #__content WHERE state = 1 ORDER BY title ASC"
|
||||
key_field="id"
|
||||
value_field="title"
|
||||
header="MOD_MOKOJOOMHERO_ARTICLE_SELECT"
|
||||
showon="contentSource:article"
|
||||
/>
|
||||
<field
|
||||
name="useArticleTitle"
|
||||
type="radio"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
label="MOD_MOKOJOOMHERO_USE_ARTICLE_TITLE_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_USE_ARTICLE_TITLE_DESC"
|
||||
default="0"
|
||||
showon="contentSource:article"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
<field
|
||||
name="showCard"
|
||||
type="radio"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
label="MOD_MOKOJOOMHERO_SHOW_CARD_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_SHOW_CARD_DESC"
|
||||
default="1"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
<field
|
||||
name="contentAnimation"
|
||||
type="list"
|
||||
label="MOD_MOKOJOOMHERO_CONTENT_ANIM_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_CONTENT_ANIM_DESC"
|
||||
default="none"
|
||||
>
|
||||
<option value="none">MOD_MOKOJOOMHERO_ANIM_NONE</option>
|
||||
<option value="fade-in">MOD_MOKOJOOMHERO_ANIM_FADE_IN</option>
|
||||
<option value="slide-up">MOD_MOKOJOOMHERO_ANIM_SLIDE_UP</option>
|
||||
<option value="slide-left">MOD_MOKOJOOMHERO_ANIM_SLIDE_LEFT</option>
|
||||
<option value="slide-right">MOD_MOKOJOOMHERO_ANIM_SLIDE_RIGHT</option>
|
||||
</field>
|
||||
<field
|
||||
name="contentAnimationDelay"
|
||||
type="number"
|
||||
label="MOD_MOKOJOOMHERO_CONTENT_ANIM_DELAY_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_CONTENT_ANIM_DELAY_DESC"
|
||||
default="0"
|
||||
min="0"
|
||||
max="3000"
|
||||
step="100"
|
||||
showon="contentAnimation!:none"
|
||||
/>
|
||||
<field
|
||||
name="cardDelay"
|
||||
type="number"
|
||||
label="MOD_MOKOJOOMHERO_CARD_DELAY_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_CARD_DELAY_DESC"
|
||||
default="0"
|
||||
min="0"
|
||||
max="5000"
|
||||
step="250"
|
||||
showon="showCard:1"
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset name="advanced"
|
||||
label="MOD_MOKOJOOMHERO_FIELDSET_OVERLAY"
|
||||
>
|
||||
<field
|
||||
name="overlayColor"
|
||||
type="color"
|
||||
label="MOD_MOKOJOOMHERO_OVERLAY_COLOR_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_OVERLAY_COLOR_DESC"
|
||||
default="#000000"
|
||||
/>
|
||||
<field
|
||||
name="overlayType"
|
||||
type="list"
|
||||
label="MOD_MOKOJOOMHERO_OVERLAY_TYPE_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_OVERLAY_TYPE_DESC"
|
||||
default="solid"
|
||||
>
|
||||
<option value="solid">MOD_MOKOJOOMHERO_OVERLAY_SOLID</option>
|
||||
<option value="gradient-bottom">MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_BOTTOM</option>
|
||||
<option value="gradient-top">MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_TOP</option>
|
||||
<option value="gradient-left">MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_LEFT</option>
|
||||
<option value="gradient-right">MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_RIGHT</option>
|
||||
</field>
|
||||
<field
|
||||
name="overlayOpacity"
|
||||
type="range"
|
||||
label="MOD_MOKOJOOMHERO_OVERLAY_OPACITY_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_OVERLAY_OPACITY_DESC"
|
||||
default="0.5"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.1"
|
||||
/>
|
||||
<field
|
||||
name="textAlign"
|
||||
type="list"
|
||||
label="MOD_MOKOJOOMHERO_TEXT_ALIGN_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_TEXT_ALIGN_DESC"
|
||||
default="center"
|
||||
>
|
||||
<option value="left">MOD_MOKOJOOMHERO_ALIGN_LEFT</option>
|
||||
<option value="center">MOD_MOKOJOOMHERO_ALIGN_CENTER</option>
|
||||
<option value="right">MOD_MOKOJOOMHERO_ALIGN_RIGHT</option>
|
||||
</field>
|
||||
<field
|
||||
name="verticalAlign"
|
||||
type="list"
|
||||
label="MOD_MOKOJOOMHERO_VALIGN_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_VALIGN_DESC"
|
||||
default="center"
|
||||
>
|
||||
<option value="top">MOD_MOKOJOOMHERO_VALIGN_TOP</option>
|
||||
<option value="center">MOD_MOKOJOOMHERO_VALIGN_CENTER</option>
|
||||
<option value="bottom">MOD_MOKOJOOMHERO_VALIGN_BOTTOM</option>
|
||||
</field>
|
||||
<field
|
||||
name="textColor"
|
||||
type="color"
|
||||
label="MOD_MOKOJOOMHERO_TEXT_COLOR_LABEL"
|
||||
description="MOD_MOKOJOOMHERO_TEXT_COLOR_DESC"
|
||||
default="#ffffff"
|
||||
/>
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
|
||||
</extension>
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Site
|
||||
* @subpackage mod_mokojoomhero
|
||||
*
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GPL-3.0-or-later
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
/** @var string $heroMode */
|
||||
/** @var array $heroImages */
|
||||
/** @var int $slideInterval */
|
||||
/** @var string $fadeType */
|
||||
/** @var string $videoUrl */
|
||||
/** @var string $youtubeId */
|
||||
/** @var string $vimeoId */
|
||||
/** @var string $heroHeight */
|
||||
/** @var string $heroHeightMobile */
|
||||
/** @var string $overlayColor */
|
||||
/** @var string $overlayType */
|
||||
/** @var float $overlayOpacity */
|
||||
/** @var string $textAlign */
|
||||
/** @var string $verticalAlign */
|
||||
/** @var string $textColor */
|
||||
/** @var string $contentSource */
|
||||
/** @var int $articleId */
|
||||
/** @var bool $useArticleTitle */
|
||||
/** @var string $heroContent */
|
||||
/** @var string $articleTitle */
|
||||
/** @var array $slides */
|
||||
/** @var bool $showCard */
|
||||
/** @var int $cardDelay */
|
||||
/** @var string $contentAnimation */
|
||||
/** @var int $contentAnimationDelay */
|
||||
/** @var bool $parallaxEnabled */
|
||||
/** @var float $parallaxSpeed */
|
||||
/** @var bool $showMuteToggle */
|
||||
/** @var string $videoPoster */
|
||||
/** @var bool $showScrollIndicator */
|
||||
/** @var string $bgColor */
|
||||
/** @var string $gradientStart */
|
||||
/** @var string $gradientEnd */
|
||||
/** @var int $gradientAngle */
|
||||
$moduleId = 'mod-mokojoomhero-' . $module->id;
|
||||
|
||||
// Convert hex overlay colour to rgba
|
||||
$r = hexdec(substr($overlayColor, 1, 2));
|
||||
$g = hexdec(substr($overlayColor, 3, 2));
|
||||
$b = hexdec(substr($overlayColor, 5, 2));
|
||||
$rgbaOpaque = "rgba($r, $g, $b, $overlayOpacity)";
|
||||
$rgbaTransparent = "rgba($r, $g, $b, 0)";
|
||||
|
||||
// Build overlay background based on type
|
||||
$overlayDirections = [
|
||||
'gradient-bottom' => 'to bottom',
|
||||
'gradient-top' => 'to top',
|
||||
'gradient-left' => 'to left',
|
||||
'gradient-right' => 'to right',
|
||||
];
|
||||
|
||||
if ($overlayType !== 'solid' && isset($overlayDirections[$overlayType])) {
|
||||
$dir = $overlayDirections[$overlayType];
|
||||
$overlayBg = "background: linear-gradient($dir, $rgbaTransparent, $rgbaOpaque);";
|
||||
} else {
|
||||
$overlayBg = "background-color: $rgbaOpaque;";
|
||||
}
|
||||
|
||||
// Map vertical alignment to CSS align-items
|
||||
$valignMap = ['top' => 'flex-start', 'center' => 'center', 'bottom' => 'flex-end'];
|
||||
$valignCss = $valignMap[$verticalAlign] ?? 'center';
|
||||
|
||||
$heightAttr = htmlspecialchars($heroHeight, ENT_QUOTES, 'UTF-8');
|
||||
?>
|
||||
<?php if ($heroHeightMobile) : ?>
|
||||
<style>#<?php echo $moduleId; ?> { --mokojoomhero-mobile-height: <?php echo htmlspecialchars($heroHeightMobile, ENT_QUOTES, 'UTF-8'); ?>; }</style>
|
||||
<?php endif; ?>
|
||||
<div id="<?php echo $moduleId; ?>" class="mokojoomhero" style="height: <?php echo $heightAttr; ?>;"
|
||||
<?php if ($parallaxEnabled) : ?>
|
||||
data-parallax="<?php echo $parallaxSpeed; ?>"
|
||||
<?php endif; ?>
|
||||
<?php if ($heroMode === 'images' && count($heroImages) > 1) : ?>
|
||||
data-slides="<?php echo htmlspecialchars(json_encode($heroImages), ENT_QUOTES, 'UTF-8'); ?>"
|
||||
data-interval="<?php echo $slideInterval; ?>"
|
||||
data-transition="<?php echo htmlspecialchars($fadeType, ENT_QUOTES, 'UTF-8'); ?>"
|
||||
<?php if ($slides) : ?>
|
||||
data-slide-content="<?php echo htmlspecialchars(json_encode($slides), ENT_QUOTES, 'UTF-8'); ?>"
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
>
|
||||
<?php // Background layer — solid colour, single image, slideshow, or video ?>
|
||||
<?php if ($heroMode === 'color') : ?>
|
||||
<div class="mokojoomhero__color" style="background-color: <?php echo htmlspecialchars($bgColor, ENT_QUOTES, 'UTF-8'); ?>;"></div>
|
||||
<?php elseif ($heroMode === 'gradient') : ?>
|
||||
<div class="mokojoomhero__color" style="background: linear-gradient(<?php echo $gradientAngle; ?>deg, <?php echo htmlspecialchars($gradientStart, ENT_QUOTES, 'UTF-8'); ?>, <?php echo htmlspecialchars($gradientEnd, ENT_QUOTES, 'UTF-8'); ?>);"></div>
|
||||
<?php elseif ($heroMode === 'video' && $youtubeId) : ?>
|
||||
<?php if ($videoPoster) : ?>
|
||||
<div class="mokojoomhero__poster" style="background-image: url('<?php echo htmlspecialchars(\Joomla\CMS\Uri\Uri::root() . ltrim($videoPoster, '/'), ENT_QUOTES, 'UTF-8'); ?>');"></div>
|
||||
<?php endif; ?>
|
||||
<iframe class="mokojoomhero__video" src="https://www.youtube-nocookie.com/embed/<?php echo htmlspecialchars($youtubeId, ENT_QUOTES, 'UTF-8'); ?>?autoplay=1&mute=1&loop=1&playlist=<?php echo htmlspecialchars($youtubeId, ENT_QUOTES, 'UTF-8'); ?>&controls=0&showinfo=0&rel=0&modestbranding=1&playsinline=1&enablejsapi=1&origin=<?php echo htmlspecialchars(\Joomla\CMS\Uri\Uri::root(), ENT_QUOTES, 'UTF-8'); ?>" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
<?php elseif ($heroMode === 'video' && $vimeoId) : ?>
|
||||
<?php if ($videoPoster) : ?>
|
||||
<div class="mokojoomhero__poster" style="background-image: url('<?php echo htmlspecialchars(\Joomla\CMS\Uri\Uri::root() . ltrim($videoPoster, '/'), ENT_QUOTES, 'UTF-8'); ?>');"></div>
|
||||
<?php endif; ?>
|
||||
<iframe class="mokojoomhero__video" src="https://player.vimeo.com/video/<?php echo htmlspecialchars($vimeoId, ENT_QUOTES, 'UTF-8'); ?>?autoplay=1&muted=1&loop=1&background=1" allow="autoplay" allowfullscreen></iframe>
|
||||
<?php elseif (($heroMode === 'video' || $heroMode === 'localvideo') && $videoUrl) : ?>
|
||||
<?php if ($videoPoster) : ?>
|
||||
<div class="mokojoomhero__poster" style="background-image: url('<?php echo htmlspecialchars(\Joomla\CMS\Uri\Uri::root() . ltrim($videoPoster, '/'), ENT_QUOTES, 'UTF-8'); ?>');"></div>
|
||||
<?php endif; ?>
|
||||
<video class="mokojoomhero__video" autoplay muted loop playsinline<?php if ($videoPoster) : ?> poster="<?php echo htmlspecialchars(\Joomla\CMS\Uri\Uri::root() . ltrim($videoPoster, '/'), ENT_QUOTES, 'UTF-8'); ?>"<?php endif; ?>>
|
||||
<source src="<?php echo htmlspecialchars($videoUrl, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
</video>
|
||||
<?php elseif ($heroImages) : ?>
|
||||
<?php foreach ($heroImages as $i => $img) : ?>
|
||||
<div class="mokojoomhero__slide<?php echo $i === 0 ? ' mokojoomhero__slide--active' : ''; ?>"
|
||||
style="background-image: url('<?php echo htmlspecialchars($img, ENT_QUOTES, 'UTF-8'); ?>');"
|
||||
aria-hidden="<?php echo $i === 0 ? 'false' : 'true'; ?>">
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (($heroMode === 'video' || $heroMode === 'localvideo') && $showMuteToggle) : ?>
|
||||
<button class="mokojoomhero__mute-toggle" type="button" aria-label="Unmute video" data-muted="true">
|
||||
<span class="mokojoomhero__mute-icon" aria-hidden="true">🔇</span>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($showScrollIndicator) : ?>
|
||||
<button class="mokojoomhero__scroll-indicator" type="button" aria-label="Scroll down">
|
||||
<svg class="mokojoomhero__scroll-chevron" viewBox="0 0 24 24" width="32" height="32" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php // Overlay + content ?>
|
||||
<div class="mokojoomhero__overlay" style="<?php echo $overlayBg; ?> align-items: <?php echo $valignCss; ?>;">
|
||||
<div class="mokojoomhero__content<?php if ($contentAnimation !== 'none') : ?> mokojoomhero__content--anim-<?php echo htmlspecialchars($contentAnimation, ENT_QUOTES, 'UTF-8'); ?><?php endif; ?>" style="text-align: <?php echo htmlspecialchars($textAlign, ENT_QUOTES, 'UTF-8'); ?>; color: <?php echo htmlspecialchars($textColor, ENT_QUOTES, 'UTF-8'); ?>;<?php if ($contentAnimationDelay) : ?> animation-delay: <?php echo $contentAnimationDelay; ?>ms;<?php endif; ?>">
|
||||
<?php
|
||||
$displayTitle = ($contentSource === 'article' && $useArticleTitle && $articleTitle)
|
||||
? $articleTitle
|
||||
: $module->title;
|
||||
$showTitle = ($contentSource === 'article' && $useArticleTitle && $articleTitle) || $module->showtitle;
|
||||
?>
|
||||
<?php if ($heroContent || $showTitle) : ?>
|
||||
<?php if ($showCard) : ?>
|
||||
<div class="mokojoomhero__card"<?php if ($cardDelay) : ?> style="animation-delay: <?php echo $cardDelay; ?>ms;" data-card-delay="<?php echo $cardDelay; ?>"<?php endif; ?>>
|
||||
<?php if ($showTitle) : ?>
|
||||
<h2 class="mokojoomhero__title"><?php echo htmlspecialchars($displayTitle, ENT_QUOTES, 'UTF-8'); ?></h2>
|
||||
<?php endif; ?>
|
||||
<?php echo $heroContent; ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<?php if ($showTitle) : ?>
|
||||
<h2 class="mokojoomhero__title"><?php echo htmlspecialchars($displayTitle, ENT_QUOTES, 'UTF-8'); ?></h2>
|
||||
<?php endif; ?>
|
||||
<?php echo $heroContent; ?>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
<html><body bgcolor="#FFFFFF"></body></html>
|
||||
@@ -1 +0,0 @@
|
||||
<html><body bgcolor="#FFFFFF"></body></html>
|
||||
@@ -1,2 +0,0 @@
|
||||
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
; SPDX-License-Identifier: GPL-3.0-or-later
|
||||
@@ -1,5 +0,0 @@
|
||||
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
; SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
PLG_SYSTEM_MOKOJOOMHERO="System - MokoJoomHero"
|
||||
PLG_SYSTEM_MOKOJOOMHERO_DESCRIPTION="System plugin for MokoJoomHero"
|
||||
@@ -1 +0,0 @@
|
||||
<html><body bgcolor="#FFFFFF"></body></html>
|
||||
@@ -1,2 +0,0 @@
|
||||
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
; SPDX-License-Identifier: GPL-3.0-or-later
|
||||
@@ -1,5 +0,0 @@
|
||||
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
; SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
PLG_SYSTEM_MOKOJOOMHERO="System - MokoJoomHero"
|
||||
PLG_SYSTEM_MOKOJOOMHERO_DESCRIPTION="System plugin for MokoJoomHero"
|
||||
@@ -1 +0,0 @@
|
||||
<html><body bgcolor="#FFFFFF"></body></html>
|
||||
@@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package MokoJoomHero
|
||||
* @subpackage plg_system_mokojoomhero
|
||||
* @author Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
@@ -1,34 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
* @package MokoJoomHero
|
||||
* @subpackage plg_system_mokojoomhero
|
||||
* @author Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
-->
|
||||
<extension type="plugin" group="system" method="upgrade">
|
||||
<name>PLG_SYSTEM_MOKOJOOMHERO</name>
|
||||
<version>01.08.00-rc</version>
|
||||
<creationDate>2026-06-02</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<description>PLG_SYSTEM_MOKOJOOMHERO_DESCRIPTION</description>
|
||||
|
||||
<namespace path="src">Joomla\Plugin\System\MokoJoomHero</namespace>
|
||||
|
||||
<files>
|
||||
<filename plugin="mokojoomhero">mokojoomhero.php</filename>
|
||||
<folder>services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
|
||||
<languages folder="language">
|
||||
<language tag="en-GB">en-GB/plg_system_mokojoomhero.ini</language>
|
||||
<language tag="en-GB">en-GB/plg_system_mokojoomhero.sys.ini</language>
|
||||
<language tag="en-US">en-US/plg_system_mokojoomhero.ini</language>
|
||||
<language tag="en-US">en-US/plg_system_mokojoomhero.sys.ini</language>
|
||||
</languages>
|
||||
</extension>
|
||||
@@ -1 +0,0 @@
|
||||
<html><body bgcolor="#FFFFFF"></body></html>
|
||||
@@ -1,39 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package MokoJoomHero
|
||||
* @subpackage plg_system_mokojoomhero
|
||||
* @author Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Extension\PluginInterface;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Joomla\Plugin\System\MokoJoomHero\Extension\MokoJoomHero;
|
||||
|
||||
return new class implements ServiceProviderInterface {
|
||||
public function register(Container $container): void
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
function (Container $container) {
|
||||
$plugin = new MokoJoomHero(
|
||||
$container->get(DispatcherInterface::class),
|
||||
(array) PluginHelper::getPlugin('system', 'mokojoomhero')
|
||||
);
|
||||
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package MokoJoomHero
|
||||
* @subpackage plg_system_mokojoomhero
|
||||
* @author Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\System\MokoJoomHero\Extension;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
class MokoJoomHero extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<html><body bgcolor="#FFFFFF"></body></html>
|
||||
@@ -1 +0,0 @@
|
||||
<html><body bgcolor="#FFFFFF"></body></html>
|
||||
@@ -1,30 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
* @package MokoJoomHero
|
||||
* @author Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
-->
|
||||
<extension type="package" method="upgrade">
|
||||
<name>Package - MokoJoomHero</name>
|
||||
<packagename>mokojoomhero</packagename>
|
||||
<version>01.08.00-rc</version>
|
||||
<creationDate>2026-06-02</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<description>Random hero image slideshow or background video with content overlay. Includes the hero module and system plugin. By Moko Consulting.</description>
|
||||
|
||||
<scriptfile>pkg_script.php</scriptfile>
|
||||
|
||||
<files folder="packages">
|
||||
<file type="module" id="mod_mokojoomhero" client="site">mod_mokojoomhero.zip</file>
|
||||
<file type="plugin" id="mokojoomhero" group="system">plg_system_mokojoomhero.zip</file>
|
||||
</files>
|
||||
|
||||
<updateservers>
|
||||
<server type="extension" name="MokoJoomHero Updates">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/raw/branch/main/updates.xml</server>
|
||||
</updateservers>
|
||||
</extension>
|
||||
@@ -1,59 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package MokoJoomHero
|
||||
* @author Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Installer\InstallerAdapter;
|
||||
|
||||
class Pkg_MokoJoomHeroInstallerScript
|
||||
{
|
||||
/**
|
||||
* Called after install/update — only enables the system plugin on fresh install.
|
||||
*
|
||||
* @param string $type Action type
|
||||
* @param InstallerAdapter $parent Installer adapter
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function postflight(string $type, InstallerAdapter $parent): void
|
||||
{
|
||||
if ($type === 'install') {
|
||||
try {
|
||||
$db = Factory::getDbo();
|
||||
|
||||
// Enable the system plugin automatically on fresh install
|
||||
$query = $db->getQuery(true)
|
||||
->update($db->quoteName('#__extensions'))
|
||||
->set($db->quoteName('enabled') . ' = 1')
|
||||
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
|
||||
->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
|
||||
->where($db->quoteName('element') . ' = ' . $db->quote('mokojoomhero'));
|
||||
|
||||
$db->setQuery($query);
|
||||
$db->execute();
|
||||
|
||||
if ($db->getAffectedRows() === 0) {
|
||||
Factory::getApplication()->enqueueMessage(
|
||||
'MokoJoomHero: The system plugin could not be auto-enabled. '
|
||||
. 'Please enable it manually in Extensions → Plugins.',
|
||||
'warning'
|
||||
);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Factory::getApplication()->enqueueMessage(
|
||||
'MokoJoomHero: Failed to auto-enable system plugin: ' . $e->getMessage()
|
||||
. ' — Please enable it manually in Extensions → Plugins.',
|
||||
'warning'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
*
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GPL-3.0-or-later
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Site
|
||||
* @subpackage mod_mokojoomhero
|
||||
*
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
/** @var string $heroMode */
|
||||
/** @var array $heroImages */
|
||||
/** @var int $slideInterval */
|
||||
/** @var string $videoUrl */
|
||||
/** @var string $youtubeId */
|
||||
/** @var string $vimeoId */
|
||||
/** @var string $heroHeight */
|
||||
/** @var string $overlayColor */
|
||||
/** @var float $overlayOpacity */
|
||||
/** @var string $textAlign */
|
||||
/** @var string $textColor */
|
||||
/** @var string $heroContent */
|
||||
/** @var bool $showCard */
|
||||
/** @var int $cardDelay */
|
||||
/** @var bool $showMuteToggle */
|
||||
/** @var string $content */
|
||||
|
||||
$moduleId = 'mod-mokojoomhero-' . $module->id;
|
||||
|
||||
// Convert hex overlay colour to rgba
|
||||
$r = hexdec(substr($overlayColor, 1, 2));
|
||||
$g = hexdec(substr($overlayColor, 3, 2));
|
||||
$b = hexdec(substr($overlayColor, 5, 2));
|
||||
$rgba = "rgba($r, $g, $b, $overlayOpacity)";
|
||||
|
||||
$heightAttr = htmlspecialchars($heroHeight, ENT_QUOTES, 'UTF-8');
|
||||
?>
|
||||
<div id="<?php echo $moduleId; ?>" class="mokojoomhero" style="height: <?php echo $heightAttr; ?>;"
|
||||
<?php if ($heroMode === 'images' && count($heroImages) > 1) : ?>
|
||||
data-slides="<?php echo htmlspecialchars(json_encode($heroImages), ENT_QUOTES, 'UTF-8'); ?>"
|
||||
data-interval="<?php echo $slideInterval; ?>"
|
||||
<?php endif; ?>
|
||||
>
|
||||
<?php // Background layer — single image, slideshow, or video ?>
|
||||
<?php if ($heroMode === 'video' && $youtubeId) : ?>
|
||||
<iframe class="mokojoomhero__video" src="https://www.youtube-nocookie.com/embed/<?php echo htmlspecialchars($youtubeId, ENT_QUOTES, 'UTF-8'); ?>?autoplay=1&mute=1&loop=1&playlist=<?php echo htmlspecialchars($youtubeId, ENT_QUOTES, 'UTF-8'); ?>&controls=0&showinfo=0&rel=0&modestbranding=1&playsinline=1&enablejsapi=1&origin=<?php echo htmlspecialchars(\Joomla\CMS\Uri\Uri::root(), ENT_QUOTES, 'UTF-8'); ?>" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
<?php elseif ($heroMode === 'video' && $vimeoId) : ?>
|
||||
<iframe class="mokojoomhero__video" src="https://player.vimeo.com/video/<?php echo htmlspecialchars($vimeoId, ENT_QUOTES, 'UTF-8'); ?>?autoplay=1&muted=1&loop=1&background=1" allow="autoplay" allowfullscreen></iframe>
|
||||
<?php elseif (($heroMode === 'video' || $heroMode === 'localvideo') && $videoUrl) : ?>
|
||||
<video class="mokojoomhero__video" autoplay muted loop playsinline>
|
||||
<source src="<?php echo htmlspecialchars($videoUrl, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
</video>
|
||||
<?php elseif ($heroImages) : ?>
|
||||
<?php foreach ($heroImages as $i => $img) : ?>
|
||||
<div class="mokojoomhero__slide<?php echo $i === 0 ? ' mokojoomhero__slide--active' : ''; ?>"
|
||||
style="background-image: url('<?php echo htmlspecialchars($img, ENT_QUOTES, 'UTF-8'); ?>');"
|
||||
aria-hidden="<?php echo $i === 0 ? 'false' : 'true'; ?>">
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (($heroMode === 'video' || $heroMode === 'localvideo') && $showMuteToggle) : ?>
|
||||
<button class="mokojoomhero__mute-toggle" type="button" aria-label="Unmute video" data-muted="true">
|
||||
<span class="mokojoomhero__mute-icon" aria-hidden="true">🔇</span>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php // Overlay + content ?>
|
||||
<div class="mokojoomhero__overlay" style="background-color: <?php echo $rgba; ?>;">
|
||||
<div class="mokojoomhero__content" style="text-align: <?php echo htmlspecialchars($textAlign, ENT_QUOTES, 'UTF-8'); ?>; color: <?php echo htmlspecialchars($textColor, ENT_QUOTES, 'UTF-8'); ?>;">
|
||||
<?php if ($heroContent || $module->showtitle) : ?>
|
||||
<?php if ($showCard) : ?>
|
||||
<div class="mokojoomhero__card"<?php if ($cardDelay) : ?> style="animation-delay: <?php echo $cardDelay; ?>ms;" data-card-delay="<?php echo $cardDelay; ?>"<?php endif; ?>>
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<h2 class="mokojoomhero__title"><?php echo htmlspecialchars($module->title, ENT_QUOTES, 'UTF-8'); ?></h2>
|
||||
<?php endif; ?>
|
||||
<?php echo $heroContent; ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<h2 class="mokojoomhero__title"><?php echo htmlspecialchars($module->title, ENT_QUOTES, 'UTF-8'); ?></h2>
|
||||
<?php endif; ?>
|
||||
<?php echo $heroContent; ?>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
+86
-10
@@ -1,27 +1,103 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
VERSION: 01.12.00
|
||||
VERSION: 01.07.00
|
||||
-->
|
||||
|
||||
<updates>
|
||||
<update>
|
||||
<name>Package - MokoJoomHero</name>
|
||||
<description>Package - MokoJoomHero stable build.</description>
|
||||
<element>pkg_mokojoomhero</element>
|
||||
<type>package</type>
|
||||
<name>MokoJoomHero</name>
|
||||
<description>MokoJoomHero stable build.</description>
|
||||
<element>mod_mokojoomhero</element>
|
||||
<type>module</type>
|
||||
<client>site</client>
|
||||
<version>01.12.00</version>
|
||||
<creationDate>2026-06-04</creationDate>
|
||||
<infourl title='Package - MokoJoomHero'>https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/tag/stable</infourl>
|
||||
<version>01.06.00</version>
|
||||
<creationDate>2026-05-30</creationDate>
|
||||
<infourl title="MokoJoomHero">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/tag/stable</infourl>
|
||||
<downloads>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/download/stable/pkg_mokojoomhero-01.12.00.zip</downloadurl>
|
||||
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/download/stable/mod_mokojoomhero-01.06.00.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>675e97f0f66e206b09a256152efdccd8afca979d7d68321a9931eb3d65fb3e0d</sha256>
|
||||
<sha256>de21e4010e19323746c9aeff12d6a240dc29a2a0c3ef1e091549a2331e710bc7</sha256>
|
||||
<tags><tag>stable</tag></tags>
|
||||
<changelogurl>https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/raw/branch/main/CHANGELOG.md</changelogurl>
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
<targetplatform name="joomla" version="(5|6)\..*"/>
|
||||
</update>
|
||||
<update>
|
||||
<name>Module - MokoJoomHero</name>
|
||||
<description>Module - MokoJoomHero dev build.</description>
|
||||
<element>mod_mokojoomhero</element>
|
||||
<type>module</type>
|
||||
<client>site</client>
|
||||
<version>01.07.00-dev</version>
|
||||
<creationDate>2026-05-30</creationDate>
|
||||
<infourl title="Module - MokoJoomHero">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/tag/development</infourl>
|
||||
<downloads>
|
||||
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/download/development/mod_mokojoomhero-01.07.00-dev.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>32301fad78e9ad31613d2d02a8f2dd0519f5118b7683335b36532a4e1e5fe969</sha256>
|
||||
<tags><tag>dev</tag></tags>
|
||||
<changelogurl>https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/raw/branch/main/CHANGELOG.md</changelogurl>
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
<targetplatform name="joomla" version="(5|6)\..*"/>
|
||||
</update>
|
||||
<update>
|
||||
<name>Module - MokoJoomHero</name>
|
||||
<description>Module - MokoJoomHero alpha build.</description>
|
||||
<element>mod_mokojoomhero</element>
|
||||
<type>module</type>
|
||||
<client>site</client>
|
||||
<version>01.07.00-alpha</version>
|
||||
<creationDate>2026-05-30</creationDate>
|
||||
<infourl title="Module - MokoJoomHero">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/tag/alpha</infourl>
|
||||
<downloads>
|
||||
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/download/alpha/mod_mokojoomhero-01.07.00-alpha.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>32301fad78e9ad31613d2d02a8f2dd0519f5118b7683335b36532a4e1e5fe969</sha256>
|
||||
<tags><tag>alpha</tag></tags>
|
||||
<changelogurl>https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/raw/branch/main/CHANGELOG.md</changelogurl>
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
<targetplatform name="joomla" version="(5|6)\..*"/>
|
||||
</update>
|
||||
<update>
|
||||
<name>Module - MokoJoomHero</name>
|
||||
<description>Module - MokoJoomHero beta build.</description>
|
||||
<element>mod_mokojoomhero</element>
|
||||
<type>module</type>
|
||||
<client>site</client>
|
||||
<version>01.07.00-beta</version>
|
||||
<creationDate>2026-05-30</creationDate>
|
||||
<infourl title="Module - MokoJoomHero">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/tag/beta</infourl>
|
||||
<downloads>
|
||||
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/download/beta/mod_mokojoomhero-01.07.00-beta.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>32301fad78e9ad31613d2d02a8f2dd0519f5118b7683335b36532a4e1e5fe969</sha256>
|
||||
<tags><tag>beta</tag></tags>
|
||||
<changelogurl>https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/raw/branch/main/CHANGELOG.md</changelogurl>
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
<targetplatform name="joomla" version="(5|6)\..*"/>
|
||||
</update>
|
||||
<update>
|
||||
<name>Module - MokoJoomHero</name>
|
||||
<description>Module - MokoJoomHero rc build.</description>
|
||||
<element>mod_mokojoomhero</element>
|
||||
<type>module</type>
|
||||
<client>site</client>
|
||||
<version>01.07.00-rc</version>
|
||||
<creationDate>2026-05-30</creationDate>
|
||||
<infourl title='Module - MokoJoomHero'>https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/tag/release-candidate</infourl>
|
||||
<downloads>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/download/release-candidate/mod_mokojoomhero-01.07.00-rc.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>32301fad78e9ad31613d2d02a8f2dd0519f5118b7683335b36532a4e1e5fe969</sha256>
|
||||
<tags><tag>rc</tag></tags>
|
||||
<changelogurl>https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/raw/branch/main/CHANGELOG.md</changelogurl>
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
<targetplatform name="joomla" version="(5|6)\..*" />
|
||||
</update>
|
||||
</updates>
|
||||
|
||||
Reference in New Issue
Block a user