Compare commits
88 Commits
release-ca
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| d9f241c80a | |||
|
|
5ff523837f | ||
|
|
96d7520fa8 | ||
|
|
598605c65c | ||
|
|
f6ac970ec0 | ||
| 3340c00e26 | |||
|
|
89e255265d | ||
|
|
484c223c7e | ||
| 1be001993e | |||
| 5b08fe53b5 | |||
|
|
3b90a375aa | ||
|
|
a0dc907f8d | ||
| 6f15c3f4f0 | |||
| 489ad687af | |||
| 2e69795536 | |||
| d6f6476454 | |||
|
|
592b0ea640 | ||
|
|
db584db4af | ||
| 12a98762cd | |||
| 2f000c330e | |||
|
|
4977a8c6ff | ||
|
|
c379fab50d | ||
| 6d09487aa7 | |||
|
|
a292026966 | ||
|
|
9ce82bdacc | ||
| ce01759455 | |||
|
|
7af3dc0b2d | ||
|
|
e98a1d125f | ||
|
|
3e9379f300 | ||
|
|
e7e71bf77e | ||
|
|
f438398e81 | ||
|
|
b158e7684c | ||
|
|
765bb9b368 | ||
|
|
659486fac4 | ||
|
|
f2d496b7e5 | ||
|
|
3339dbb620 | ||
|
|
50331262c9 | ||
|
|
f8da2bef6a | ||
|
|
eee3242c4b | ||
|
|
157eab4470 | ||
|
|
208a072161 | ||
|
|
4423294272 | ||
|
|
5c014a1484 | ||
|
|
d9cc2707bb | ||
|
|
cf0051213f | ||
|
|
17174814ba | ||
|
|
ea2ccc482c | ||
|
|
6a40dc558b | ||
|
|
30bba91a8a | ||
|
|
9760cb4a0e | ||
|
|
f6b3f7f0ab | ||
|
|
7c9850c0a9 | ||
|
|
acff16c0a0 | ||
|
|
0672f63136 | ||
|
|
139f1d643e | ||
|
|
005efe752c | ||
|
|
fb4f764bc4 | ||
|
|
b5cbba7899 | ||
|
|
9d0089eac3 | ||
|
|
ee345f1bb6 | ||
|
|
edc66d3404 | ||
|
|
51718b2bb8 | ||
|
|
9a0345defd | ||
|
|
bc546cc1e1 | ||
|
|
f99715743b | ||
|
|
d903e1e232 | ||
|
|
b6ff24da50 | ||
|
|
748cd855f5 | ||
|
|
30660cfee8 | ||
|
|
5bd66387e3 | ||
|
|
55d6a3ff7c | ||
|
|
7c43a9fe64 | ||
|
|
b7ab26c999 | ||
|
|
b965070f3f | ||
|
|
084245e9c1 | ||
|
|
602d3f69bc | ||
|
|
61aa54df78 | ||
|
|
8430ea6804 | ||
|
|
ad1070be03 | ||
|
|
8db0a4fa88 | ||
|
|
fb8d91c716 | ||
|
|
455f5825db | ||
|
|
c9222b4c31 | ||
|
|
314a4683ae | ||
|
|
c1e6a5f42d | ||
|
|
44b823d4f7 | ||
| a581da7bdc | |||
|
|
67f6b61bf2 |
532
.gitea/workflows/release.yml
Normal file
532
.gitea/workflows/release.yml
Normal file
@@ -0,0 +1,532 @@
|
|||||||
|
# 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: Gitea.Workflow
|
||||||
|
# INGROUP: MokoOnyx.Release
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||||
|
# PATH: /.gitea/workflows/release.yml
|
||||||
|
# VERSION: 01.00.00
|
||||||
|
# BRIEF: Joomla release — build ZIP, publish to Gitea, mirror to GitHub
|
||||||
|
|
||||||
|
name: Create Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '[0-9][0-9].[0-9][0-9].[0-9][0-9]'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: 'Release version (e.g., 01.00.00)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
prerelease:
|
||||||
|
description: 'Mark as pre-release'
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
stability:
|
||||||
|
description: 'Stability tag (development, alpha, beta, rc, stable)'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: 'development'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
GITEA_URL: https://git.mokoconsulting.tech
|
||||||
|
GITEA_ORG: MokoConsulting
|
||||||
|
GITEA_REPO: MokoOnyx
|
||||||
|
EXT_ELEMENT: mokoonyx
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build Release Package
|
||||||
|
runs-on: release
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
run: |
|
||||||
|
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
|
||||||
|
php -v
|
||||||
|
composer --version
|
||||||
|
|
||||||
|
- name: Get version and stability
|
||||||
|
id: meta
|
||||||
|
run: |
|
||||||
|
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
|
||||||
|
VERSION="${{ inputs.version }}"
|
||||||
|
STABILITY="${{ inputs.stability }}"
|
||||||
|
PRERELEASE="${{ inputs.prerelease }}"
|
||||||
|
else
|
||||||
|
VERSION=${GITHUB_REF#refs/tags/}
|
||||||
|
STABILITY="stable"
|
||||||
|
PRERELEASE="false"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Derive suffix and tag from stability
|
||||||
|
case "$STABILITY" in
|
||||||
|
development) SUFFIX="-dev"; TAG_NAME="development" ;;
|
||||||
|
alpha) SUFFIX="-alpha"; TAG_NAME="alpha" ;;
|
||||||
|
beta) SUFFIX="-beta"; TAG_NAME="beta" ;;
|
||||||
|
rc) SUFFIX="-rc"; TAG_NAME="release-candidate" ;;
|
||||||
|
stable) SUFFIX=""; TAG_NAME="stable" ;;
|
||||||
|
*) SUFFIX="-dev"; TAG_NAME="development" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
ZIP_NAME="${EXT_ELEMENT}-${VERSION}${SUFFIX}.zip"
|
||||||
|
|
||||||
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "prerelease=${PRERELEASE}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "tag_name=${TAG_NAME}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Building: ${ZIP_NAME} (${STABILITY})"
|
||||||
|
|
||||||
|
- name: Auto-bump patch version
|
||||||
|
id: bump
|
||||||
|
env:
|
||||||
|
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||||
|
INPUT_VERSION: ${{ steps.meta.outputs.version }}
|
||||||
|
INPUT_STABILITY: ${{ steps.meta.outputs.stability }}
|
||||||
|
INPUT_SUFFIX: ${{ steps.meta.outputs.suffix }}
|
||||||
|
run: |
|
||||||
|
BRANCH="${{ github.ref_name }}"
|
||||||
|
GITEA_API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||||
|
|
||||||
|
# Read current version from README.md
|
||||||
|
CURRENT=$(sed -n 's/.*VERSION:[[:space:]]*\([0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]\).*/\1/p' README.md 2>/dev/null | head -1)
|
||||||
|
if [ -z "$CURRENT" ]; then
|
||||||
|
echo "No VERSION in README.md — using input version"
|
||||||
|
echo "version=${INPUT_VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "zip_name=${EXT_ELEMENT}-${INPUT_VERSION}${INPUT_SUFFIX}.zip" >> "$GITHUB_OUTPUT"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Bump patch: XX.YY.ZZ → XX.YY.(ZZ+1)
|
||||||
|
MAJOR=$(echo "$CURRENT" | cut -d. -f1)
|
||||||
|
MINOR=$(echo "$CURRENT" | cut -d. -f2)
|
||||||
|
PATCH=$(echo "$CURRENT" | cut -d. -f3)
|
||||||
|
NEW_PATCH=$(printf "%02d" $((10#$PATCH + 1)))
|
||||||
|
NEW_VERSION="${MAJOR}.${MINOR}.${NEW_PATCH}"
|
||||||
|
|
||||||
|
echo "Bumping: ${CURRENT} → ${NEW_VERSION}"
|
||||||
|
|
||||||
|
# Update README.md
|
||||||
|
sed -i "s/VERSION:[[:space:]]*${CURRENT}/VERSION: ${NEW_VERSION}/" README.md
|
||||||
|
|
||||||
|
# Update templateDetails.xml / manifest
|
||||||
|
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
||||||
|
if [ -n "$MANIFEST" ]; then
|
||||||
|
sed -i "s|<version>${CURRENT}</version>|<version>${NEW_VERSION}</version>|" "$MANIFEST"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update only the matching stability channel in updates.xml
|
||||||
|
if [ -f "updates.xml" ]; then
|
||||||
|
export PY_OLD="$CURRENT" PY_NEW="$NEW_VERSION" PY_STABILITY="$INPUT_STABILITY"
|
||||||
|
python3 << 'PYEOF'
|
||||||
|
import re, os
|
||||||
|
old = os.environ["PY_OLD"]
|
||||||
|
new = os.environ["PY_NEW"]
|
||||||
|
stability = os.environ["PY_STABILITY"]
|
||||||
|
with open("updates.xml") as f:
|
||||||
|
content = f.read()
|
||||||
|
pattern = r"(<update>(?:(?!</update>).)*?<tag>" + re.escape(stability) + r"</tag>.*?</update>)"
|
||||||
|
match = re.search(pattern, content, re.DOTALL)
|
||||||
|
if match:
|
||||||
|
block = match.group(1)
|
||||||
|
updated = block.replace(old, new)
|
||||||
|
content = content.replace(block, updated)
|
||||||
|
with open("updates.xml", "w") as f:
|
||||||
|
f.write(content)
|
||||||
|
print(f"Updated {stability} channel: {old} -> {new}")
|
||||||
|
PYEOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Commit bump to current branch
|
||||||
|
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||||
|
git config --local user.name "gitea-actions[bot]"
|
||||||
|
git remote set-url origin "https://jmiller:${GA_TOKEN}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
|
git add -A
|
||||||
|
git diff --cached --quiet || {
|
||||||
|
git commit -m "chore(version): bump ${CURRENT} → ${NEW_VERSION} [skip ci]" \
|
||||||
|
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||||
|
git push
|
||||||
|
}
|
||||||
|
|
||||||
|
# For stable releases from dev: merge dev → main via Gitea API
|
||||||
|
if [ "$INPUT_STABILITY" = "stable" ] && [ "$BRANCH" != "main" ]; then
|
||||||
|
echo "Merging ${BRANCH} → main via Gitea API..."
|
||||||
|
curl -sf -X POST -H "Authorization: token ${GA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${GITEA_API}/merges" \
|
||||||
|
-d "$(jq -n \
|
||||||
|
--arg base "main" \
|
||||||
|
--arg head "${BRANCH}" \
|
||||||
|
--arg msg "chore(release): merge ${BRANCH} for stable ${NEW_VERSION} [skip ci]" \
|
||||||
|
'{base: $base, head: $head, merge_message_field: $msg}'
|
||||||
|
)" > /dev/null 2>&1 || echo "Merge API call failed — may need manual merge"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "version=${NEW_VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "zip_name=${EXT_ELEMENT}-${NEW_VERSION}${INPUT_SUFFIX}.zip" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
env:
|
||||||
|
COMPOSER_AUTH: '{"http-basic":{"git.mokoconsulting.tech":{"username":"token","password":"${{ secrets.GA_TOKEN }}"}}}'
|
||||||
|
run: |
|
||||||
|
if [ -f "composer.json" ]; then
|
||||||
|
composer install --no-dev --optimize-autoloader --no-interaction
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- name: Minify CSS and JS
|
||||||
|
run: |
|
||||||
|
npm ci --ignore-scripts
|
||||||
|
node scripts/minify.js
|
||||||
|
|
||||||
|
- name: Create package
|
||||||
|
run: |
|
||||||
|
mkdir -p build/package
|
||||||
|
rsync -av \
|
||||||
|
--exclude='sftp-config*' \
|
||||||
|
--exclude='.ftpignore' \
|
||||||
|
--exclude='*.ppk' \
|
||||||
|
--exclude='*.pem' \
|
||||||
|
--exclude='*.key' \
|
||||||
|
--exclude='.env*' \
|
||||||
|
--exclude='*.local' \
|
||||||
|
src/ build/package/
|
||||||
|
|
||||||
|
- name: Build ZIP
|
||||||
|
id: zip
|
||||||
|
run: |
|
||||||
|
ZIP_NAME="${{ steps.bump.outputs.zip_name }}"
|
||||||
|
cd build/package
|
||||||
|
zip -r "../${ZIP_NAME}" .
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
SHA256=$(sha256sum "${ZIP_NAME}" | cut -d' ' -f1)
|
||||||
|
SIZE=$(stat -c%s "${ZIP_NAME}")
|
||||||
|
|
||||||
|
echo "sha256=${SHA256}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "size=${SIZE}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "SHA-256: ${SHA256}"
|
||||||
|
echo "Size: ${SIZE} bytes"
|
||||||
|
|
||||||
|
# ── Gitea Release (PRIMARY) ──────────────────────────────────────
|
||||||
|
- name: "Gitea: Delete existing release"
|
||||||
|
run: |
|
||||||
|
TAG="${{ steps.meta.outputs.tag_name }}"
|
||||||
|
TOKEN="${{ secrets.GA_TOKEN }}"
|
||||||
|
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
|
||||||
|
# Find and delete existing release by tag (may not exist — ignore 404)
|
||||||
|
RELEASE_ID=$(curl -s -H "Authorization: token ${TOKEN}" \
|
||||||
|
"${API}/releases/tags/${TAG}" 2>/dev/null | jq -r '.id // empty')
|
||||||
|
|
||||||
|
if [ -n "$RELEASE_ID" ]; then
|
||||||
|
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||||
|
"${API}/releases/${RELEASE_ID}" || true
|
||||||
|
echo "Deleted existing release id=${RELEASE_ID}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Delete existing tag
|
||||||
|
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||||
|
"${API}/tags/${TAG}" 2>/dev/null || true
|
||||||
|
|
||||||
|
- name: "Gitea: Create release"
|
||||||
|
id: gitea_release
|
||||||
|
run: |
|
||||||
|
TAG="${{ steps.meta.outputs.tag_name }}"
|
||||||
|
VERSION="${{ steps.bump.outputs.version }}"
|
||||||
|
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||||
|
PRERELEASE="${{ steps.meta.outputs.prerelease }}"
|
||||||
|
SHA256="${{ steps.zip.outputs.sha256 }}"
|
||||||
|
TOKEN="${{ secrets.GA_TOKEN }}"
|
||||||
|
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
|
||||||
|
# Build release body
|
||||||
|
BODY="## ${EXT_ELEMENT} ${VERSION} (${STABILITY})
|
||||||
|
|
||||||
|
### SHA-256
|
||||||
|
\`${SHA256}\`"
|
||||||
|
|
||||||
|
# Extract changelog if available
|
||||||
|
if [ -f "CHANGELOG.md" ]; then
|
||||||
|
NOTES=$(awk "/## \[${VERSION}\]/,/## \[/{if(/## \[${VERSION}\]/)next;if(/## \[/)exit;print}" CHANGELOG.md)
|
||||||
|
if [ -n "$NOTES" ]; then
|
||||||
|
BODY="## ${EXT_ELEMENT} ${VERSION} (${STABILITY})
|
||||||
|
|
||||||
|
${NOTES}
|
||||||
|
|
||||||
|
### SHA-256
|
||||||
|
\`${SHA256}\`"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
IS_PRE="true"
|
||||||
|
if [ "$STABILITY" = "stable" ]; then
|
||||||
|
IS_PRE="false"
|
||||||
|
fi
|
||||||
|
|
||||||
|
RESULT=$(curl -sf -X POST -H "Authorization: token ${TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${API}/releases" \
|
||||||
|
-d "$(jq -n \
|
||||||
|
--arg tag "$TAG" \
|
||||||
|
--arg target "${{ github.ref_name }}" \
|
||||||
|
--arg name "${EXT_ELEMENT} ${VERSION} ${STABILITY^}" \
|
||||||
|
--arg body "$BODY" \
|
||||||
|
--argjson pre "$IS_PRE" \
|
||||||
|
'{tag_name: $tag, target_commitish: $target, name: $name, body: $body, prerelease: $pre}'
|
||||||
|
)")
|
||||||
|
|
||||||
|
RELEASE_ID=$(echo "$RESULT" | jq -r '.id')
|
||||||
|
echo "release_id=${RELEASE_ID}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Gitea release created: id=${RELEASE_ID}, tag=${TAG}"
|
||||||
|
|
||||||
|
- name: "Gitea: Upload ZIP"
|
||||||
|
run: |
|
||||||
|
RELEASE_ID="${{ steps.gitea_release.outputs.release_id }}"
|
||||||
|
ZIP_NAME="${{ steps.bump.outputs.zip_name }}"
|
||||||
|
TOKEN="${{ secrets.GA_TOKEN }}"
|
||||||
|
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
|
||||||
|
curl -sf -X POST \
|
||||||
|
-H "Authorization: token ${TOKEN}" \
|
||||||
|
-H "Content-Type: application/octet-stream" \
|
||||||
|
"${API}/releases/${RELEASE_ID}/assets?name=${ZIP_NAME}" \
|
||||||
|
--data-binary "@build/${ZIP_NAME}"
|
||||||
|
|
||||||
|
echo "Uploaded ${ZIP_NAME} to Gitea release ${RELEASE_ID}"
|
||||||
|
|
||||||
|
# ── GitHub Mirror (BACKUP) ───────────────────────────────────────
|
||||||
|
- name: "GitHub: Mirror release (stable/rc only)"
|
||||||
|
if: ${{ steps.meta.outputs.stability == 'stable' || steps.meta.outputs.stability == 'rc' }}
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
TAG="${{ steps.meta.outputs.tag_name }}"
|
||||||
|
VERSION="${{ steps.bump.outputs.version }}"
|
||||||
|
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||||
|
ZIP_NAME="${{ steps.bump.outputs.zip_name }}"
|
||||||
|
SHA256="${{ steps.zip.outputs.sha256 }}"
|
||||||
|
TOKEN="${{ secrets.GH_TOKEN }}"
|
||||||
|
GH_REPO="mokoconsulting-tech/${GITEA_REPO}"
|
||||||
|
GH_API="https://api.github.com/repos/${GH_REPO}"
|
||||||
|
|
||||||
|
IS_PRE="true"
|
||||||
|
[ "$STABILITY" = "stable" ] && IS_PRE="false"
|
||||||
|
|
||||||
|
# Delete existing release by tag
|
||||||
|
EXISTING=$(curl -sf -H "Authorization: token ${TOKEN}" \
|
||||||
|
"${GH_API}/releases/tags/${TAG}" 2>/dev/null | jq -r '.id // empty')
|
||||||
|
if [ -n "$EXISTING" ]; then
|
||||||
|
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||||
|
"${GH_API}/releases/${EXISTING}" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Delete tag
|
||||||
|
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||||
|
"${GH_API}/git/refs/tags/${TAG}" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Create release
|
||||||
|
RELEASE_ID=$(curl -sf -X POST -H "Authorization: token ${TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${GH_API}/releases" \
|
||||||
|
-d "$(jq -n \
|
||||||
|
--arg tag "$TAG" \
|
||||||
|
--arg target "${{ github.sha }}" \
|
||||||
|
--arg name "${EXT_ELEMENT} ${VERSION} ${STABILITY^} (mirror)" \
|
||||||
|
--arg body "Mirror of Gitea release. SHA-256: \`${SHA256}\`" \
|
||||||
|
--argjson pre "$IS_PRE" \
|
||||||
|
'{tag_name: $tag, target_commitish: $target, name: $name, body: $body, prerelease: $pre, draft: false}'
|
||||||
|
)" | jq -r '.id')
|
||||||
|
|
||||||
|
# Upload ZIP
|
||||||
|
if [ -n "$RELEASE_ID" ] && [ "$RELEASE_ID" != "null" ]; then
|
||||||
|
curl -sf -X POST \
|
||||||
|
-H "Authorization: token ${TOKEN}" \
|
||||||
|
-H "Content-Type: application/octet-stream" \
|
||||||
|
"https://uploads.github.com/repos/${GH_REPO}/releases/${RELEASE_ID}/assets?name=${ZIP_NAME}" \
|
||||||
|
--data-binary "@build/${ZIP_NAME}"
|
||||||
|
echo "GitHub mirror: uploaded ${ZIP_NAME}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Update updates.xml ──────────────────────────────────────────
|
||||||
|
- name: "Update updates.xml for this channel"
|
||||||
|
run: |
|
||||||
|
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||||
|
VERSION="${{ steps.bump.outputs.version }}"
|
||||||
|
SHA256="${{ steps.zip.outputs.sha256 }}"
|
||||||
|
ZIP_NAME="${{ steps.bump.outputs.zip_name }}"
|
||||||
|
TAG="${{ steps.meta.outputs.tag_name }}"
|
||||||
|
DATE=$(date +%Y-%m-%d)
|
||||||
|
|
||||||
|
if [ ! -f "updates.xml" ] || [ -z "$SHA256" ]; then
|
||||||
|
echo "No updates.xml or no SHA — skipping"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
export PY_STABILITY="$STABILITY" PY_VERSION="$VERSION" PY_SHA256="$SHA256" \
|
||||||
|
PY_ZIP_NAME="$ZIP_NAME" PY_TAG="$TAG" PY_DATE="$DATE" \
|
||||||
|
PY_GITEA_ORG="$GITEA_ORG" PY_GITEA_REPO="$GITEA_REPO"
|
||||||
|
python3 << 'PYEOF'
|
||||||
|
import re, os
|
||||||
|
|
||||||
|
stability = os.environ["PY_STABILITY"]
|
||||||
|
version = os.environ["PY_VERSION"]
|
||||||
|
sha256 = os.environ["PY_SHA256"]
|
||||||
|
zip_name = os.environ["PY_ZIP_NAME"]
|
||||||
|
tag = os.environ["PY_TAG"]
|
||||||
|
date = os.environ["PY_DATE"]
|
||||||
|
gitea_org = os.environ["PY_GITEA_ORG"]
|
||||||
|
gitea_repo = os.environ["PY_GITEA_REPO"]
|
||||||
|
|
||||||
|
# Map stability to the <tag> value in updates.xml
|
||||||
|
tag_map = {
|
||||||
|
"development": "development",
|
||||||
|
"alpha": "alpha",
|
||||||
|
"beta": "beta",
|
||||||
|
"rc": "rc",
|
||||||
|
"stable": "stable",
|
||||||
|
}
|
||||||
|
xml_tag = tag_map.get(stability, "development")
|
||||||
|
|
||||||
|
with open("updates.xml", "r") as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Build regex to find the specific <update> block for this stability tag
|
||||||
|
# Use negative lookahead to avoid matching across multiple <update> blocks
|
||||||
|
block_pattern = r"(<update>(?:(?!</update>).)*?<tag>" + re.escape(xml_tag) + r"</tag>.*?</update>)"
|
||||||
|
match = re.search(block_pattern, content, re.DOTALL)
|
||||||
|
|
||||||
|
if not match:
|
||||||
|
print(f"No <update> block found for <tag>{xml_tag}</tag>")
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
block = match.group(1)
|
||||||
|
original_block = block
|
||||||
|
|
||||||
|
# Update version
|
||||||
|
block = re.sub(r"<version>[^<]*</version>", f"<version>{version}</version>", block)
|
||||||
|
|
||||||
|
# Update creation date
|
||||||
|
block = re.sub(r"<creationDate>[^<]*</creationDate>", f"<creationDate>{date}</creationDate>", block)
|
||||||
|
|
||||||
|
# Update SHA-256
|
||||||
|
block = re.sub(r"<sha256>[^<]*</sha256>", f"<sha256>{sha256}</sha256>", block)
|
||||||
|
|
||||||
|
# Update Gitea download URL
|
||||||
|
gitea_url = f"https://git.mokoconsulting.tech/{gitea_org}/{gitea_repo}/releases/download/{tag}/{zip_name}"
|
||||||
|
block = re.sub(
|
||||||
|
r"(<downloadurl[^>]*>)https://git\.mokoconsulting\.tech/[^<]*(</downloadurl>)",
|
||||||
|
rf"\g<1>{gitea_url}\g<2>",
|
||||||
|
block
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update GitHub download URL only for RC and stable (others are Gitea-only)
|
||||||
|
if stability in ("rc", "stable"):
|
||||||
|
gh_url = f"https://github.com/mokoconsulting-tech/{gitea_repo}/releases/download/{tag}/{zip_name}"
|
||||||
|
block = re.sub(
|
||||||
|
r"(<downloadurl[^>]*>)https://github\.com/[^<]*(</downloadurl>)",
|
||||||
|
rf"\g<1>{gh_url}\g<2>",
|
||||||
|
block
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Remove any GitHub download URL for dev/alpha/beta
|
||||||
|
block = re.sub(
|
||||||
|
r"\n\s*<downloadurl[^>]*>https://github\.com/[^<]*</downloadurl>",
|
||||||
|
"",
|
||||||
|
block
|
||||||
|
)
|
||||||
|
|
||||||
|
content = content.replace(original_block, block)
|
||||||
|
|
||||||
|
with open("updates.xml", "w") as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
print(f"Updated {xml_tag} channel: version={version}, sha={sha256[:16]}..., date={date}")
|
||||||
|
PYEOF
|
||||||
|
|
||||||
|
- name: "Commit updates.xml to current branch and main"
|
||||||
|
run: |
|
||||||
|
if git diff --quiet updates.xml 2>/dev/null; then
|
||||||
|
echo "No changes to updates.xml"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||||
|
VERSION="${{ steps.bump.outputs.version }}"
|
||||||
|
CURRENT_BRANCH="${{ github.ref_name }}"
|
||||||
|
TOKEN="${{ secrets.GA_TOKEN }}"
|
||||||
|
|
||||||
|
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} SHA-256 for ${VERSION} [skip ci]" \
|
||||||
|
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||||
|
|
||||||
|
# Set push URL with GA_TOKEN for authenticated pushes (branch protection requires jmiller)
|
||||||
|
git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
|
|
||||||
|
# Push to current branch
|
||||||
|
git push || true
|
||||||
|
|
||||||
|
# Sync updates.xml to main via direct API
|
||||||
|
GA_TOKEN="${{ secrets.GA_TOKEN }}"
|
||||||
|
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||||
|
|
||||||
|
FILE_SHA=$(curl -sf -H "Authorization: token ${GA_TOKEN}" \
|
||||||
|
"${API}/contents/updates.xml?ref=main" | jq -r '.sha // empty')
|
||||||
|
|
||||||
|
if [ -n "$FILE_SHA" ]; then
|
||||||
|
CONTENT=$(base64 -w0 updates.xml)
|
||||||
|
curl -sf -X PUT -H "Authorization: token ${GA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${API}/contents/updates.xml" \
|
||||||
|
-d "$(jq -n \
|
||||||
|
--arg content "$CONTENT" \
|
||||||
|
--arg sha "$FILE_SHA" \
|
||||||
|
--arg msg "chore: sync updates.xml ${STABILITY} ${VERSION} [skip ci]" \
|
||||||
|
--arg branch "main" \
|
||||||
|
'{content: $content, sha: $sha, message: $msg, branch: $branch}'
|
||||||
|
)" > /dev/null 2>&1 \
|
||||||
|
&& echo "updates.xml synced to main" \
|
||||||
|
|| echo "WARNING: failed to sync updates.xml to main"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.bump.outputs.version }}"
|
||||||
|
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||||
|
ZIP_NAME="${{ steps.bump.outputs.zip_name }}"
|
||||||
|
SHA256="${{ steps.zip.outputs.sha256 }}"
|
||||||
|
TAG="${{ steps.meta.outputs.tag_name }}"
|
||||||
|
|
||||||
|
echo "### Release Created" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Stability | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Tag | \`${TAG}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| SHA-256 | \`${SHA256}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Gitea | [Release](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${TAG}) |" >> $GITHUB_STEP_SUMMARY
|
||||||
221
.github/CLAUDE.md
vendored
221
.github/CLAUDE.md
vendored
@@ -6,60 +6,31 @@ This file is part of a Moko Consulting project.
|
|||||||
SPDX-License-Identifier: GPL-3.0-or-later
|
SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
DEFGROUP: MokoStandards.Templates.GitHub
|
DEFGROUP: MokoOnyx.GitHub
|
||||||
INGROUP: MokoStandards.Templates
|
INGROUP: MokoOnyx
|
||||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||||
PATH: /templates/github/copilot-instructions.joomla.md.template
|
PATH: /.github/CLAUDE.md
|
||||||
VERSION: XX.YY.ZZ
|
VERSION: 01.00.05
|
||||||
BRIEF: GitHub Copilot custom instructions template for Joomla/MokoWaaS governed repositories
|
BRIEF: Claude Code custom instructions for MokoOnyx template
|
||||||
NOTE: Synced to .github/copilot-instructions.md in all Joomla/WaaS repos via bulk sync.
|
|
||||||
Tokens replaced at sync time: MokoOnyx, https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx, {{EXTENSION_NAME}},
|
|
||||||
{{EXTENSION_TYPE}}, {{EXTENSION_ELEMENT}}
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
> [!IMPORTANT]
|
# MokoOnyx — Claude Code Instructions
|
||||||
> **🔧 AI Self-Update Required on First Use**
|
|
||||||
>
|
|
||||||
> This file may contain unfilled `{{TOKEN}}` placeholders. The MokoStandards bulk-sync system
|
|
||||||
> replaces them automatically at sync time; if you can still see them, sync has not yet run for
|
|
||||||
> this repository — or this is a brand-new repo.
|
|
||||||
>
|
|
||||||
> **On your very first interaction in this repository, before answering any other question:**
|
|
||||||
>
|
|
||||||
> 1. Check whether any `{{TOKEN}}` placeholders remain in this file.
|
|
||||||
> 2. If they do, locate the values as described in the table below and replace every occurrence
|
|
||||||
> in **both** `.github/copilot-instructions.md` and `.github/CLAUDE.md`.
|
|
||||||
> 3. After replacing all tokens, **delete this entire notice block** (from `> [!IMPORTANT]`
|
|
||||||
> through the closing `> ---` line) in both files.
|
|
||||||
> 4. Tell the developer: *"I've self-updated the AI context files with this repository's
|
|
||||||
> values — all set."*
|
|
||||||
>
|
|
||||||
> | Placeholder | Where to find the value |
|
|
||||||
> |---|---|
|
|
||||||
> | `MokoOnyx` | The GitHub repository name (visible in the URL, `README.md` heading, or `git remote -v`) |
|
|
||||||
> | `https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx` | Full GitHub URL, e.g. `https://git.mokoconsulting.tech/MokoConsulting/<repo-name>` |
|
|
||||||
> | `{{EXTENSION_NAME}}` | The `<name>` element in `manifest.xml` at the repository root |
|
|
||||||
> | `{{EXTENSION_TYPE}}` | The `type` attribute of the `<extension>` tag in `manifest.xml` (`component`, `module`, `plugin`, or `template`) |
|
|
||||||
> | `{{EXTENSION_ELEMENT}}` | The `<element>` tag in `manifest.xml`, or the filename prefix (e.g. `com_myextension`, `mod_mymodule`) |
|
|
||||||
>
|
|
||||||
> ---
|
|
||||||
|
|
||||||
# MokoOnyx — GitHub Copilot Custom Instructions
|
|
||||||
|
|
||||||
## What This Repo Is
|
## What This Repo Is
|
||||||
|
|
||||||
This is a **Moko Consulting MokoWaaS** (Joomla) repository governed by [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards). All coding standards, workflows, and policies are defined there and enforced here via bulk sync.
|
This is a **Moko Consulting MokoWaaS** (Joomla) repository governed by [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards). All coding standards, workflows, and policies are defined there and enforced here via bulk sync.
|
||||||
|
|
||||||
Repository URL: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
Repository URL: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||||
Extension name: **{{EXTENSION_NAME}}**
|
Extension name: **MokoOnyx**
|
||||||
Extension type: **{{EXTENSION_TYPE}}** (`{{EXTENSION_ELEMENT}}`)
|
Extension type: **template** (`mokoonyx`)
|
||||||
Platform: **Joomla 4.x / MokoWaaS**
|
Platform: **Joomla 5.x / 6.x / MokoWaaS**
|
||||||
|
Successor to: **MokoCassiopeia** (renamed in v01.00.00)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Primary Language
|
## Primary Language
|
||||||
|
|
||||||
**PHP** (≥ 7.4) is the primary language for this Joomla extension. JavaScript may be used for frontend enhancements. YAML uses 2-space indentation. All other text files use tabs per `.editorconfig`.
|
**PHP** (≥ 8.1) is the primary language for this Joomla template. JavaScript may be used for frontend enhancements. YAML uses 2-space indentation. All other text files use tabs per `.editorconfig`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -77,7 +48,7 @@ Every new file needs a copyright header as its first content.
|
|||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*
|
*
|
||||||
* FILE INFORMATION
|
* FILE INFORMATION
|
||||||
* DEFGROUP: MokoOnyx.{{EXTENSION_TYPE}}
|
* DEFGROUP: MokoOnyx.Template
|
||||||
* INGROUP: MokoOnyx
|
* INGROUP: MokoOnyx
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||||
* PATH: /path/to/file.php
|
* PATH: /path/to/file.php
|
||||||
@@ -115,139 +86,93 @@ BRIEF: One-line description
|
|||||||
|
|
||||||
**`README.md` is the single source of truth for the repository version.**
|
**`README.md` is the single source of truth for the repository version.**
|
||||||
|
|
||||||
- **Bump the patch version on every PR** — increment `XX.YY.ZZ` (e.g. `01.02.03` → `01.02.04`) in `README.md` before opening the PR; the `sync-version-on-merge` workflow propagates it automatically to all badges and `FILE INFORMATION` headers on merge to `main`.
|
- **Patch version is auto-bumped by the release workflow** — `release.yml` reads the current version from `README.md`, increments the patch (`XX.YY.ZZ` → `XX.YY.(ZZ+1)`), updates `README.md`, `templateDetails.xml`, and the matching channel in `updates.xml`, commits, pushes, then builds the ZIP. Manual bumping is no longer required.
|
||||||
- The `VERSION: XX.YY.ZZ` field in `README.md` governs all other version references.
|
- The `VERSION: XX.YY.ZZ` field in `README.md` governs all other version references.
|
||||||
- Version format is zero-padded semver: `XX.YY.ZZ` (e.g. `01.02.03`).
|
- Version format is zero-padded semver: `XX.YY.ZZ` (e.g. `01.00.05`).
|
||||||
- Never hardcode a specific version in document body text — use the badge or FILE INFORMATION header only.
|
- Never hardcode a specific version in document body text — use the badge or FILE INFORMATION header only.
|
||||||
|
|
||||||
### Joomla Version Alignment
|
### Joomla Version Alignment
|
||||||
|
|
||||||
The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `updates.xml`. The `make release` command / release workflow updates all three automatically.
|
The version in `README.md` **must always match** the `<version>` tag in `templateDetails.xml` and the matching channel entry in `updates.xml`. The release workflow updates all three automatically.
|
||||||
|
|
||||||
|
### Multi-Channel updates.xml
|
||||||
|
|
||||||
|
`updates.xml` contains separate `<update>` blocks per stability channel (development, alpha, beta, rc, stable). Each release workflow only modifies its own channel using targeted Python regex replacement — other channels are preserved untouched. Joomla filters by the user's "Minimum Stability" setting.
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<!-- In manifest.xml — must match README.md version -->
|
|
||||||
<version>01.02.04</version>
|
|
||||||
|
|
||||||
<!-- In updates.xml — prepend a new <update> block for every release.
|
|
||||||
Note: the backslash in version="4\.[0-9]+" is a literal backslash character
|
|
||||||
in the XML attribute value. Joomla's update server treats the value as a
|
|
||||||
regular expression, so \. matches a literal dot. -->
|
|
||||||
<updates>
|
<updates>
|
||||||
<update>
|
<!-- 1. DEVELOPMENT --> <update>...<tag>development</tag>...</update>
|
||||||
<name>{{EXTENSION_NAME}}</name>
|
<!-- 2. ALPHA --> <update>...<tag>alpha</tag>...</update>
|
||||||
<version>01.02.04</version>
|
<!-- 3. BETA --> <update>...<tag>beta</tag>...</update>
|
||||||
<downloads>
|
<!-- 4. RC --> <update>...<tag>rc</tag>...</update>
|
||||||
<downloadurl type="full" format="zip">
|
<!-- 5. STABLE --> <update>...<tag>stable</tag>...</update>
|
||||||
https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/01.02.04/{{EXTENSION_ELEMENT}}-01.02.04.zip
|
|
||||||
</downloadurl>
|
|
||||||
</downloads>
|
|
||||||
<targetplatform name="joomla" version="4\.[0-9]+" />
|
|
||||||
</update>
|
|
||||||
<!-- … older entries preserved below … -->
|
|
||||||
</updates>
|
</updates>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Key rules:**
|
||||||
|
- SHA-256 must be raw hex (no `sha256:` prefix)
|
||||||
|
- Version format must be `XX.YY.ZZ`, not tag names like `v01`
|
||||||
|
- Download URLs must point to Gitea (not GitHub) for all pre-release channels
|
||||||
|
- **Always push updates.xml to main** — Joomla sites read from main, not dev
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Joomla Extension Structure
|
## MokoCassiopeia Migration (v01.x only)
|
||||||
|
|
||||||
|
MokoOnyx v01.x includes a migration system for MokoCassiopeia users:
|
||||||
|
|
||||||
|
- **`helper/migrate.php`** — runs on first page load via `index.php` bootstrap
|
||||||
|
- Creates `.migrated` marker file so it only runs once
|
||||||
|
- Copies template style params from MokoCassiopeia → MokoOnyx
|
||||||
|
- Creates matching styles, copies user files, redirects update server
|
||||||
|
- **Joomla 6 does NOT call `<scriptfile>` for templates** — that's why migration runs from `index.php` instead of `script.php`
|
||||||
|
- The migration script will be **removed in v02.00.00** (see ROADMAP.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Template Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
MokoOnyx/
|
MokoOnyx/
|
||||||
├── manifest.xml # Joomla installer manifest (root — required)
|
├── src/
|
||||||
├── updates.xml # Update server manifest (root — required, see below)
|
│ ├── templateDetails.xml # Joomla installer manifest
|
||||||
├── site/ # Frontend (site) code
|
│ ├── script.php # Install/update script (limited in Joomla 6 for templates)
|
||||||
│ ├── controller.php
|
│ ├── index.php # Main template file (includes migration bootstrap)
|
||||||
│ ├── controllers/
|
│ ├── helper/
|
||||||
│ ├── models/
|
│ │ ├── migrate.php # MokoCassiopeia → MokoOnyx migration (v01.x)
|
||||||
│ └── views/
|
│ │ ├── favicon.php # Favicon generator
|
||||||
├── admin/ # Backend (admin) code
|
│ │ └── minify.php # Asset minification
|
||||||
│ ├── controller.php
|
│ ├── language/ # Language INI files (en-GB, en-US)
|
||||||
│ ├── controllers/
|
│ ├── media/ # CSS, JS, images, vendor assets
|
||||||
│ ├── models/
|
│ └── templates/ # Custom CSS palette starters, theme test
|
||||||
│ ├── views/
|
├── updates.xml # Update server manifest (root — required)
|
||||||
│ └── sql/
|
├── .gitea/workflows/ # Gitea Actions workflows
|
||||||
├── language/ # Language INI files
|
├── docs/ # Documentation
|
||||||
├── media/ # CSS, JS, images (deployed to /media/{{EXTENSION_ELEMENT}}/)
|
├── README.md # Version source of truth
|
||||||
├── docs/ # Technical documentation
|
|
||||||
├── tests/ # Test suite
|
|
||||||
├── .github/
|
|
||||||
│ ├── workflows/
|
|
||||||
│ ├── copilot-instructions.md # This file
|
|
||||||
│ └── CLAUDE.md
|
|
||||||
├── README.md # Version source of truth
|
|
||||||
├── CHANGELOG.md
|
├── CHANGELOG.md
|
||||||
├── CONTRIBUTING.md
|
└── LICENSE
|
||||||
├── LICENSE # GPL-3.0-or-later
|
|
||||||
└── Makefile # Build automation
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## updates.xml — Required in Repo Root
|
## Gitea Actions — Token Usage
|
||||||
|
|
||||||
`updates.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
|
Every workflow must use **`secrets.GA_TOKEN`** (the org-level Personal Access Token).
|
||||||
|
|
||||||
The `manifest.xml` must reference it via:
|
|
||||||
```xml
|
|
||||||
<updateservers>
|
|
||||||
<server type="extension" priority="1" name="{{EXTENSION_NAME}}">
|
|
||||||
https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/raw/main/updates.xml
|
|
||||||
</server>
|
|
||||||
</updateservers>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rules:**
|
|
||||||
- Every release must prepend a new `<update>` block at the top of `updates.xml` — old entries must be preserved below.
|
|
||||||
- The `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`.
|
|
||||||
- The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL).
|
|
||||||
- `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## manifest.xml Rules
|
|
||||||
|
|
||||||
- Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`).
|
|
||||||
- `<version>` tag must be kept in sync with `README.md` version and `updates.xml`.
|
|
||||||
- Must include `<updateservers>` block pointing to this repo's `updates.xml`.
|
|
||||||
- Must include `<files folder="site">` and `<administration>` sections.
|
|
||||||
- Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## GitHub Actions — Token Usage
|
|
||||||
|
|
||||||
Every workflow must use **`secrets.GH_TOKEN`** (the org-level Personal Access Token).
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# ✅ Correct
|
# ✅ Correct
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
token: ${{ secrets.GA_TOKEN }}
|
||||||
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# ❌ Wrong — never use these in workflows
|
# ❌ Wrong
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
Note: Workflows are in `.gitea/workflows/` (not `.github/workflows/`).
|
||||||
|
|
||||||
## MokoStandards Reference
|
|
||||||
|
|
||||||
This repository is governed by [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards). Authoritative policies:
|
|
||||||
|
|
||||||
| Document | Purpose |
|
|
||||||
|----------|---------|
|
|
||||||
| [file-header-standards.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type |
|
|
||||||
| [coding-style-guide.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions |
|
|
||||||
| [branching-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow |
|
|
||||||
| [merge-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions |
|
|
||||||
| [changelog-standards.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md |
|
|
||||||
| [joomla-development-guide.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -260,7 +185,7 @@ This repository is governed by [MokoStandards](https://git.mokoconsulting.tech/M
|
|||||||
| PHP variable | `$snake_case` | `$item_id` |
|
| PHP variable | `$snake_case` | `$item_id` |
|
||||||
| PHP constant | `UPPER_SNAKE_CASE` | `MAX_ITEMS` |
|
| PHP constant | `UPPER_SNAKE_CASE` | `MAX_ITEMS` |
|
||||||
| PHP class file | `PascalCase.php` | `ItemModel.php` |
|
| PHP class file | `PascalCase.php` | `ItemModel.php` |
|
||||||
| YAML workflow | `kebab-case.yml` | `ci-joomla.yml` |
|
| YAML workflow | `kebab-case.yml` | `release.yml` |
|
||||||
| Markdown doc | `kebab-case.md` | `installation-guide.md` |
|
| Markdown doc | `kebab-case.md` | `installation-guide.md` |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -286,11 +211,11 @@ Approved prefixes: `dev/` · `rc/` · `version/` · `patch/` · `copilot/` · `d
|
|||||||
| Change type | Documentation to update |
|
| Change type | Documentation to update |
|
||||||
|-------------|------------------------|
|
|-------------|------------------------|
|
||||||
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
|
||||||
| New or changed manifest.xml | Update `updates.xml` version; bump README.md version |
|
| New or changed templateDetails.xml | Release workflow auto-bumps version across README.md, templateDetails.xml, and updates.xml |
|
||||||
| New release | Prepend `<update>` block to `updates.xml`; update CHANGELOG.md; bump README.md version |
|
| New release | Trigger `release.yml` — auto-bumps patch, builds ZIP, updates matching channel in `updates.xml` |
|
||||||
| New or changed workflow | `docs/workflows/<workflow-name>.md` |
|
| New or changed workflow | `docs/workflows/<workflow-name>.md` |
|
||||||
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
|
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
|
||||||
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
|
| **Every release** | **Patch auto-bumped** by `release.yml` — no manual version bump needed |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -300,5 +225,7 @@ Approved prefixes: `dev/` · `rc/` · `version/` · `patch/` · `copilot/` · `d
|
|||||||
- Never skip the FILE INFORMATION block on a new file
|
- Never skip the FILE INFORMATION block on a new file
|
||||||
- Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files
|
- Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files
|
||||||
- Never hardcode version numbers in body text — update `README.md` and let automation propagate
|
- Never hardcode version numbers in body text — update `README.md` and let automation propagate
|
||||||
- Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN`
|
- Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GA_TOKEN`
|
||||||
- Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync
|
- Never let `templateDetails.xml` version, `updates.xml` version, and `README.md` version go out of sync
|
||||||
|
- Always push `updates.xml` to main after updating on dev (Joomla reads from main)
|
||||||
|
- Always update documentation when changing MokoStandards-API or MokoStandards repos
|
||||||
|
|||||||
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@@ -103,6 +103,16 @@ jobs:
|
|||||||
composer install --no-dev --optimize-autoloader --no-interaction
|
composer install --no-dev --optimize-autoloader --no-interaction
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- name: Minify CSS and JS
|
||||||
|
run: |
|
||||||
|
npm ci --ignore-scripts
|
||||||
|
node scripts/minify.js
|
||||||
|
|
||||||
- name: Create package
|
- name: Create package
|
||||||
run: |
|
run: |
|
||||||
mkdir -p build/package
|
mkdir -p build/package
|
||||||
|
|||||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -119,6 +119,14 @@ site/
|
|||||||
*.js.map
|
*.js.map
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Minified assets (generated at release time by scripts/minify.js)
|
||||||
|
# Vendor pre-minified files are excluded from this rule.
|
||||||
|
# ============================================================
|
||||||
|
src/media/css/*.min.css
|
||||||
|
src/media/css/theme/*.min.css
|
||||||
|
src/media/js/*.min.js
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# CI / test artifacts
|
# CI / test artifacts
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|||||||
496
README.md
496
README.md
@@ -9,512 +9,46 @@
|
|||||||
INGROUP: MokoOnyx.Documentation
|
INGROUP: MokoOnyx.Documentation
|
||||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||||
FILE: ./README.md
|
FILE: ./README.md
|
||||||
VERSION: 01.00.00
|
VERSION: 01.00.26
|
||||||
BRIEF: Documentation for MokoOnyx template
|
BRIEF: Documentation for MokoOnyx template
|
||||||
-->
|
-->
|
||||||
|
|
||||||
# MokoOnyx → MokoOnyx
|
# MokoOnyx
|
||||||
|
|
||||||
> **This template is being renamed to MokoOnyx.** Version 01.00.00 is the bridge release that automatically migrates your settings. After updating, MokoOnyx will be your active template and MokoOnyx can be safely uninstalled.
|
> **MokoOnyx** is the successor to MokoCassiopeia. On install, it automatically migrates your settings, content references, and custom files. After installing, MokoCassiopeia can be safely uninstalled.
|
||||||
|
|
||||||
**A Modern, Lightweight Joomla Template Based on Cassiopeia**
|
**A Modern, Lightweight Joomla Template Based on Cassiopeia**
|
||||||
|
|
||||||
[](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/v03)
|
[](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/stable)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
[](https://www.joomla.org)
|
[](https://www.joomla.org)
|
||||||
[](https://www.php.net)
|
[](https://www.php.net)
|
||||||
|
|
||||||
MokoOnyx is a modern, lightweight enhancement layer built on top of Joomla's Cassiopeia template. It adds **Font Awesome 7**, **Bootstrap 5** helpers, an automatic **Table of Contents (TOC)** utility, advanced **Dark Mode** theming, and optional integrations for **Google Tag Manager** and **Google Analytics (GA4)**—all while maintaining minimal core template overrides for maximum upgrade compatibility.
|
MokoOnyx is a modern, lightweight enhancement layer built on top of Joomla's Cassiopeia template. It adds **Font Awesome 7**, **Bootstrap 5** helpers, an automatic **Table of Contents (TOC)** utility, advanced **Dark Mode** theming, and optional integrations for **Google Tag Manager** and **Google Analytics (GA4)** -- all while maintaining minimal core template overrides for maximum upgrade compatibility.
|
||||||
|
|
||||||
---
|
## Features
|
||||||
|
|
||||||
## 📑 Table of Contents
|
|
||||||
|
|
||||||
- [Features](#-features)
|
|
||||||
- [Requirements](#-requirements)
|
|
||||||
- [Installation](#-installation)
|
|
||||||
- [Quick Start](#-quick-start)
|
|
||||||
- [Configuration](#️-configuration)
|
|
||||||
- [Theme System](#-theme-system)
|
|
||||||
- [Development](#-development)
|
|
||||||
- [Documentation](#-documentation)
|
|
||||||
- [Changelog](#-changelog)
|
|
||||||
- [Support](#-support)
|
|
||||||
- [Contributing](#-contributing)
|
|
||||||
- [Included Libraries](#-included-libraries)
|
|
||||||
- [License](#-license)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✨ Features
|
|
||||||
|
|
||||||
### Core Enhancements
|
|
||||||
|
|
||||||
- **Built on Cassiopeia**: Extends Joomla's default template with minimal overrides
|
- **Built on Cassiopeia**: Extends Joomla's default template with minimal overrides
|
||||||
- **Font Awesome 7**: Fully integrated into Joomla's asset manager with 2,000+ icons
|
- **Font Awesome 7**: Fully integrated into Joomla's asset manager with 2,000+ icons
|
||||||
- **Bootstrap 5**: Extended utility classes and responsive grid system
|
- **Bootstrap 5**: Extended utility classes and responsive grid system
|
||||||
- **No Template Overrides**: Clean installation that inherits all Cassiopeia defaults
|
- **Template Overrides**: Includes overrides for all core Joomla modules, Community Builder, and DPCalendar
|
||||||
- **Upgrade-Friendly**: Minimal modifications ensure smooth Joomla updates
|
|
||||||
|
|
||||||
### Advanced Theming
|
|
||||||
|
|
||||||
- **Dark Mode Support**: Built-in light/dark mode toggle with system preference detection
|
- **Dark Mode Support**: Built-in light/dark mode toggle with system preference detection
|
||||||
- **Color Palettes**: Standard, Alternative, and Custom color schemes
|
- **Google Tag Manager / GA4**: Optional analytics integrations
|
||||||
- **Theme Persistence**: User preferences saved via localStorage
|
|
||||||
- **Theme Control Options**: Switch, radio buttons, or hidden controls
|
|
||||||
- **Auto Dark Mode**: Optional automatic dark mode based on time/system settings
|
|
||||||
- **Meta Tags**: Automatic color-scheme and theme-color meta tags
|
|
||||||
|
|
||||||
### Developer Features
|
|
||||||
|
|
||||||
- **Custom Code Injection**: Add custom HTML to `<head>` start/end
|
|
||||||
- **Drawer Sidebars**: Configurable left/right drawer positions with custom icons
|
|
||||||
- **Font Options**: Local and web fonts (Roboto, Fira Sans, Noto Sans)
|
|
||||||
- **Sticky Header**: Optional sticky navigation
|
|
||||||
- **Back to Top**: Floating back-to-top button
|
|
||||||
|
|
||||||
### Analytics & Tracking
|
|
||||||
|
|
||||||
- **Google Tag Manager**: Optional GTM integration with container ID configuration
|
|
||||||
- **Google Analytics**: Optional GA4 integration with measurement ID
|
|
||||||
- **Privacy-Friendly**: All tracking features are optional and easily disabled
|
|
||||||
|
|
||||||
### Content Features
|
|
||||||
|
|
||||||
- **Table of Contents**: Automatic TOC generation for long articles
|
- **Table of Contents**: Automatic TOC generation for long articles
|
||||||
- Placement options: `toc-left` or `toc-right` layouts
|
|
||||||
- Automatic heading extraction and navigation
|
|
||||||
- Responsive sidebar positioning
|
|
||||||
|
|
||||||
---
|
## Requirements
|
||||||
|
|
||||||
## 📋 Requirements
|
- **Joomla**: 5.x or 6.x
|
||||||
|
- **PHP**: 8.1 or higher
|
||||||
|
|
||||||
- **Joomla**: 4.4.x or 5.x
|
## Installation
|
||||||
- **PHP**: 8.0 or higher
|
|
||||||
- **Database**: MySQL 5.7+ / MariaDB 10.2+ / PostgreSQL 11+
|
|
||||||
- **Browser Support**: Modern browsers (Chrome, Firefox, Safari, Edge)
|
|
||||||
|
|
||||||
---
|
Download the latest `mokoonyx-{version}.zip` from [Releases](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases) and install via Joomla's Extension Manager.
|
||||||
|
|
||||||
## 📦 Installation
|
## License
|
||||||
|
|
||||||
**Note**: MokoOnyx is a **standalone Joomla template extension** (not bundled as a package). Install it directly via Joomla's Extension Manager.
|
|
||||||
|
|
||||||
### Via Joomla Extension Manager
|
|
||||||
|
|
||||||
1. Download the latest `mokoonyx-{version}.zip` from [Releases](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases)
|
|
||||||
2. In Joomla admin, navigate to **System → Install → Extensions**
|
|
||||||
3. Upload the ZIP file and click **Upload & Install**
|
|
||||||
4. Navigate to **System → Site Templates**
|
|
||||||
5. Set **MokoOnyx** as your default template
|
|
||||||
|
|
||||||
### Via Git (Development)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx.git
|
|
||||||
cd MokoOnyx
|
|
||||||
```
|
|
||||||
|
|
||||||
See [Development Guide](./docs/JOOMLA_DEVELOPMENT.md) for development setup.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Quick Start
|
|
||||||
|
|
||||||
### 1. Install the Template
|
|
||||||
|
|
||||||
Install `mokoonyx.zip` via Joomla's Extension Manager.
|
|
||||||
|
|
||||||
### 2. Set as Default
|
|
||||||
|
|
||||||
Navigate to **System → Site Templates** and set **MokoOnyx** as default.
|
|
||||||
|
|
||||||
### 3. Configure Template Options
|
|
||||||
|
|
||||||
Go to **System → Site Templates → MokoOnyx** to configure:
|
|
||||||
|
|
||||||
- **Branding**: Upload logo, set site title/description
|
|
||||||
- **Theme**: Configure color schemes and dark mode
|
|
||||||
- **Layout**: Set container type (static/fluid), sticky header
|
|
||||||
- **Analytics**: Add GTM/GA4 tracking codes (optional)
|
|
||||||
- **Custom Code**: Inject custom HTML/CSS/JS
|
|
||||||
|
|
||||||
### 4. Test Dark Mode
|
|
||||||
|
|
||||||
The template includes a dark mode toggle. Test it by:
|
|
||||||
- Using the floating theme toggle button (bottom-right by default)
|
|
||||||
- Checking theme persistence across page loads
|
|
||||||
- Verifying system preference detection
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Once installed and set as the default site template, MokoOnyx works out of the box with Joomla's standard content and module system. Key usage points:
|
|
||||||
|
|
||||||
- **Template Options** — Configure via **System → Site Templates → MokoOnyx** (theme colours, layout, analytics, favicon, drawers)
|
|
||||||
- **Custom Colour Schemes** — Copy `templates/mokoonyx/templates/light.custom.css` or `dark.custom.css` to `media/templates/site/mokoonyx/css/theme/` and select "Custom" in the Theme tab
|
|
||||||
- **Custom CSS/JS** — Create `media/templates/site/mokoonyx/css/user.css` or `js/user.js` for site-specific overrides that survive template updates
|
|
||||||
- **Module Overrides** — The template includes overrides for common Joomla modules with consistent title rendering, Bootstrap 5 styling, and Font Awesome 7 icons
|
|
||||||
- **Dark Mode** — Enabled by default with a floating toggle button; respects system preference and persists via localStorage
|
|
||||||
|
|
||||||
See [Configuration](#️-configuration) below for detailed parameter reference.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⚙️ Configuration
|
|
||||||
|
|
||||||
### Global Parameters
|
|
||||||
|
|
||||||
Access template configuration via **System → Site Templates → MokoOnyx**.
|
|
||||||
|
|
||||||
#### Theme Tab
|
|
||||||
|
|
||||||
**General Settings:**
|
|
||||||
- **Theme Enabled**: Enable/disable theme system
|
|
||||||
- **Theme Control Type**: Switch (Light↔Dark), Radios (Light/Dark/System), or None
|
|
||||||
- **Default Choice**: System, Light, or Dark
|
|
||||||
- **Auto Dark Mode**: Automatic dark mode based on time
|
|
||||||
- **Meta Tags**: Enable color-scheme and theme-color meta tags
|
|
||||||
- **Bridge Bootstrap ARIA**: Sync theme with Bootstrap's data-bs-theme
|
|
||||||
|
|
||||||
**Variables & Palettes:**
|
|
||||||
- **Light Mode Palette**: Standard, Alternative, or Custom
|
|
||||||
- **Dark Mode Palette**: Standard, Alternative, or Custom
|
|
||||||
|
|
||||||
**Typography:**
|
|
||||||
- **Font Scheme**: Local (Roboto) or Web fonts (Fira Sans, Roboto+Noto Sans)
|
|
||||||
|
|
||||||
**Branding & Icons:**
|
|
||||||
- **Brand**: Enable/disable site branding
|
|
||||||
- **Logo File**: Upload custom logo (no default logo included)
|
|
||||||
- **Site Title**: Custom site title
|
|
||||||
- **Site Description**: Tagline/description
|
|
||||||
- **Font Awesome Kit**: Optional FA Pro kit code
|
|
||||||
|
|
||||||
**Header & Navigation:**
|
|
||||||
- **Sticky Header**: Enable fixed header on scroll
|
|
||||||
- **Back to Top**: Enable floating back-to-top button
|
|
||||||
|
|
||||||
**Theme Toggle UI:**
|
|
||||||
- **FAB Enabled**: Enable floating action button toggle
|
|
||||||
- **FAB Position**: Bottom-right, Bottom-left, Top-right, or Top-left
|
|
||||||
|
|
||||||
#### Advanced Tab
|
|
||||||
|
|
||||||
- **Layout**: Static or Fluid container
|
|
||||||
|
|
||||||
#### Google Tab
|
|
||||||
|
|
||||||
- **Google Tag Manager**: Enable and configure GTM container ID
|
|
||||||
- **Google Analytics**: Enable and configure GA4 measurement ID
|
|
||||||
|
|
||||||
#### Custom Code Tab
|
|
||||||
|
|
||||||
- **Custom Head Start**: HTML injected at start of `<head>`
|
|
||||||
- **Custom Head End**: HTML injected at end of `<head>`
|
|
||||||
|
|
||||||
#### Drawers Tab
|
|
||||||
|
|
||||||
- **Left Drawer Icon**: Font Awesome icon class (e.g., `fa-solid fa-chevron-right`)
|
|
||||||
- **Right Drawer Icon**: Font Awesome icon class (e.g., `fa-solid fa-chevron-left`)
|
|
||||||
|
|
||||||
### Custom Theme Palettes
|
|
||||||
|
|
||||||
MokoOnyx supports custom theme schemes:
|
|
||||||
|
|
||||||
1. **Copy template files** from `/templates/` directory:
|
|
||||||
- `light.custom.css` → `media/templates/site/mokoonyx/css/theme/light.custom.css`
|
|
||||||
- `dark.custom.css` → `media/templates/site/mokoonyx/css/theme/dark.custom.css`
|
|
||||||
2. **Customize** the CSS variables to match your brand colors
|
|
||||||
3. **Enable in Joomla**: System → Site Templates → MokoOnyx → Theme tab → Set palette to "Custom"
|
|
||||||
4. **Save** and view your site with custom colors
|
|
||||||
|
|
||||||
**Note:** Custom color files are excluded from version control (`.gitignore`) to prevent fork-specific customizations from being committed.
|
|
||||||
|
|
||||||
**Quick Example:**
|
|
||||||
|
|
||||||
```css
|
|
||||||
:root[data-bs-theme="light"] {
|
|
||||||
--color-primary: #1e40af;
|
|
||||||
--color-link: #2563eb;
|
|
||||||
--color-hover: #1d4ed8;
|
|
||||||
--body-color: #1f2937;
|
|
||||||
--body-bg: #ffffff;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Complete Reference:** See [CSS Variables Documentation](./docs/CSS_VARIABLES.md) for all available variables and detailed usage examples.
|
|
||||||
|
|
||||||
### Table of Contents
|
|
||||||
|
|
||||||
Enable automatic TOC for articles:
|
|
||||||
|
|
||||||
1. Edit an article in Joomla admin
|
|
||||||
2. Navigate to **Options → Layout**
|
|
||||||
3. Select **toc-left** or **toc-right**
|
|
||||||
4. Save the article
|
|
||||||
|
|
||||||
The TOC will automatically generate from article headings (H2, H3, etc.) and appear as a sidebar.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎨 Theme System
|
|
||||||
|
|
||||||
### Dark Mode Features
|
|
||||||
|
|
||||||
- **Automatic Detection**: Respects user's system preferences
|
|
||||||
- **Manual Toggle**: Floating button or radio controls
|
|
||||||
- **Persistence**: Saves preference in localStorage
|
|
||||||
- **Smooth Transitions**: Animated theme switching
|
|
||||||
- **Comprehensive Support**: All components themed for dark mode
|
|
||||||
|
|
||||||
### Theme Control Types
|
|
||||||
|
|
||||||
1. **Switch**: Simple light/dark toggle button
|
|
||||||
2. **Radios**: Three options - Light, Dark, System
|
|
||||||
3. **None**: No visible control (respects system only)
|
|
||||||
|
|
||||||
### Meta Tags
|
|
||||||
|
|
||||||
When enabled, the template adds:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<meta name="color-scheme" content="light dark">
|
|
||||||
<meta name="theme-color" content="#1e3a8a" media="(prefers-color-scheme: dark)">
|
|
||||||
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🛠 Development
|
|
||||||
|
|
||||||
### For Contributors
|
|
||||||
|
|
||||||
**New to the project?** See [Quick Start Guide](./docs/QUICK_START.md) for a 5-minute setup.
|
|
||||||
|
|
||||||
### Development Resources
|
|
||||||
|
|
||||||
- **[Quick Start Guide](./docs/QUICK_START.md)** - Setup and first steps
|
|
||||||
- **[Joomla Development Guide](./docs/JOOMLA_DEVELOPMENT.md)** - Testing, quality checks, deployment
|
|
||||||
- **[Workflow Guide](./docs/WORKFLOW_GUIDE.md)** - Git workflow and branching
|
|
||||||
- **[Contributing Guide](./CONTRIBUTING.md)** - Contribution guidelines
|
|
||||||
- **[Roadmap](./docs/ROADMAP.md)** - Feature roadmap and planning
|
|
||||||
|
|
||||||
### Development Tools
|
|
||||||
|
|
||||||
- **Pre-commit Hooks**: Automatic validation before commits
|
|
||||||
- **PHP CodeSniffer**: Code style validation (Joomla standards)
|
|
||||||
- **PHPStan**: Static analysis for PHP code
|
|
||||||
- **Codeception**: Testing framework
|
|
||||||
|
|
||||||
### Quick Development Setup
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Clone repository
|
|
||||||
git clone https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx.git
|
|
||||||
cd MokoOnyx
|
|
||||||
|
|
||||||
# Install development dependencies (if using Composer)
|
|
||||||
composer install --dev
|
|
||||||
|
|
||||||
# Run code quality checks
|
|
||||||
make validate # or manual commands
|
|
||||||
```
|
|
||||||
|
|
||||||
### Building Template Package
|
|
||||||
|
|
||||||
See [Joomla Development Guide](./docs/JOOMLA_DEVELOPMENT.md) for packaging instructions.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📚 Documentation
|
|
||||||
|
|
||||||
### User Documentation
|
|
||||||
|
|
||||||
- **[README](./README.md)** - This file (overview and features)
|
|
||||||
- **[CHANGELOG](./CHANGELOG.md)** - Version history and changes
|
|
||||||
- **[Roadmap](./docs/ROADMAP.md)** - Planned features and timeline
|
|
||||||
|
|
||||||
### Developer Documentation
|
|
||||||
|
|
||||||
- **[Quick Start](./docs/QUICK_START.md)** - 5-minute developer setup
|
|
||||||
- **[Development Guide](./docs/JOOMLA_DEVELOPMENT.md)** - Comprehensive development guide
|
|
||||||
- **[Workflow Guide](./docs/WORKFLOW_GUIDE.md)** - Git workflow and processes
|
|
||||||
- **[CSS Variables Reference](./docs/CSS_VARIABLES.md)** - Complete CSS customization guide
|
|
||||||
- **[Documentation Index](./docs/README.md)** - All documentation links
|
|
||||||
|
|
||||||
### Governance
|
|
||||||
|
|
||||||
- **[Contributing](./CONTRIBUTING.md)** - How to contribute
|
|
||||||
- **[Code of Conduct](./CODE_OF_CONDUCT.md)** - Community standards
|
|
||||||
- **[Governance](./GOVERNANCE.md)** - Project governance model
|
|
||||||
- **[Security Policy](./SECURITY.md)** - Security reporting and procedures
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📖 Changelog
|
|
||||||
|
|
||||||
See the [CHANGELOG.md](./CHANGELOG.md) for detailed version history.
|
|
||||||
|
|
||||||
### Recent Releases
|
|
||||||
|
|
||||||
- **[03.06.03]** (2026-01-30) - README updates and TOC color variable improvements
|
|
||||||
- **[03.06.02]** (2026-01-30) - Complete rebrand to MokoOnyx, removed all overrides
|
|
||||||
- **[03.06.00]** (2026-01-28) - Version standardization
|
|
||||||
- **[03.05.01]** (2026-01-09) - Security and compliance improvements
|
|
||||||
- **[02.00.00]** (2025-08-30) - Dark mode toggle and improved theming
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💬 Support
|
|
||||||
|
|
||||||
### Getting Help
|
|
||||||
|
|
||||||
- **Documentation**: Check this README and [docs folder](./docs/)
|
|
||||||
- **Issues**: Report bugs via [GitHub Issues](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/issues)
|
|
||||||
- **Discussions**: Ask questions in [GitHub Discussions](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/discussions)
|
|
||||||
- **Roadmap**: View planned features in [Roadmap](https://mokoconsulting.tech/support/joomla-cms/mokoonyx-roadmap)
|
|
||||||
|
|
||||||
### Reporting Bugs
|
|
||||||
|
|
||||||
Please include:
|
|
||||||
- Joomla version
|
|
||||||
- PHP version
|
|
||||||
- Template version
|
|
||||||
- Steps to reproduce
|
|
||||||
- Expected vs actual behavior
|
|
||||||
- Screenshots (if applicable)
|
|
||||||
|
|
||||||
### Security Issues
|
|
||||||
|
|
||||||
**Do not** report security vulnerabilities via public issues. See [SECURITY.md](./SECURITY.md) for reporting procedures.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🤝 Contributing
|
|
||||||
|
|
||||||
We welcome contributions! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.
|
|
||||||
|
|
||||||
### How to Contribute
|
|
||||||
|
|
||||||
1. Fork the repository
|
|
||||||
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
|
||||||
3. Make your changes
|
|
||||||
4. Run quality checks
|
|
||||||
5. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
||||||
6. Push to the branch (`git push origin feature/amazing-feature`)
|
|
||||||
7. Open a Pull Request
|
|
||||||
|
|
||||||
### Development Workflow
|
|
||||||
|
|
||||||
See [Workflow Guide](./docs/WORKFLOW_GUIDE.md) for detailed Git workflow.
|
|
||||||
|
|
||||||
### Customizations
|
|
||||||
|
|
||||||
For template customizations, use Joomla's built-in template settings (System → Site Templates → MokoOnyx → Custom Code tab) for HTML/CSS/JS customizations.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📦 Included Libraries
|
|
||||||
|
|
||||||
MokoOnyx includes the following third-party libraries to provide enhanced functionality:
|
|
||||||
|
|
||||||
### Bootstrap TOC
|
|
||||||
|
|
||||||
- **Version**: 1.0.1
|
|
||||||
- **Author**: Aidan Feldman
|
|
||||||
- **License**: MIT License
|
|
||||||
- **Source**: [GitHub Repository](https://github.com/afeld/bootstrap-toc)
|
|
||||||
- **Release**: [v1.0.1 Release](https://github.com/afeld/bootstrap-toc/releases/tag/v1.0.1)
|
|
||||||
- **Purpose**: Automatically generates a table of contents from article headings with scrollspy support
|
|
||||||
- **Location**: `src/media/vendor/bootstrap-toc/`
|
|
||||||
- **Integration**: Registered in `joomla.asset.json` as `vendor.bootstrap-toc` (CSS) and `vendor.bootstrap-toc.js` (JavaScript)
|
|
||||||
- **Usage**: Activated when using `toc-left` or `toc-right` article layouts
|
|
||||||
- **Features**:
|
|
||||||
- Automatic TOC generation from H1-H6 headings
|
|
||||||
- Hierarchical nested navigation
|
|
||||||
- Active state highlighting with scrollspy
|
|
||||||
- Responsive design (collapses on mobile)
|
|
||||||
- Smooth scrolling to sections
|
|
||||||
- Automatic unique ID generation for headings
|
|
||||||
- **Customizations**: CSS adapted to use Cassiopeia CSS variables for theme compatibility
|
|
||||||
|
|
||||||
### Font Awesome 7 Free
|
|
||||||
|
|
||||||
- **Version**: 7.0 (Free)
|
|
||||||
- **License**: Font Awesome Free License
|
|
||||||
- **Source**: [Font Awesome](https://fontawesome.com)
|
|
||||||
- **Purpose**: Provides 2,000+ vector icons for interface elements
|
|
||||||
- **Location**: `src/media/vendor/fa7free/`
|
|
||||||
- **Integration**: Fully integrated into Joomla's asset manager
|
|
||||||
- **Styles Available**: Solid, Regular, Brands
|
|
||||||
|
|
||||||
### Bootstrap 5
|
|
||||||
|
|
||||||
- **Version**: 5.x (via Joomla)
|
|
||||||
- **License**: MIT License
|
|
||||||
- **Source**: [Bootstrap](https://getbootstrap.com)
|
|
||||||
- **Purpose**: Provides responsive grid system and utility classes
|
|
||||||
- **Integration**: Inherited from Joomla's Cassiopeia template, extended with additional helpers
|
|
||||||
- **Components Used**: Grid, utilities, modal, dropdown, collapse, offcanvas, tooltip, popover, scrollspy
|
|
||||||
|
|
||||||
### Integration Method
|
|
||||||
|
|
||||||
All third-party libraries are:
|
|
||||||
- ✅ Properly licensed and attributed
|
|
||||||
- ✅ Registered in Joomla's Web Asset Manager (`joomla.asset.json`)
|
|
||||||
- ✅ Loaded on-demand to optimize performance
|
|
||||||
- ✅ Versioned and documented for maintenance
|
|
||||||
- ✅ Compatible with Joomla 4.4.x and 5.x
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📄 License
|
|
||||||
|
|
||||||
This project is licensed under the **GNU General Public License v3.0** - see the [LICENSE](./LICENSE) file for details.
|
This project is licensed under the **GNU General Public License v3.0** - see the [LICENSE](./LICENSE) file for details.
|
||||||
|
|
||||||
### Third-Party Licenses
|
|
||||||
|
|
||||||
- **Joomla! CMS**: GPL-2.0-or-later
|
|
||||||
- **Cassiopeia Template**: GPL-2.0-or-later (Joomla Project)
|
|
||||||
- **Font Awesome 7 Free**: Font Awesome Free License
|
|
||||||
- **Bootstrap 5**: MIT License
|
|
||||||
- **Bootstrap TOC**: MIT License (A. Feld)
|
|
||||||
|
|
||||||
All third-party libraries and assets remain the property of their respective authors and are credited in source files.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔗 Links
|
**Made with love by [Moko Consulting](https://mokoconsulting.tech)**
|
||||||
|
|
||||||
- **Repository**: [GitHub](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx)
|
|
||||||
- **Issue Tracker**: [GitHub Issues](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/issues)
|
|
||||||
- **Discussions**: [GitHub Discussions](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/discussions)
|
|
||||||
- **Roadmap**: [Full Roadmap](https://mokoconsulting.tech/support/joomla-cms/mokoonyx-roadmap)
|
|
||||||
- **Moko Consulting**: [Website](https://mokoconsulting.tech)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Metadata
|
|
||||||
|
|
||||||
- **Maintainer**: Moko Consulting Engineering
|
|
||||||
- **Author**: Jonathan Miller (@jmiller-moko)
|
|
||||||
- **Repository**: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
|
||||||
- **License**: GPL-3.0-or-later
|
|
||||||
- **Classification**: Public Open Source Standards
|
|
||||||
|
|
||||||
## 📝 Revision History
|
|
||||||
|
|
||||||
| Date | Version | Change Summary | Author |
|
|
||||||
| ---------- | -------- | ------------------------------------------------------------------------- | ------------------------------- |
|
|
||||||
| 2026-01-30 | 03.06.03 | Updated README title, fixed custom color variables instructions, improved TOC color scheme integration | Copilot Agent |
|
|
||||||
| 2026-01-30 | 03.06.02 | Regenerated README with comprehensive documentation and updated structure | Copilot Agent |
|
|
||||||
| 2026-01-30 | 03.06.02 | Complete rebrand to MokoOnyx, removed overrides | Copilot Agent |
|
|
||||||
| 2026-01-05 | 03.00.00 | Initial publication of template documentation and feature overview | Moko Consulting |
|
|
||||||
| 2026-01-05 | 03.00.00 | Fixed malformed markdown table formatting in revision history | Jonathan Miller (@jmiller-moko) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Made with ❤️ by [Moko Consulting](https://mokoconsulting.tech)**
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
DEFGROUP: Joomla.Template.Site
|
DEFGROUP: Joomla.Template.Site
|
||||||
INGROUP: MokoOnyx.Documentation
|
INGROUP: MokoOnyx.Documentation
|
||||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-cassiopeia
|
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||||
FILE: docs/ROADMAP.md
|
FILE: docs/ROADMAP.md
|
||||||
VERSION: 03.09.03
|
VERSION: 03.09.03
|
||||||
BRIEF: Version-specific roadmap for MokoOnyx template
|
BRIEF: Version-specific roadmap for MokoOnyx template
|
||||||
@@ -144,6 +144,25 @@ This document provides a comprehensive, version-specific roadmap for the MokoOny
|
|||||||
|
|
||||||
The following versions represent our planned annual major releases, each building upon the previous version's foundation.
|
The following versions represent our planned annual major releases, each building upon the previous version's foundation.
|
||||||
|
|
||||||
|
#### v02.00.00 (Q3 2026) - Clean Slate
|
||||||
|
**Status**: Planned
|
||||||
|
**Target Release**: Q3 2026
|
||||||
|
|
||||||
|
**Breaking Changes**:
|
||||||
|
- **Remove MokoCassiopeia migration script** — the `helper/migrate.php` bootstrap in `index.php` and the `.migrated` marker file will be removed. All MokoCassiopeia users must migrate before upgrading to v02.
|
||||||
|
- **Remove MokoCassiopeia references** — all `str_replace(mokocassiopeia, mokoonyx)` logic, old name constants, and legacy compatibility code will be cleaned out.
|
||||||
|
- **Remove `script.php` bridge logic** — the `postflight()` migration code for MokoCassiopeia will be removed.
|
||||||
|
|
||||||
|
**New Features**:
|
||||||
|
- Clean codebase with no migration overhead
|
||||||
|
- Performance improvements from removing first-load migration check
|
||||||
|
- Fresh start for MokoOnyx-native development
|
||||||
|
|
||||||
|
**Migration Notice**:
|
||||||
|
Users still running MokoCassiopeia must install MokoOnyx v01.x first to migrate their settings before upgrading to v02. The v01.x line will remain available for download.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
#### v04.00.00 (Q4 2027) - Enhanced Accessibility & Performance
|
#### v04.00.00 (Q4 2027) - Enhanced Accessibility & Performance
|
||||||
**Status**: Planned
|
**Status**: Planned
|
||||||
**Target Release**: December 2027
|
**Target Release**: December 2027
|
||||||
@@ -860,8 +879,8 @@ MokoOnyx aims to be the **most developer-friendly, user-customizable, and standa
|
|||||||
|
|
||||||
### Official Links
|
### Official Links
|
||||||
- **Full Roadmap**: [https://mokoconsulting.tech/support/joomla-cms/mokoonyx-roadmap](https://mokoconsulting.tech/support/joomla-cms/mokoonyx-roadmap)
|
- **Full Roadmap**: [https://mokoconsulting.tech/support/joomla-cms/mokoonyx-roadmap](https://mokoconsulting.tech/support/joomla-cms/mokoonyx-roadmap)
|
||||||
- **Repository**: [https://git.mokoconsulting.tech/MokoConsulting/moko-cassiopeia](https://git.mokoconsulting.tech/MokoConsulting/moko-cassiopeia)
|
- **Repository**: [https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx)
|
||||||
- **Issue Tracker**: [GitHub Issues](https://git.mokoconsulting.tech/MokoConsulting/moko-cassiopeia/issues)
|
- **Issue Tracker**: [GitHub Issues](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/issues)
|
||||||
- **Changelog**: [CHANGELOG.md](../CHANGELOG.md)
|
- **Changelog**: [CHANGELOG.md](../CHANGELOG.md)
|
||||||
|
|
||||||
### Community
|
### Community
|
||||||
@@ -882,7 +901,7 @@ MokoOnyx aims to be the **most developer-friendly, user-customizable, and standa
|
|||||||
Have ideas for future features? We welcome community input!
|
Have ideas for future features? We welcome community input!
|
||||||
|
|
||||||
**How to Suggest Features**:
|
**How to Suggest Features**:
|
||||||
1. Check the [GitHub Issues](https://git.mokoconsulting.tech/MokoConsulting/moko-cassiopeia/issues) for existing requests
|
1. Check the [GitHub Issues](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/issues) for existing requests
|
||||||
2. Open a new issue with the `enhancement` label
|
2. Open a new issue with the `enhancement` label
|
||||||
3. Provide clear use cases and benefits
|
3. Provide clear use cases and benefits
|
||||||
4. Engage in community discussion
|
4. Engage in community discussion
|
||||||
|
|||||||
144
package-lock.json
generated
Normal file
144
package-lock.json
generated
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
{
|
||||||
|
"name": "mokoonyx-build",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "mokoonyx-build",
|
||||||
|
"devDependencies": {
|
||||||
|
"clean-css": "^5.3.3",
|
||||||
|
"terser": "^5.39.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
|
"version": "0.3.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||||
|
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.24"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@jridgewell/resolve-uri": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@jridgewell/source-map": {
|
||||||
|
"version": "0.3.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
|
||||||
|
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/gen-mapping": "^0.3.5",
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.25"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@jridgewell/sourcemap-codec": {
|
||||||
|
"version": "1.5.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||||
|
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
|
"version": "0.3.31",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||||
|
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/resolve-uri": "^3.1.0",
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/acorn": {
|
||||||
|
"version": "8.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
|
||||||
|
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"acorn": "bin/acorn"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/buffer-from": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/clean-css": {
|
||||||
|
"version": "5.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz",
|
||||||
|
"integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"source-map": "~0.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/commander": {
|
||||||
|
"version": "2.20.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||||
|
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/source-map": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/source-map-support": {
|
||||||
|
"version": "0.5.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||||
|
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"buffer-from": "^1.0.0",
|
||||||
|
"source-map": "^0.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/terser": {
|
||||||
|
"version": "5.46.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/terser/-/terser-5.46.2.tgz",
|
||||||
|
"integrity": "sha512-uxfo9fPcSgLDYob/w1FuL0c99MWiJDnv+5qXSQc5+Ki5NjVNsYi66INnMFBjf6uFz6OnX12piJQPF4IpjJTNTw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/source-map": "^0.3.3",
|
||||||
|
"acorn": "^8.15.0",
|
||||||
|
"commander": "^2.20.0",
|
||||||
|
"source-map-support": "~0.5.20"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"terser": "bin/terser"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
87
scripts/minify.js
Normal file
87
scripts/minify.js
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/* 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: MokoOnyx.Build
|
||||||
|
* INGROUP: MokoOnyx
|
||||||
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||||
|
* PATH: /scripts/minify.js
|
||||||
|
* VERSION: 01.00.00
|
||||||
|
* BRIEF: Minify project CSS and JS assets (excludes vendor/)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const CleanCSS = require('clean-css');
|
||||||
|
const { minify: terserMinify } = require('terser');
|
||||||
|
|
||||||
|
const SRC = path.resolve(__dirname, '..', 'src', 'media');
|
||||||
|
|
||||||
|
// Project-owned files only — vendor assets ship pre-minified
|
||||||
|
const CSS_FILES = [
|
||||||
|
'css/editor.css',
|
||||||
|
'css/template.css',
|
||||||
|
'css/theme/dark.standard.css',
|
||||||
|
'css/theme/light.standard.css',
|
||||||
|
];
|
||||||
|
|
||||||
|
const JS_FILES = [
|
||||||
|
'js/gtm.js',
|
||||||
|
'js/template.js',
|
||||||
|
];
|
||||||
|
|
||||||
|
async function minifyCSS(relPath) {
|
||||||
|
const src = path.join(SRC, relPath);
|
||||||
|
const dest = src.replace(/\.css$/, '.min.css');
|
||||||
|
const input = fs.readFileSync(src, 'utf8');
|
||||||
|
const result = new CleanCSS({ level: 1 }).minify(input);
|
||||||
|
|
||||||
|
if (result.errors.length) {
|
||||||
|
console.error(` FAIL ${relPath}:`, result.errors);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(dest, result.styles);
|
||||||
|
const ratio = ((1 - result.styles.length / input.length) * 100).toFixed(0);
|
||||||
|
console.log(` ${relPath} → .min.css (${ratio}% smaller)`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function minifyJS(relPath) {
|
||||||
|
const src = path.join(SRC, relPath);
|
||||||
|
const dest = src.replace(/\.js$/, '.min.js');
|
||||||
|
const input = fs.readFileSync(src, 'utf8');
|
||||||
|
const result = await terserMinify(input, { compress: true, mangle: true });
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
console.error(` FAIL ${relPath}:`, result.error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(dest, result.code);
|
||||||
|
const ratio = ((1 - result.code.length / input.length) * 100).toFixed(0);
|
||||||
|
console.log(` ${relPath} → .min.js (${ratio}% smaller)`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('Minifying project assets...\n');
|
||||||
|
|
||||||
|
let ok = true;
|
||||||
|
|
||||||
|
for (const f of CSS_FILES) {
|
||||||
|
if (!await minifyCSS(f)) ok = false;
|
||||||
|
}
|
||||||
|
for (const f of JS_FILES) {
|
||||||
|
if (!await minifyJS(f)) ok = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(ok ? '\nDone.' : '\nCompleted with errors.');
|
||||||
|
process.exit(ok ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -48,7 +48,8 @@ class MokoFaviconHelper
|
|||||||
$sourceTime = filemtime($sourcePath);
|
$sourceTime = filemtime($sourcePath);
|
||||||
$stampFile = $outputDir . '/.favicon_generated';
|
$stampFile = $outputDir . '/.favicon_generated';
|
||||||
|
|
||||||
if (is_file($stampFile) && filemtime($stampFile) >= $sourceTime) {
|
$manifestFile = $outputDir . '/site.webmanifest';
|
||||||
|
if (is_file($stampFile) && filemtime($stampFile) >= $sourceTime && is_file($manifestFile)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
297
src/helper/migrate.php
Normal file
297
src/helper/migrate.php
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* One-time migration from MokoCassiopeia → MokoOnyx.
|
||||||
|
* Called from index.php on first page load. Creates a .migrated
|
||||||
|
* marker file so it only runs once.
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined('_JEXEC') or die;
|
||||||
|
|
||||||
|
use Joomla\CMS\Factory;
|
||||||
|
use Joomla\CMS\Log\Log;
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
$markerFile = __DIR__ . '/../.migrated';
|
||||||
|
|
||||||
|
// Already migrated
|
||||||
|
if (file_exists($markerFile)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = Factory::getDbo();
|
||||||
|
$app = Factory::getApplication();
|
||||||
|
|
||||||
|
$oldName = 'mokocassiopeia';
|
||||||
|
$newName = 'mokoonyx';
|
||||||
|
$oldDisplay = 'MokoCassiopeia';
|
||||||
|
$newDisplay = 'MokoOnyx';
|
||||||
|
|
||||||
|
// Init logger
|
||||||
|
Log::addLogger(
|
||||||
|
['text_file' => 'mokoonyx_migrate.log.php'],
|
||||||
|
Log::ALL,
|
||||||
|
['mokoonyx_migrate']
|
||||||
|
);
|
||||||
|
|
||||||
|
$log = function (string $msg, int $level = Log::INFO) {
|
||||||
|
Log::add($msg, $level, 'mokoonyx_migrate');
|
||||||
|
};
|
||||||
|
|
||||||
|
$log('=== MokoOnyx migration started (index.php bootstrap) ===');
|
||||||
|
|
||||||
|
// Check if MokoCassiopeia has styles to migrate
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->select('*')
|
||||||
|
->from('#__template_styles')
|
||||||
|
->where($db->quoteName('template') . ' = ' . $db->quote($oldName))
|
||||||
|
->where($db->quoteName('client_id') . ' = 0');
|
||||||
|
$oldStyles = $db->setQuery($query)->loadObjectList();
|
||||||
|
|
||||||
|
if (empty($oldStyles)) {
|
||||||
|
$log('No MokoCassiopeia styles found — fresh install, nothing to migrate.');
|
||||||
|
@file_put_contents($markerFile, date('Y-m-d H:i:s') . ' fresh install');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$log('Found ' . count($oldStyles) . ' MokoCassiopeia style(s) to migrate.');
|
||||||
|
|
||||||
|
// Get the default MokoOnyx style (created by Joomla installer)
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->select('id')
|
||||||
|
->from('#__template_styles')
|
||||||
|
->where($db->quoteName('template') . ' = ' . $db->quote($newName))
|
||||||
|
->where($db->quoteName('client_id') . ' = 0')
|
||||||
|
->order($db->quoteName('id') . ' ASC');
|
||||||
|
$defaultOnyxId = (int) $db->setQuery($query, 0, 1)->loadResult();
|
||||||
|
|
||||||
|
$isFirst = true;
|
||||||
|
|
||||||
|
foreach ($oldStyles as $old) {
|
||||||
|
$newTitle = str_replace($oldDisplay, $newDisplay, $old->title);
|
||||||
|
$newTitle = str_replace($oldName, $newName, $newTitle);
|
||||||
|
$newParams = is_string($old->params)
|
||||||
|
? str_replace($oldName, $newName, $old->params)
|
||||||
|
: $old->params;
|
||||||
|
|
||||||
|
if ($isFirst && $defaultOnyxId) {
|
||||||
|
// Apply params to the installer-created default MokoOnyx style
|
||||||
|
$update = $db->getQuery(true)
|
||||||
|
->update('#__template_styles')
|
||||||
|
->set($db->quoteName('title') . ' = ' . $db->quote($newTitle))
|
||||||
|
->set($db->quoteName('params') . ' = ' . $db->quote($newParams))
|
||||||
|
->where('id = ' . $defaultOnyxId);
|
||||||
|
$db->setQuery($update)->execute();
|
||||||
|
|
||||||
|
// Set as default if MokoCassiopeia was default
|
||||||
|
if ($old->home == 1) {
|
||||||
|
$db->setQuery(
|
||||||
|
$db->getQuery(true)
|
||||||
|
->update('#__template_styles')
|
||||||
|
->set($db->quoteName('home') . ' = 1')
|
||||||
|
->where('id = ' . $defaultOnyxId)
|
||||||
|
)->execute();
|
||||||
|
|
||||||
|
$db->setQuery(
|
||||||
|
$db->getQuery(true)
|
||||||
|
->update('#__template_styles')
|
||||||
|
->set($db->quoteName('home') . ' = 0')
|
||||||
|
->where('id = ' . (int) $old->id)
|
||||||
|
)->execute();
|
||||||
|
|
||||||
|
$log('Set MokoOnyx as default site template.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$log("Applied params to default MokoOnyx style: {$newTitle}");
|
||||||
|
$isFirst = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional styles: check if already exists
|
||||||
|
$check = $db->getQuery(true)
|
||||||
|
->select('COUNT(*)')
|
||||||
|
->from('#__template_styles')
|
||||||
|
->where($db->quoteName('template') . ' = ' . $db->quote($newName))
|
||||||
|
->where($db->quoteName('title') . ' = ' . $db->quote($newTitle));
|
||||||
|
if ((int) $db->setQuery($check)->loadResult() > 0) {
|
||||||
|
$log("Style '{$newTitle}' already exists — skipping.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new MokoOnyx style copy
|
||||||
|
$new = clone $old;
|
||||||
|
unset($new->id);
|
||||||
|
$new->template = $newName;
|
||||||
|
$new->title = $newTitle;
|
||||||
|
$new->params = $newParams;
|
||||||
|
$new->home = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db->insertObject('#__template_styles', $new, 'id');
|
||||||
|
$log("Created MokoOnyx style: {$newTitle}");
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$log("Failed to create style '{$newTitle}': " . $e->getMessage(), Log::WARNING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy user files from MokoCassiopeia media
|
||||||
|
$oldMedia = JPATH_ROOT . '/media/templates/site/' . $oldName;
|
||||||
|
$newMedia = JPATH_ROOT . '/media/templates/site/' . $newName;
|
||||||
|
|
||||||
|
if (is_dir($oldMedia) && is_dir($newMedia)) {
|
||||||
|
$userFiles = [
|
||||||
|
'css/theme/light.custom.css',
|
||||||
|
'css/theme/dark.custom.css',
|
||||||
|
'css/theme/light.custom.min.css',
|
||||||
|
'css/theme/dark.custom.min.css',
|
||||||
|
'css/user.css',
|
||||||
|
'css/user.min.css',
|
||||||
|
'js/user.js',
|
||||||
|
'js/user.min.js',
|
||||||
|
];
|
||||||
|
|
||||||
|
$copied = 0;
|
||||||
|
foreach ($userFiles as $rel) {
|
||||||
|
$src = $oldMedia . '/' . $rel;
|
||||||
|
$dst = $newMedia . '/' . $rel;
|
||||||
|
if (is_file($src) && !is_file($dst)) {
|
||||||
|
$dir = dirname($dst);
|
||||||
|
if (!is_dir($dir)) {
|
||||||
|
mkdir($dir, 0755, true);
|
||||||
|
}
|
||||||
|
copy($src, $dst);
|
||||||
|
$copied++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($copied > 0) {
|
||||||
|
$log("Copied {$copied} user file(s) from MokoCassiopeia.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace MokoCassiopeia references in article content
|
||||||
|
try {
|
||||||
|
$contentTables = [
|
||||||
|
['table' => '#__content', 'columns' => ['introtext', 'fulltext']],
|
||||||
|
];
|
||||||
|
|
||||||
|
$totalReplaced = 0;
|
||||||
|
|
||||||
|
foreach ($contentTables as $spec) {
|
||||||
|
foreach ($spec['columns'] as $col) {
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->update($spec['table'])
|
||||||
|
->set(
|
||||||
|
$db->quoteName($col) . ' = REPLACE(REPLACE('
|
||||||
|
. $db->quoteName($col) . ', '
|
||||||
|
. $db->quote($oldDisplay) . ', '
|
||||||
|
. $db->quote($newDisplay) . '), '
|
||||||
|
. $db->quote($oldName) . ', '
|
||||||
|
. $db->quote($newName) . ')'
|
||||||
|
)
|
||||||
|
->where(
|
||||||
|
'(' . $db->quoteName($col) . ' LIKE ' . $db->quote('%' . $oldDisplay . '%')
|
||||||
|
. ' OR ' . $db->quoteName($col) . ' LIKE ' . $db->quote('%' . $oldName . '%') . ')'
|
||||||
|
);
|
||||||
|
$db->setQuery($query)->execute();
|
||||||
|
$totalReplaced += $db->getAffectedRows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($totalReplaced > 0) {
|
||||||
|
$log("Replaced MokoCassiopeia references in {$totalReplaced} content row(s).");
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$log('Content replacement failed: ' . $e->getMessage(), Log::WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace MokoCassiopeia references in custom HTML modules
|
||||||
|
try {
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->update('#__modules')
|
||||||
|
->set(
|
||||||
|
$db->quoteName('content') . ' = REPLACE(REPLACE('
|
||||||
|
. $db->quoteName('content') . ', '
|
||||||
|
. $db->quote($oldDisplay) . ', '
|
||||||
|
. $db->quote($newDisplay) . '), '
|
||||||
|
. $db->quote($oldName) . ', '
|
||||||
|
. $db->quote($newName) . ')'
|
||||||
|
)
|
||||||
|
->where(
|
||||||
|
'(' . $db->quoteName('content') . ' LIKE ' . $db->quote('%' . $oldDisplay . '%')
|
||||||
|
. ' OR ' . $db->quoteName('content') . ' LIKE ' . $db->quote('%' . $oldName . '%') . ')'
|
||||||
|
);
|
||||||
|
$db->setQuery($query)->execute();
|
||||||
|
$modulesUpdated = $db->getAffectedRows();
|
||||||
|
|
||||||
|
if ($modulesUpdated > 0) {
|
||||||
|
$log("Replaced MokoCassiopeia references in {$modulesUpdated} module(s).");
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$log('Module content replacement failed: ' . $e->getMessage(), Log::WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the update server
|
||||||
|
try {
|
||||||
|
$onyxUpdatesUrl = 'https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/raw/branch/main/updates.xml';
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->update('#__update_sites')
|
||||||
|
->set($db->quoteName('location') . ' = ' . $db->quote($onyxUpdatesUrl))
|
||||||
|
->set($db->quoteName('name') . ' = ' . $db->quote($newDisplay))
|
||||||
|
->where($db->quoteName('location') . ' LIKE ' . $db->quote('%MokoCassiopeia%'));
|
||||||
|
$db->setQuery($query)->execute();
|
||||||
|
$n = $db->getAffectedRows();
|
||||||
|
if ($n > 0) {
|
||||||
|
$log("Redirected {$n} update site(s) to MokoOnyx.");
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$log('Update server redirect failed: ' . $e->getMessage(), Log::WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock MokoCassiopeia (allow uninstall) + lock MokoOnyx (prevent accidental uninstall)
|
||||||
|
try {
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->update('#__extensions')
|
||||||
|
->set($db->quoteName('locked') . ' = 0')
|
||||||
|
->set($db->quoteName('protected') . ' = 0')
|
||||||
|
->where($db->quoteName('element') . ' = ' . $db->quote($oldName))
|
||||||
|
->where($db->quoteName('type') . ' = ' . $db->quote('template'));
|
||||||
|
$db->setQuery($query)->execute();
|
||||||
|
if ($db->getAffectedRows() > 0) {
|
||||||
|
$log('Unlocked MokoCassiopeia (can be uninstalled).');
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$log('Failed to unlock MokoCassiopeia: ' . $e->getMessage(), Log::WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->update('#__extensions')
|
||||||
|
->set($db->quoteName('locked') . ' = 1')
|
||||||
|
->where($db->quoteName('element') . ' = ' . $db->quote($newName))
|
||||||
|
->where($db->quoteName('type') . ' = ' . $db->quote('template'));
|
||||||
|
$db->setQuery($query)->execute();
|
||||||
|
if ($db->getAffectedRows() > 0) {
|
||||||
|
$log('Locked MokoOnyx (protected from uninstall).');
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$log('Failed to lock MokoOnyx: ' . $e->getMessage(), Log::WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write marker file
|
||||||
|
@file_put_contents($markerFile, date('Y-m-d H:i:s') . " migrated {$oldName} → {$newName}");
|
||||||
|
|
||||||
|
$log('=== Migration completed ===');
|
||||||
|
|
||||||
|
// Enqueue message for admin
|
||||||
|
if ($app->isClient('administrator')) {
|
||||||
|
$app->enqueueMessage(
|
||||||
|
'<strong>MokoOnyx has imported your MokoCassiopeia settings.</strong><br>'
|
||||||
|
. 'You can safely uninstall MokoCassiopeia from Extensions → Manage.',
|
||||||
|
'success'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})();
|
||||||
48
src/html/layouts/joomla/module/card.php
Normal file
48
src/html/layouts/joomla/module/card.php
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
*
|
||||||
|
* This file is part of a Moko Consulting project.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* FILE INFORMATION
|
||||||
|
* DEFGROUP: MokoOnyx.Layout
|
||||||
|
* INGROUP: MokoOnyx
|
||||||
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||||
|
* PATH: /html/layouts/joomla/module/card.php
|
||||||
|
* VERSION: 01.00.07
|
||||||
|
* BRIEF: Custom card module chrome — renders module titles for all modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined('_JEXEC') or die;
|
||||||
|
|
||||||
|
use Joomla\CMS\Language\Text;
|
||||||
|
|
||||||
|
$module = $displayData['module'];
|
||||||
|
$params = $displayData['params'];
|
||||||
|
$attribs = $displayData['attribs'];
|
||||||
|
|
||||||
|
$moduleTag = htmlspecialchars($params->get('module_tag', 'div'), ENT_QUOTES, 'UTF-8');
|
||||||
|
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_QUOTES, 'UTF-8');
|
||||||
|
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_QUOTES, 'UTF-8');
|
||||||
|
$bootstrapSize = (int) $params->get('bootstrap_size', 0);
|
||||||
|
$moduleClass = htmlspecialchars($attribs['moduleclass_sfx'] ?? '', ENT_QUOTES, 'UTF-8');
|
||||||
|
|
||||||
|
$moduleId = 'module-' . $module->id;
|
||||||
|
|
||||||
|
if ($module->content === '' && $module->content === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cardClass = 'card' . ($moduleClass ? ' ' . $moduleClass : '');
|
||||||
|
?>
|
||||||
|
<<?php echo $moduleTag; ?> id="<?php echo $moduleId; ?>" class="<?php echo $cardClass; ?>">
|
||||||
|
<?php if ((bool) $module->showtitle) : ?>
|
||||||
|
<div class="card-header">
|
||||||
|
<<?php echo $headerTag; ?> class="card-title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php echo $module->content; ?>
|
||||||
|
</div>
|
||||||
|
</<?php echo $moduleTag; ?>>
|
||||||
1
src/html/layouts/joomla/module/index.html
Normal file
1
src/html/layouts/joomla/module/index.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<!DOCTYPE html><title></title>
|
||||||
194
src/html/mod_cblogin/default.php
Normal file
194
src/html/mod_cblogin/default.php
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Community Builder (TM)
|
||||||
|
* @version $Id: $
|
||||||
|
* @package CommunityBuilder
|
||||||
|
* @copyright (C) 2004-2025 www.joomlapolis.com / Lightning MultiCom SA - and its licensors, all rights reserved
|
||||||
|
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html GNU/GPL version 2
|
||||||
|
*/
|
||||||
|
|
||||||
|
use CBLib\Application\Application;
|
||||||
|
use Joomla\CMS\HTML\HTMLHelper;
|
||||||
|
use Joomla\CMS\Language\Text;
|
||||||
|
|
||||||
|
if ( ! ( defined( '_VALID_CB' ) || defined( '_JEXEC' ) || defined( '_VALID_MOS' ) ) ) { die( 'Direct Access to this location is not allowed.' ); }
|
||||||
|
|
||||||
|
HTMLHelper::_( 'behavior.keepalive' );
|
||||||
|
|
||||||
|
?>
|
||||||
|
<?php echo modCBLoginHelper::getPlugins( $params, $type, 'beforeForm' ); ?>
|
||||||
|
<form action="<?php echo $_CB_framework->viewUrl( 'login', true, null, 'html', $secureForm ); ?>" method="post" id="login-form" class="cbLoginForm">
|
||||||
|
<input type="hidden" name="option" value="com_comprofiler" />
|
||||||
|
<input type="hidden" name="view" value="login" />
|
||||||
|
<input type="hidden" name="op2" value="login" />
|
||||||
|
<input type="hidden" name="return" value="B:<?php echo $loginReturnUrl; ?>" />
|
||||||
|
<input type="hidden" name="message" value="<?php echo (int) $params->get( 'login_message', 0 ); ?>" />
|
||||||
|
<input type="hidden" name="loginfrom" value="<?php echo htmlspecialchars( ( defined( '_UE_LOGIN_FROM' ) ? _UE_LOGIN_FROM : 'loginmodule' ) ); ?>" />
|
||||||
|
<?php echo Application::Session()->getFormTokenInput(); ?>
|
||||||
|
<?php echo modCBLoginHelper::getPlugins( $params, $type, 'start' ); ?>
|
||||||
|
<?php if ( $preLogintText ) { ?>
|
||||||
|
<div class="pretext <?php echo htmlspecialchars( $templateClass ); ?>">
|
||||||
|
<p><?php echo $preLogintText; ?></p>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
<?php echo modCBLoginHelper::getPlugins( $params, $type, 'almostStart' ); ?>
|
||||||
|
<?php if ( $loginMethod != 4 ) { ?>
|
||||||
|
<fieldset class="userdata">
|
||||||
|
<p id="form-login-username">
|
||||||
|
<?php if ( in_array( $showUsernameLabel, array( 1, 2, 3, 5 ) ) ) { ?>
|
||||||
|
<?php if ( in_array( $showUsernameLabel, array( 2, 5 ) ) ) { ?>
|
||||||
|
<span class="<?php echo htmlspecialchars( $templateClass ); ?>">
|
||||||
|
<span class="cbModuleUsernameIcon fa fa-user" title="<?php echo htmlspecialchars( $userNameText ); ?>"></span>
|
||||||
|
</span>
|
||||||
|
<?php } else { ?>
|
||||||
|
<label for="modlgn-username">
|
||||||
|
<?php if ( $showUsernameLabel == 3 ) { ?>
|
||||||
|
<span class="<?php echo htmlspecialchars( $templateClass ); ?>">
|
||||||
|
<span class="cbModuleUsernameIcon fa fa-user" title="<?php echo htmlspecialchars( $userNameText ); ?>"></span>
|
||||||
|
</span>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ( in_array( $showUsernameLabel, array( 1, 3 ) ) ) { ?>
|
||||||
|
<?php echo htmlspecialchars( $userNameText ); ?>
|
||||||
|
<?php } ?>
|
||||||
|
</label>
|
||||||
|
<?php } ?>
|
||||||
|
<?php } ?>
|
||||||
|
<input id="modlgn-username" type="text" name="username" class="<?php echo ( $styleUsername ? htmlspecialchars( $styleUsername ) : 'inputbox' ); ?>" size="<?php echo $usernameInputLength; ?>"<?php echo ( in_array( $showUsernameLabel, array( 4, 5 ) ) ? ' placeholder="' . htmlspecialchars( $userNameText ) . '"' : null ); ?> />
|
||||||
|
</p>
|
||||||
|
<p id="form-login-password">
|
||||||
|
<?php if ( in_array( $showPasswordLabel, array( 1, 2, 3, 5 ) ) ) { ?>
|
||||||
|
<?php if ( in_array( $showPasswordLabel, array( 2, 5 ) ) ) { ?>
|
||||||
|
<span class="<?php echo htmlspecialchars( $templateClass ); ?>">
|
||||||
|
<span class="cbModulePasswordIcon fa fa-lock" title="<?php echo htmlspecialchars( CBTxt::T( 'Password' ) ); ?>"></span>
|
||||||
|
</span>
|
||||||
|
<?php } else { ?>
|
||||||
|
<label for="modlgn-passwd">
|
||||||
|
<?php if ( $showPasswordLabel == 3 ) { ?>
|
||||||
|
<span class="<?php echo htmlspecialchars( $templateClass ); ?>">
|
||||||
|
<span class="cbModulePasswordIcon fa fa-lock" title="<?php echo htmlspecialchars( CBTxt::T( 'Password' ) ); ?>"></span>
|
||||||
|
</span>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ( in_array( $showPasswordLabel, array( 1, 3 ) ) ) { ?>
|
||||||
|
<?php echo htmlspecialchars( CBTxt::T( 'Password' ) ); ?>
|
||||||
|
<?php } ?>
|
||||||
|
</label>
|
||||||
|
<?php } ?>
|
||||||
|
<?php } ?>
|
||||||
|
<input id="modlgn-passwd" type="password" name="passwd" class="<?php echo ( $stylePassword ? htmlspecialchars( $stylePassword ) : 'inputbox' ); ?>" size="<?php echo $passwordInputLength; ?>"<?php echo ( in_array( $showPasswordLabel, array( 4, 5 ) ) ? ' placeholder="' . htmlspecialchars( CBTxt::T( 'Password' ) ) . '"' : null ); ?> />
|
||||||
|
</p>
|
||||||
|
<?php if ( count( $twoFactorMethods ) > 1 ) { ?>
|
||||||
|
<p id="form-login-secretkey">
|
||||||
|
<?php if ( in_array( $showSecretKeyLabel, array( 1, 2, 3, 5 ) ) ) { ?>
|
||||||
|
<?php if ( in_array( $showSecretKeyLabel, array( 2, 5 ) ) ) { ?>
|
||||||
|
<span class="<?php echo htmlspecialchars( $templateClass ); ?>">
|
||||||
|
<span class="cbModuleSecretKeyIcon fa fa-star" title="<?php echo htmlspecialchars( CBTxt::T( 'Secret Key' ) ); ?>"></span>
|
||||||
|
</span>
|
||||||
|
<?php } else { ?>
|
||||||
|
<label for="modlgn-secretkey">
|
||||||
|
<?php if ( $showSecretKeyLabel == 3 ) { ?>
|
||||||
|
<span class="<?php echo htmlspecialchars( $templateClass ); ?>">
|
||||||
|
<span class="cbModuleSecretKeyIcon fa fa-star" title="<?php echo htmlspecialchars( CBTxt::T( 'Secret Key' ) ); ?>"></span>
|
||||||
|
</span>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ( in_array( $showSecretKeyLabel, array( 1, 3 ) ) ) { ?>
|
||||||
|
<?php echo htmlspecialchars( CBTxt::T( 'Secret Key' ) ); ?>
|
||||||
|
<?php } ?>
|
||||||
|
</label>
|
||||||
|
<?php } ?>
|
||||||
|
<?php } ?>
|
||||||
|
<input id="modlgn-secretkey" autocomplete="one-time-code" type="text" name="secretkey" tabindex="0" class="<?php echo ( $styleSecretKey ? htmlspecialchars( $styleSecretKey ) : 'inputbox' ); ?>" size="<?php echo $secretKeyInputLength; ?>"<?php echo ( in_array( $showSecretKeyLabel, array( 4, 5 ) ) ? ' placeholder="' . htmlspecialchars( CBTxt::T( 'Secret Key' ) ) . '"' : null ); ?> />
|
||||||
|
</p>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ( in_array( $showRememberMe, array( 1, 3 ) ) ) { ?>
|
||||||
|
<p id="form-login-remember">
|
||||||
|
<label for="modlgn-remember"><?php echo htmlspecialchars( CBTxt::T( 'Remember Me' ) ); ?></label>
|
||||||
|
<input id="modlgn-remember" type="checkbox" name="remember" class="inputbox" value="yes"<?php echo ( $showRememberMe == 3 ? ' checked="checked"' : null ); ?> />
|
||||||
|
</p>
|
||||||
|
<?php } elseif ( $showRememberMe == 2 ) { ?>
|
||||||
|
<input id="modlgn-remember" type="hidden" name="remember" class="inputbox" value="yes" />
|
||||||
|
<?php } ?>
|
||||||
|
<?php echo modCBLoginHelper::getPlugins( $params, $type, 'beforeButton', 'p' ); ?>
|
||||||
|
<button type="submit" name="Submit" class="<?php echo ( $styleLogin ? htmlspecialchars( $styleLogin ) : 'button' ); ?>"<?php echo $buttonStyle; ?>>
|
||||||
|
<?php if ( in_array( $showButton, array( 1, 2, 3 ) ) ) { ?>
|
||||||
|
<span class="<?php echo htmlspecialchars( $templateClass ); ?>">
|
||||||
|
<span class="cbModuleLoginIcon fa fa-sign-in" title="<?php echo htmlspecialchars( CBTxt::T( 'Log in' ) ); ?>"></span>
|
||||||
|
</span>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ( in_array( $showButton, array( 0, 1, 4 ) ) ) { ?>
|
||||||
|
<?php echo htmlspecialchars( CBTxt::T( 'Log in' ) ); ?>
|
||||||
|
<?php } ?>
|
||||||
|
</button>
|
||||||
|
<?php echo modCBLoginHelper::getPlugins( $params, $type, 'afterButton', 'p' ); ?>
|
||||||
|
</fieldset>
|
||||||
|
<?php } else { ?>
|
||||||
|
<?php echo modCBLoginHelper::getPlugins( $params, $type, 'beforeButton', 'p' ); ?>
|
||||||
|
<?php echo modCBLoginHelper::getPlugins( $params, $type, 'afterButton', 'p' ); ?>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ( $showForgotLogin || $showRegister ) { ?>
|
||||||
|
<ul id="form-login-links">
|
||||||
|
<?php if ( $showForgotLogin ) { ?>
|
||||||
|
<?php if ( ! Application::Config()->getBool( 'forgotlogin_type', true ) ) { ?>
|
||||||
|
<li id="form-login-forgot-password">
|
||||||
|
<a href="<?php echo cbSef( 'index.php?option=com_users&view=reset' ); ?>"<?php echo ( $styleForgotLogin ? ' class="' . htmlspecialchars( $styleForgotLogin ) . '"' : null ); ?>>
|
||||||
|
<?php if ( in_array( $showForgotLogin, array( 2, 3 ) ) ) { ?>
|
||||||
|
<span class="<?php echo htmlspecialchars( $templateClass ); ?>">
|
||||||
|
<span class="cbModuleForgotLoginIcon fa fa-unlock-alt" title="<?php echo htmlspecialchars( Text::_( 'MOD_LOGIN_FORGOT_YOUR_PASSWORD' ) ); ?>"></span>
|
||||||
|
</span>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ( in_array( $showForgotLogin, array( 1, 3 ) ) ) { ?>
|
||||||
|
<?php echo Text::_( 'MOD_LOGIN_FORGOT_YOUR_PASSWORD' ); ?>
|
||||||
|
<?php } ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li id="form-login-forgot-username">
|
||||||
|
<a href="<?php echo cbSef( 'index.php?option=com_users&view=remind' ); ?>"<?php echo ( $styleForgotLogin ? ' class="' . htmlspecialchars( $styleForgotLogin ) . '"' : null ); ?>>
|
||||||
|
<?php if ( in_array( $showForgotLogin, array( 2, 3 ) ) ) { ?>
|
||||||
|
<span class="<?php echo htmlspecialchars( $templateClass ); ?>">
|
||||||
|
<span class="cbModuleForgotLoginIcon fa fa-unlock-alt" title="<?php echo htmlspecialchars( Text::_( 'MOD_LOGIN_FORGOT_YOUR_USERNAME' ) ); ?>"></span>
|
||||||
|
</span>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ( in_array( $showForgotLogin, array( 1, 3 ) ) ) { ?>
|
||||||
|
<?php echo Text::_( 'MOD_LOGIN_FORGOT_YOUR_USERNAME' ); ?>
|
||||||
|
<?php } ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php } else { ?>
|
||||||
|
<li id="form-login-forgot">
|
||||||
|
<a href="<?php echo $_CB_framework->viewUrl( 'lostpassword', true, null, 'html', $secureForm ); ?>"<?php echo ( $styleForgotLogin ? ' class="' . htmlspecialchars( $styleForgotLogin ) . '"' : null ); ?>>
|
||||||
|
<?php if ( in_array( $showForgotLogin, array( 2, 3 ) ) ) { ?>
|
||||||
|
<span class="<?php echo htmlspecialchars( $templateClass ); ?>">
|
||||||
|
<span class="cbModuleForgotLoginIcon fa fa-unlock-alt" title="<?php echo htmlspecialchars( CBTxt::T( 'Forgot Login?' ) ); ?>"></span>
|
||||||
|
</span>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ( in_array( $showForgotLogin, array( 1, 3 ) ) ) { ?>
|
||||||
|
<?php echo CBTxt::T( 'Forgot Login?' ); ?>
|
||||||
|
<?php } ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php } ?>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ( $showRegister ) { ?>
|
||||||
|
<li id="form-login-register">
|
||||||
|
<a href="<?php echo $_CB_framework->viewUrl( 'registers', true, null, 'html', $secureForm ); ?>"<?php echo ( $styleRegister ? ' class="' . htmlspecialchars( $styleRegister ) . '"' : null ); ?>>
|
||||||
|
<?php if ( in_array( $params->get( 'show_newaccount', 1 ), array( 2, 3 ) ) ) { ?>
|
||||||
|
<span class="<?php echo htmlspecialchars( $templateClass ); ?>">
|
||||||
|
<span class="cbModuleRegisterIcon fa fa-edit" title="<?php echo htmlspecialchars( CBTxt::T( 'UE_REGISTER', 'Sign up' ) ); ?>"></span>
|
||||||
|
</span>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ( in_array( $params->get( 'show_newaccount', 1 ), array( 1, 3 ) ) ) { ?>
|
||||||
|
<?php echo CBTxt::T( 'UE_REGISTER', 'Sign up' ); ?>
|
||||||
|
<?php } ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php } ?>
|
||||||
|
</ul>
|
||||||
|
<?php } ?>
|
||||||
|
<?php echo modCBLoginHelper::getPlugins( $params, $type, 'almostEnd' ); ?>
|
||||||
|
<?php if ( $postLoginText ) { ?>
|
||||||
|
<div class="posttext <?php echo htmlspecialchars( $templateClass ); ?>">
|
||||||
|
<p><?php echo $postLoginText; ?></p>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
<?php echo modCBLoginHelper::getPlugins( $params, $type, 'end' ); ?>
|
||||||
|
</form>
|
||||||
|
<?php echo modCBLoginHelper::getPlugins( $params, $type, 'afterForm' ); ?>
|
||||||
127
src/html/mod_cblogin/default_logout.php
Normal file
127
src/html/mod_cblogin/default_logout.php
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Community Builder (TM)
|
||||||
|
* @version $Id: $
|
||||||
|
* @package CommunityBuilder
|
||||||
|
* @copyright (C) 2004-2025 www.joomlapolis.com / Lightning MultiCom SA - and its licensors, all rights reserved
|
||||||
|
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html GNU/GPL version 2
|
||||||
|
*/
|
||||||
|
|
||||||
|
use CBLib\Application\Application;
|
||||||
|
use Joomla\CMS\HTML\HTMLHelper;
|
||||||
|
|
||||||
|
if ( ! ( defined( '_VALID_CB' ) || defined( '_JEXEC' ) || defined( '_VALID_MOS' ) ) ) { die( 'Direct Access to this location is not allowed.' ); }
|
||||||
|
|
||||||
|
HTMLHelper::_( 'behavior.keepalive' );
|
||||||
|
|
||||||
|
?>
|
||||||
|
<?php echo modCBLoginHelper::getPlugins( $params, $type, 'beforeForm' ); ?>
|
||||||
|
<form action="<?php echo $_CB_framework->viewUrl( 'logout', true, null, 'html', $secureForm ); ?>" method="post" id="login-form" class="cbLogoutForm">
|
||||||
|
<input type="hidden" name="option" value="com_comprofiler" />
|
||||||
|
<input type="hidden" name="view" value="logout" />
|
||||||
|
<input type="hidden" name="op2" value="logout" />
|
||||||
|
<input type="hidden" name="return" value="B:<?php echo $logoutReturnUrl; ?>" />
|
||||||
|
<input type="hidden" name="message" value="<?php echo (int) $params->get( 'logout_message', 0 ); ?>" />
|
||||||
|
<?php echo Application::Session()->getFormTokenInput(); ?>
|
||||||
|
<?php echo modCBLoginHelper::getPlugins( $params, $type, 'start' ); ?>
|
||||||
|
<?php if ( $preLogoutText ) { ?>
|
||||||
|
<div class="pretext <?php echo htmlspecialchars( $templateClass ); ?>">
|
||||||
|
<p><?php echo $preLogoutText; ?></p>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
<?php echo modCBLoginHelper::getPlugins( $params, $type, 'almostStart' ); ?>
|
||||||
|
<?php if ( (int) $params->get( 'greeting', 1 ) ) { ?>
|
||||||
|
<div class="login-greeting <?php echo htmlspecialchars( $templateClass ); ?>">
|
||||||
|
<p><?php echo $greetingText; ?></p>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ( (int) $params->get( 'show_avatar', 1 ) ) { ?>
|
||||||
|
<div class="login-avatar <?php echo htmlspecialchars( $templateClass ); ?>">
|
||||||
|
<p><?php echo $cbUser->getField( 'avatar', null, 'html', 'none', 'list', 0, true ); ?></p>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
<?php echo modCBLoginHelper::getPlugins( $params, $type, 'beforeButton', 'p' ); ?>
|
||||||
|
<div class="logout-button">
|
||||||
|
<button type="submit" name="Submit" class="<?php echo ( $styleLogout ? htmlspecialchars( $styleLogout ) : 'button' ); ?>"<?php echo $buttonStyle; ?>>
|
||||||
|
<?php if ( in_array( $showButton, array( 1, 2, 3 ) ) ) { ?>
|
||||||
|
<span class="<?php echo htmlspecialchars( $templateClass ); ?>">
|
||||||
|
<span class="cbModuleLogoutIcon fa fa-sign-out" title="<?php echo htmlspecialchars( CBTxt::T( 'Log out' ) ); ?>"></span>
|
||||||
|
</span>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ( in_array( $showButton, array( 0, 1, 4 ) ) ) { ?>
|
||||||
|
<?php echo htmlspecialchars( CBTxt::T( 'Log out' ) ); ?>
|
||||||
|
<?php } ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<?php echo modCBLoginHelper::getPlugins( $params, $type, 'afterButton', 'p' ); ?>
|
||||||
|
<?php if ( $profileViewText || $profileEditText || $showPrivateMessages || $showConnectionRequests ) { ?>
|
||||||
|
<p>
|
||||||
|
<ul class="logout-links">
|
||||||
|
<?php if ( $showPrivateMessages ) { ?>
|
||||||
|
<li class="logout-private-messages">
|
||||||
|
<a href="<?php echo $privateMessageURL; ?>"<?php echo ( $stylePrivateMsgs ? ' class="' . htmlspecialchars( $stylePrivateMsgs ) . '"' : null ); ?>>
|
||||||
|
<?php if ( $params->get( 'show_pms_icon', 0 ) ) { ?>
|
||||||
|
<span class="<?php echo htmlspecialchars( $templateClass ); ?>">
|
||||||
|
<span class="cbModulePMIcon fa fa-envelope" title="<?php echo htmlspecialchars( CBTxt::T( 'Private Messages' ) ); ?>"></span>
|
||||||
|
</span>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ( $newMessageCount ) { ?>
|
||||||
|
<?php echo ( $newMessageCount == 1 ? CBTxt::T( 'YOU_HAVE_COUNT_NEW_PRIVATE_MESSAGE', 'You have [count] new private message.', array( '[count]' => $newMessageCount ) ) : CBTxt::T( 'YOU_HAVE_COUNT_NEW_PRIVATE_MESSAGES', 'You have [count] new private messages.', array( '[count]' => $newMessageCount ) ) ); ?>
|
||||||
|
<?php } else { ?>
|
||||||
|
<?php echo CBTxt::T( 'You have no new private messages.' ); ?>
|
||||||
|
<?php } ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ( $showConnectionRequests ) { ?>
|
||||||
|
<li class="logout-connection-requests">
|
||||||
|
<a href="<?php echo $_CB_framework->viewUrl( 'manageconnections' ); ?>"<?php echo ( $styleConnRequests ? ' class="' . htmlspecialchars( $styleConnRequests ) . '"' : null ); ?>>
|
||||||
|
<?php if ( $params->get( 'show_connection_notifications_icon', 0 ) ) { ?>
|
||||||
|
<span class="<?php echo htmlspecialchars( $templateClass ); ?>">
|
||||||
|
<span class="cbModuleConnectionsIcon fa fa-users" title="<?php echo htmlspecialchars( CBTxt::T( 'Connections' ) ); ?>"></span>
|
||||||
|
</span>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ( $newConnectionRequests ) { ?>
|
||||||
|
<?php echo ( $newConnectionRequests == 1 ? CBTxt::T( 'YOU_HAVE_COUNT_NEW_CONNECTION_REQUEST', 'You have [count] new connection request.', array( '[count]' => $newConnectionRequests ) ) : CBTxt::T( 'YOU_HAVE_COUNT_NEW_CONNECTION_REQUESTS', 'You have [count] new connection requests.', array( '[count]' => $newConnectionRequests ) ) ); ?>
|
||||||
|
<?php } else { ?>
|
||||||
|
<?php echo CBTxt::T( 'You have no new connection requests.' ); ?>
|
||||||
|
<?php } ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ( $profileViewText ) { ?>
|
||||||
|
<li class="logout-profile">
|
||||||
|
<a href="<?php echo $_CB_framework->userProfileUrl(); ?>"<?php echo ( $styleProfile ? ' class="' . htmlspecialchars( $styleProfile ) . '"' : null ); ?>>
|
||||||
|
<?php if ( $params->get( 'icon_show_profile', 0 ) ) { ?>
|
||||||
|
<span class="<?php echo htmlspecialchars( $templateClass ); ?>">
|
||||||
|
<span class="cbModuleProfileViewIcon fa fa-user" title="<?php echo htmlspecialchars( $profileViewText ); ?>"></span>
|
||||||
|
</span>
|
||||||
|
<?php } ?>
|
||||||
|
<?php echo $profileViewText; ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ( $profileEditText ) { ?>
|
||||||
|
<li class="logout-profile-edit">
|
||||||
|
<a href="<?php echo $_CB_framework->userProfileEditUrl(); ?>"<?php echo ( $styleProfileEdit ? ' class="' . htmlspecialchars( $styleProfileEdit ) . '"' : null ); ?>>
|
||||||
|
<?php if ( $params->get( 'icon_edit_profile', 0 ) ) { ?>
|
||||||
|
<span class="<?php echo htmlspecialchars( $templateClass ); ?>">
|
||||||
|
<span class="cbModuleProfileEditIcon fa fa-pencil" title="<?php echo htmlspecialchars( $profileEditText ); ?>"></span>
|
||||||
|
</span>
|
||||||
|
<?php } ?>
|
||||||
|
<?php echo $profileEditText; ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php } ?>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
<?php } ?>
|
||||||
|
<?php echo modCBLoginHelper::getPlugins( $params, $type, 'almostEnd' ); ?>
|
||||||
|
<?php if ( $postLogoutText ) { ?>
|
||||||
|
<div class="posttext <?php echo htmlspecialchars( $templateClass ); ?>">
|
||||||
|
<p><?php echo $postLogoutText; ?></p>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
<?php echo modCBLoginHelper::getPlugins( $params, $type, 'end' ); ?>
|
||||||
|
</form>
|
||||||
|
<?php echo modCBLoginHelper::getPlugins( $params, $type, 'afterForm' ); ?>
|
||||||
1
src/html/mod_cblogin/index.html
Normal file
1
src/html/mod_cblogin/index.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<!DOCTYPE html><title></title>
|
||||||
66
src/html/mod_comprofilermoderator/default.php
Normal file
66
src/html/mod_comprofilermoderator/default.php
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Community Builder (TM)
|
||||||
|
* @version $Id: $
|
||||||
|
* @package CommunityBuilder
|
||||||
|
* @copyright (C) 2004-2025 www.joomlapolis.com / Lightning MultiCom SA - and its licensors, all rights reserved
|
||||||
|
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html GNU/GPL version 2
|
||||||
|
*/
|
||||||
|
if ( ! ( defined( '_VALID_CB' ) || defined( '_JEXEC' ) || defined( '_VALID_MOS' ) ) ) { die( 'Direct Access to this location is not allowed.' ); }
|
||||||
|
|
||||||
|
?>
|
||||||
|
<?php echo modCBModeratorHelper::getPlugins( $params, 'start' ); ?>
|
||||||
|
<?php if ( $preText ) { ?>
|
||||||
|
<div class="pretext">
|
||||||
|
<p><?php echo $preText; ?></p>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
<?php echo modCBModeratorHelper::getPlugins( $params, 'almostStart' ); ?>
|
||||||
|
<?php if ( modCBModeratorHelper::getPlugins( $params, 'beforeLinks' ) || $showBanned || $showImageApproval || $showUserReports || $showUnbanRequests || $showUserApproval || $showPrivateMessages || $showConnectionRequests || modCBModeratorHelper::getPlugins( $params, 'afterLinks' ) ) { ?>
|
||||||
|
<ul class="m-0 unstyled list-unstyled cbModeratorLinks">
|
||||||
|
<?php echo modCBModeratorHelper::getPlugins( $params, 'beforeLinks', 'li' ); ?>
|
||||||
|
<?php if ( $showBanned ) { ?>
|
||||||
|
<li class="cbModeratorLink cbModeratorLinkBanned">
|
||||||
|
<a href="<?php echo $_CB_framework->userProfileUrl(); ?>"><?php echo ( $bannedStatus == 1 ? CBTxt::T( 'Profile Banned' ) : CBTxt::T( 'Unban Request Pending' ) ); ?></a>
|
||||||
|
</li>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ( $showImageApproval ) { ?>
|
||||||
|
<li class="cbModeratorLink cbModeratorLinkImageApproval">
|
||||||
|
<a href="<?php echo $_CB_framework->viewUrl( 'moderateimages' ); ?>"><?php echo ( $imageApprovalCount == 1 ? CBTxt::T( 'COUNT_IMAGE_APPROVAL', '[count] Image Approval', array( '[count]' => $imageApprovalCount ) ) : CBTxt::T( 'COUNT_IMAGE_APPROVALS', '[count] Image Approvals', array( '[count]' => $imageApprovalCount ) ) ); ?></a>
|
||||||
|
</li>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ( $showUserReports ) { ?>
|
||||||
|
<li class="cbModeratorLink cbModeratorLinkUserReports">
|
||||||
|
<a href="<?php echo $_CB_framework->viewUrl( 'moderatereports' ); ?>"><?php echo ( $userReportsCount == 1 ? CBTxt::T( 'COUNT_PROFILE_REPORT', '[count] Profile Report', array( '[count]' => $userReportsCount ) ) : CBTxt::T( 'COUNT_PROFILE_REPORTS', '[count] Profile Reports', array( '[count]' => $userReportsCount ) ) ); ?></a>
|
||||||
|
</li>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ( $showUnbanRequests ) { ?>
|
||||||
|
<li class="cbModeratorLink cbModeratorLinkUnbanRequests">
|
||||||
|
<a href="<?php echo $_CB_framework->viewUrl( 'moderatebans' ); ?>"><?php echo ( $unbanRequestCount == 1 ? CBTxt::T( 'COUNT_UNBAN_REQUEST', '[count] Unban Request', array( '[count]' => $unbanRequestCount ) ) : CBTxt::T( 'COUNT_UNBAN_REQUESTS', '[count] Unban Requests', array( '[count]' => $unbanRequestCount ) ) ); ?></a>
|
||||||
|
</li>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ( $showUserApproval ) { ?>
|
||||||
|
<li class="cbModeratorLink cbModeratorLinkUserApproval">
|
||||||
|
<a href="<?php echo $_CB_framework->viewUrl( 'pendingapprovaluser' ); ?>"><?php echo ( $userApprovalCount == 1 ? CBTxt::T( 'COUNT_USER_APPROVAL', '[count] User Approval', array( '[count]' => $userApprovalCount ) ) : CBTxt::T( 'COUNT_USER_APPROVALS', '[count] User Approvals', array( '[count]' => $userApprovalCount ) ) ); ?></a>
|
||||||
|
</li>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ( $showPrivateMessages ) { ?>
|
||||||
|
<li class="cbModeratorLink cbModeratorLinkPrivateMessages">
|
||||||
|
<a href="<?php echo $privateMessageURL; ?>"><?php echo ( $newMessageCount == 1 ? CBTxt::T( 'COUNT_PRIVATE_MESSAGE', '[count] Private Message', array( '[count]' => $newMessageCount ) ) : CBTxt::T( 'COUNT_PRIVATE_MESSAGES', '[count] Private Messages', array( '[count]' => $newMessageCount ) ) ); ?></a>
|
||||||
|
</li>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ( $showConnectionRequests ) { ?>
|
||||||
|
<li class="cbModeratorLink cbModeratorLinkConnectionRequests">
|
||||||
|
<a href="<?php echo $_CB_framework->viewUrl( 'manageconnections' ); ?>"><?php echo ( $newConnectionRequests == 1 ? CBTxt::T( 'COUNT_CONNECTION_REQUEST', '[count] Connection Request', array( '[count]' => $newConnectionRequests ) ) : CBTxt::T( 'COUNT_CONNECTION_REQUESTS', '[count] Connection Requests', array( '[count]' => $newConnectionRequests ) ) ); ?></a>
|
||||||
|
</li>
|
||||||
|
<?php } ?>
|
||||||
|
<?php echo modCBModeratorHelper::getPlugins( $params, 'afterLinks', 'li' ); ?>
|
||||||
|
</ul>
|
||||||
|
<?php } ?>
|
||||||
|
<?php echo modCBModeratorHelper::getPlugins( $params, 'almostEnd' ); ?>
|
||||||
|
<?php if ( $postText ) { ?>
|
||||||
|
<div class="posttext">
|
||||||
|
<p><?php echo $postText; ?></p>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
<?php echo modCBModeratorHelper::getPlugins( $params, 'end' ); ?>
|
||||||
1
src/html/mod_comprofilermoderator/index.html
Normal file
1
src/html/mod_comprofilermoderator/index.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<!DOCTYPE html><title></title>
|
||||||
42
src/html/mod_comprofileronline/default.php
Normal file
42
src/html/mod_comprofileronline/default.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Community Builder (TM)
|
||||||
|
* @version $Id: $
|
||||||
|
* @package CommunityBuilder
|
||||||
|
* @copyright (C) 2004-2025 www.joomlapolis.com / Lightning MultiCom SA - and its licensors, all rights reserved
|
||||||
|
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html GNU/GPL version 2
|
||||||
|
*/
|
||||||
|
if ( ! ( defined( '_VALID_CB' ) || defined( '_JEXEC' ) || defined( '_VALID_MOS' ) ) ) { die( 'Direct Access to this location is not allowed.' ); }
|
||||||
|
|
||||||
|
?>
|
||||||
|
<?php echo modCBOnlineHelper::getPlugins( $params, 'start' ); ?>
|
||||||
|
<?php if ( $preText ) { ?>
|
||||||
|
<div class="pretext">
|
||||||
|
<p><?php echo $preText; ?></p>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
<?php echo modCBOnlineHelper::getPlugins( $params, 'beforeUsers' ); ?>
|
||||||
|
<?php if ( modCBOnlineHelper::getPlugins( $params, 'beforeLinks' ) || $cbUsers || modCBOnlineHelper::getPlugins( $params, 'afterUsers' ) ) { ?>
|
||||||
|
<ul class="m-0 unstyled list-unstyled cbOnlineUsers">
|
||||||
|
<?php echo modCBOnlineHelper::getPlugins( $params, 'beforeLinks' ); ?>
|
||||||
|
<?php foreach ( $cbUsers as $cbUser ) { ?>
|
||||||
|
<li class="cbOnlineUser">
|
||||||
|
<?php
|
||||||
|
if ( $params->get( 'usertext' ) ) {
|
||||||
|
echo $cbUser->replaceUserVars( $params->get( 'usertext' ) );
|
||||||
|
} else {
|
||||||
|
echo $cbUser->getField( 'formatname', null, 'html', 'none', 'list', 0, true );
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</li>
|
||||||
|
<?php } ?>
|
||||||
|
<?php echo modCBOnlineHelper::getPlugins( $params, 'afterUsers' ); ?>
|
||||||
|
</ul>
|
||||||
|
<?php } ?>
|
||||||
|
<?php echo modCBOnlineHelper::getPlugins( $params, 'almostEnd' ); ?>
|
||||||
|
<?php if ( $postText ) { ?>
|
||||||
|
<div class="posttext">
|
||||||
|
<p><?php echo $postText; ?></p>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
<?php echo modCBOnlineHelper::getPlugins( $params, 'end' ); ?>
|
||||||
1
src/html/mod_comprofileronline/index.html
Normal file
1
src/html/mod_comprofileronline/index.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<!DOCTYPE html><title></title>
|
||||||
@@ -25,8 +25,9 @@ $headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'U
|
|||||||
if ($params->get('backgroundimage')) {
|
if ($params->get('backgroundimage')) {
|
||||||
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
|
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
|
||||||
$wa = $app->getDocument()->getWebAssetManager();
|
$wa = $app->getDocument()->getWebAssetManager();
|
||||||
|
$bgUrl = Uri::root(true) . '/' . HTMLHelper::_('cleanImageURL', $params->get('backgroundimage'))->url;
|
||||||
$wa->addInlineStyle(
|
$wa->addInlineStyle(
|
||||||
'#' . $modId . '{background-image: url("' . Uri::root(true) . '/' . HTMLHelper::_('cleanImageURL', $params->get('backgroundimage'))->url . '");}',
|
'#' . $modId . '{--hero-bg-image: url("' . $bgUrl . '"); background-image: var(--hero-bg-image);}',
|
||||||
['name' => $modId]
|
['name' => $modId]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
122
src/html/mod_dpcalendar_counter/default.php
Normal file
122
src/html/mod_dpcalendar_counter/default.php
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @package DPCalendar
|
||||||
|
* @copyright Copyright (C) 2014 Digital Peak GmbH. <https://www.digital-peak.com>
|
||||||
|
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
\defined('_JEXEC') or die();
|
||||||
|
|
||||||
|
use Joomla\CMS\HTML\HTMLHelper;
|
||||||
|
|
||||||
|
if (!$events) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$translator->translateJS('MOD_DPCALENDAR_COUNTER_LABEL_YEARS');
|
||||||
|
$translator->translateJS('MOD_DPCALENDAR_COUNTER_LABEL_MONTHS');
|
||||||
|
$translator->translateJS('MOD_DPCALENDAR_COUNTER_LABEL_WEEKS');
|
||||||
|
$translator->translateJS('MOD_DPCALENDAR_COUNTER_LABEL_DAYS');
|
||||||
|
$translator->translateJS('MOD_DPCALENDAR_COUNTER_LABEL_HOURS');
|
||||||
|
$translator->translateJS('MOD_DPCALENDAR_COUNTER_LABEL_MINUTES');
|
||||||
|
$translator->translateJS('MOD_DPCALENDAR_COUNTER_LABEL_SECONDS');
|
||||||
|
$translator->translateJS('MOD_DPCALENDAR_COUNTER_LABEL_YEAR');
|
||||||
|
$translator->translateJS('MOD_DPCALENDAR_COUNTER_LABEL_MONTH');
|
||||||
|
$translator->translateJS('MOD_DPCALENDAR_COUNTER_LABEL_WEEK');
|
||||||
|
$translator->translateJS('MOD_DPCALENDAR_COUNTER_LABEL_DAY');
|
||||||
|
$translator->translateJS('MOD_DPCALENDAR_COUNTER_LABEL_HOUR');
|
||||||
|
$translator->translateJS('MOD_DPCALENDAR_COUNTER_LABEL_MINUTE');
|
||||||
|
$translator->translateJS('MOD_DPCALENDAR_COUNTER_LABEL_SECOND');
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_CLOSE');
|
||||||
|
|
||||||
|
$document->loadStyleFile('default.css', 'mod_dpcalendar_counter');
|
||||||
|
$document->loadScriptFile('default.js', 'mod_dpcalendar_counter');
|
||||||
|
$document->addStyle($params->get('custom_css', ''));
|
||||||
|
?>
|
||||||
|
<div class="mod-dpcalendar-counter mod-dpcalendar-counter-<?php echo $module->id; ?>">
|
||||||
|
<div class="mod-dpcalendar-counter__custom-text">
|
||||||
|
<?php echo HTMLHelper::_('content.prepare', $translator->translate($params->get('textbefore', ''))); ?>
|
||||||
|
</div>
|
||||||
|
<div class="mod-dpcalendar-counter__events">
|
||||||
|
<?php foreach ($events as $event) { ?>
|
||||||
|
<div class="mod-dpcalendar-counter__event"
|
||||||
|
data-date="<?php echo $dateHelper->getDate($event->start_date, $event->all_day)->format('Y-m-d H:i:s'); ?>"
|
||||||
|
data-modal="<?php echo $params->get('show_as_popup'); ?>"
|
||||||
|
data-counting="<?php echo !$params->get('disable_counting'); ?>">
|
||||||
|
<div class="mod-dpcalendar-counter__upcoming">
|
||||||
|
<div class="mod-dpcalendar-counter__intro-text">
|
||||||
|
<?php echo $translator->translate('MOD_DPCALENDAR_COUNTER_SOON_OUTPUT'); ?>
|
||||||
|
</div>
|
||||||
|
<?php if ($params->get('show_field_year', 1)) { ?>
|
||||||
|
<span class="mod-dpcalendar-counter__year dp-counter-block">
|
||||||
|
<span class="dp-counter-block__number"></span>
|
||||||
|
<span class="dp-counter-block__content"></span>
|
||||||
|
</span>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ($params->get('show_field_month', 1)) { ?>
|
||||||
|
<span class="mod-dpcalendar-counter__month dp-counter-block">
|
||||||
|
<span class="dp-counter-block__number"></span>
|
||||||
|
<span class="dp-counter-block__content"></span>
|
||||||
|
</span>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ($params->get('show_field_week', 1)) { ?>
|
||||||
|
<span class="mod-dpcalendar-counter__week dp-counter-block">
|
||||||
|
<span class="dp-counter-block__number"></span>
|
||||||
|
<span class="dp-counter-block__content"></span>
|
||||||
|
</span>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ($params->get('show_field_day', 1)) { ?>
|
||||||
|
<span class="mod-dpcalendar-counter__day dp-counter-block">
|
||||||
|
<span class="dp-counter-block__number"></span>
|
||||||
|
<span class="dp-counter-block__content"></span>
|
||||||
|
</span>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ($params->get('show_field_hour', 1)) { ?>
|
||||||
|
<span class="mod-dpcalendar-counter__hour dp-counter-block">
|
||||||
|
<span class="dp-counter-block__number"></span>
|
||||||
|
<span class="dp-counter-block__content"></span>
|
||||||
|
</span>
|
||||||
|
<?php } ?>
|
||||||
|
<span class="mod-dpcalendar-counter__minute dp-counter-block">
|
||||||
|
<span class="dp-counter-block__number"></span>
|
||||||
|
<span class="dp-counter-block__content"></span>
|
||||||
|
</span>
|
||||||
|
<span class="mod-dpcalendar-counter__second dp-counter-block">
|
||||||
|
<span class="dp-counter-block__number"></span>
|
||||||
|
<span class="dp-counter-block__content"></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="mod-dpcalendar-counter__ongoing">
|
||||||
|
<div class="mod-dpcalendar-counter__intro-text">
|
||||||
|
<?php echo $translator->translate('MOD_DPCALENDAR_COUNTER_ONGOING_OUTPUT'); ?>
|
||||||
|
</div>
|
||||||
|
<a href="<?php echo $router->getEventRoute($event->id, $event->catid); ?>" class="mod-dpcalendar-counter__link dp-link">
|
||||||
|
<?php echo $event->title; ?>
|
||||||
|
</a>
|
||||||
|
<?php if ($event->images->image_intro) { ?>
|
||||||
|
<div class="mod-dpcalendar-upcoming-counter__image">
|
||||||
|
<figure class="dp-figure">
|
||||||
|
<a href="<?php echo $router->getEventRoute($event->id, $event->catid); ?>" class="mod-dpcalendar-counter__link dp-link">
|
||||||
|
<img class="dp-image" src="<?php echo $event->images->image_intro; ?>"
|
||||||
|
alt="<?php echo $event->images->image_intro_alt; ?>"
|
||||||
|
loading="lazy" <?php echo $event->images->image_intro_dimensions; ?>>
|
||||||
|
</a>
|
||||||
|
<?php if ($event->images->image_intro_caption) { ?>
|
||||||
|
<figcaption class="dp-figure__caption"><?php echo $event->images->image_intro_caption; ?></figcaption>
|
||||||
|
<?php } ?>
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ($event->truncatedDescription) { ?>
|
||||||
|
<div class="mod-dpcalendar-counter__description">
|
||||||
|
<?php echo $event->truncatedDescription; ?>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
</div>
|
||||||
|
<div class="mod-dpcalendar-counter__custom-text">
|
||||||
|
<?php echo HTMLHelper::_('content.prepare', $translator->translate($params->get('textafter', ''))); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
1
src/html/mod_dpcalendar_counter/index.html
Normal file
1
src/html/mod_dpcalendar_counter/index.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<!DOCTYPE html><title></title>
|
||||||
37
src/html/mod_dpcalendar_map/default.php
Normal file
37
src/html/mod_dpcalendar_map/default.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @package DPCalendar
|
||||||
|
* @copyright Copyright (C) 2014 Digital Peak GmbH. <https://www.digital-peak.com>
|
||||||
|
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
\defined('_JEXEC') or die();
|
||||||
|
|
||||||
|
use Joomla\CMS\HTML\HTMLHelper;
|
||||||
|
|
||||||
|
$document->loadStyleFile('default.css', 'mod_dpcalendar_map');
|
||||||
|
$document->loadScriptFile('views/map/default.js');
|
||||||
|
$document->addStyle($params->get('custom_css', ''));
|
||||||
|
|
||||||
|
$layoutHelper->renderLayout('block.map', $displayData);
|
||||||
|
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_FIELD_CONFIG_EVENT_LABEL_NO_EVENT_TEXT');
|
||||||
|
?>
|
||||||
|
<div class="mod-dpcalendar-map mod-dpcalendar-map-<?php echo $module->id; ?> dp-search-map"
|
||||||
|
data-popup="<?php echo $params->get('show_as_popup'); ?>">
|
||||||
|
<div class="mod-dpcalendar-map__custom-text">
|
||||||
|
<?php echo HTMLHelper::_('content.prepare', $translator->translate($params->get('textbefore', ''))); ?>
|
||||||
|
</div>
|
||||||
|
<?php echo $layoutHelper->renderLayout('block.loader', $displayData); ?>
|
||||||
|
<?php echo $layoutHelper->renderLayout('block.filter', $displayData); ?>
|
||||||
|
<div class="mod-dpcalendar-map__map dp-map"
|
||||||
|
style="width: <?php echo $params->get('width', '100%'); ?>; height: <?php echo $params->get('height', '300px'); ?>"
|
||||||
|
data-zoom="<?php echo $params->get('zoom', 4); ?>"
|
||||||
|
data-latitude="<?php echo $params->get('lat', 47); ?>"
|
||||||
|
data-longitude="<?php echo $params->get('long', 4); ?>"
|
||||||
|
data-ask-consent="<?php echo $params->get('map_ask_consent'); ?>">
|
||||||
|
</div>
|
||||||
|
<div class="mod-dpcalendar-map__custom-text">
|
||||||
|
<?php echo HTMLHelper::_('content.prepare', $translator->translate($params->get('textafter', ''))); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
1
src/html/mod_dpcalendar_map/index.html
Normal file
1
src/html/mod_dpcalendar_map/index.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<!DOCTYPE html><title></title>
|
||||||
204
src/html/mod_dpcalendar_mini/_scripts.php
Normal file
204
src/html/mod_dpcalendar_mini/_scripts.php
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @package DPCalendar
|
||||||
|
* @copyright Copyright (C) 2018 Digital Peak GmbH. <https://www.digital-peak.com>
|
||||||
|
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
\defined('_JEXEC') or die();
|
||||||
|
|
||||||
|
use DigitalPeak\Component\DPCalendar\Administrator\Helper\DPCalendarHelper;
|
||||||
|
use DigitalPeak\Component\DPCalendar\Administrator\Router\Router;;
|
||||||
|
use Joomla\Utilities\ArrayHelper;
|
||||||
|
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_VIEW_CALENDAR_ALL_DAY');
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_VIEW_CALENDAR_VIEW_MONTH');
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_VIEW_CALENDAR_VIEW_WEEK');
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_VIEW_CALENDAR_VIEW_DAY');
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_VIEW_CALENDAR_VIEW_LIST');
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_VIEW_CALENDAR_VIEW_TEXTS_UNTIL');
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_VIEW_CALENDAR_VIEW_TEXTS_PAST');
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_VIEW_CALENDAR_VIEW_TEXTS_TODAY');
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_VIEW_CALENDAR_VIEW_TEXTS_TOMORROW');
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_VIEW_CALENDAR_VIEW_TEXTS_THIS_WEEK');
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_VIEW_CALENDAR_VIEW_TEXTS_NEXT_WEEK');
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_VIEW_CALENDAR_VIEW_TEXTS_THIS_MONTH');
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_VIEW_CALENDAR_VIEW_TEXTS_NEXT_MONTH');
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_VIEW_CALENDAR_VIEW_TEXTS_FUTURE');
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_VIEW_CALENDAR_VIEW_TEXTS_WEEK');
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_VIEW_CALENDAR_VIEW_TEXTS_MORE');
|
||||||
|
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_VIEW_CALENDAR_TOOLBAR_NEXT');
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_VIEW_CALENDAR_TOOLBAR_PREVIOUS');
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_VIEW_CALENDAR_TOOLBAR_TODAY');
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_VIEW_CALENDAR_SHOW_DATEPICKER');
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_VIEW_CALENDAR_TOOLBAR_PRINT');
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_VIEW_CALENDAR_TOOLBAR_ADD');
|
||||||
|
|
||||||
|
$translator->translateJS('JCANCEL');
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_CLOSE');
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_PREVIOUS');
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_NEXT');
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_FIELD_CAPACITY_UNLIMITED');
|
||||||
|
|
||||||
|
$document->addScriptOptions('calendar.names', $dateHelper->getNames());
|
||||||
|
$document->addScriptOptions('timezone', $dateHelper->getDate()->getTimezone()->getName());
|
||||||
|
$document->addScriptOptions('itemid', $app->getInput()->getInt('Itemid', 0));
|
||||||
|
|
||||||
|
// The options which will be passed to the js library
|
||||||
|
$options = [];
|
||||||
|
$options['requestUrlRoot'] = 'view=events&limit=0&format=raw&module_id=' . $module->id . '&Itemid=' . $app->getInput()->getInt('Itemid', 0);
|
||||||
|
$options['calendarIds'] = [implode(',', $ids)];
|
||||||
|
|
||||||
|
// Set the default view
|
||||||
|
$options['initialView'] = $params->get('default_view', 'month');
|
||||||
|
|
||||||
|
// Some general calendar options
|
||||||
|
$options['weekNumbers'] = (bool)$params->get('week_numbers');
|
||||||
|
$options['weekends'] = (bool)$params->get('weekend', 1);
|
||||||
|
$options['fixedWeekCount'] = (bool)$params->get('fixed_week_count', 1);
|
||||||
|
|
||||||
|
$bd = $params->get('business_hours_days', []);
|
||||||
|
if ($bd && !(is_countable($bd) ? count($bd) : 0 === 1 && !$bd[0])) {
|
||||||
|
$options['businessHours'] = [
|
||||||
|
'startTime' => $params->get('business_hours_start', ''),
|
||||||
|
'endTime' => $params->get('business_hours_end', ''),
|
||||||
|
'daysOfWeek' => $params->get('business_hours_days', [])
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$options['firstDay'] = (int)$params->get('weekstart', 1);
|
||||||
|
$options['hiddenDays'] = ArrayHelper::toInteger($params->get('hidden_days', []));
|
||||||
|
$options['scrollTime'] = $params->get('first_hour', 6) === 'now' ? 'now' : $params->get('first_hour', 6) . ':00:00';
|
||||||
|
$options['weekNumberCalculation'] = 'ISO';
|
||||||
|
$options['displayEventEnd'] = true;
|
||||||
|
$options['navLinks'] = true;
|
||||||
|
|
||||||
|
$max = $params->get('max_time', 24);
|
||||||
|
if (is_numeric($max)) {
|
||||||
|
$max .= ':00:00';
|
||||||
|
}
|
||||||
|
$options['slotMaxTime'] = $max;
|
||||||
|
|
||||||
|
$min = $params->get('min_time', 0);
|
||||||
|
if (is_numeric($min)) {
|
||||||
|
$min .= ':00:00';
|
||||||
|
}
|
||||||
|
$options['slotMinTime'] = $min;
|
||||||
|
|
||||||
|
$options['nowIndicator'] = (bool)$params->get('current_time_indicator', 1);
|
||||||
|
$options['displayEventTime'] = $params->get('compact_events', 2) != 2;
|
||||||
|
|
||||||
|
if ($params->get('event_limit', '') != '-1') {
|
||||||
|
$options['dayMaxEventRows'] = $params->get('event_limit', '') == '' ? 2 : $params->get('event_limit', '') + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the height
|
||||||
|
if ($params->get('calendar_height', 0) > 0) {
|
||||||
|
$options['contentHeight'] = (int)$params->get('calendar_height', 0);
|
||||||
|
} else {
|
||||||
|
$options['height'] = 'auto';
|
||||||
|
}
|
||||||
|
|
||||||
|
$options['slotEventOverlap'] = (bool)$params->get('overlap_events', 1);
|
||||||
|
|
||||||
|
// Set up the header
|
||||||
|
$options['headerToolbar'] = ['left' => [], 'center' => [], 'right' => []];
|
||||||
|
if ($params->get('header_show_navigation', 1)) {
|
||||||
|
$options['headerToolbar']['left'][] = 'prev';
|
||||||
|
$options['headerToolbar']['left'][] = 'next';
|
||||||
|
}
|
||||||
|
if ($params->get('header_show_today', 0)) {
|
||||||
|
$options['headerToolbar']['left'][] = 'today';
|
||||||
|
}
|
||||||
|
if ($params->get('header_show_datepicker', 0)) {
|
||||||
|
$options['headerToolbar']['left'][] = 'datepicker';
|
||||||
|
}
|
||||||
|
if ($params->get('header_show_create', 1) && DPCalendarHelper::canCreateEvent()) {
|
||||||
|
$options['headerToolbar']['left'][] = 'add';
|
||||||
|
}
|
||||||
|
if ($params->get('header_show_title', 1)) {
|
||||||
|
$options['headerToolbar']['center'][] = 'title';
|
||||||
|
}
|
||||||
|
if ($params->get('header_show_month', 1)) {
|
||||||
|
$options['headerToolbar']['right'][] = 'month';
|
||||||
|
}
|
||||||
|
if ($params->get('header_show_week', 1)) {
|
||||||
|
$options['headerToolbar']['right'][] = 'week';
|
||||||
|
}
|
||||||
|
if ($params->get('header_show_day', 1)) {
|
||||||
|
$options['headerToolbar']['right'][] = 'day';
|
||||||
|
} else {
|
||||||
|
$options['navLinks'] = false;
|
||||||
|
}
|
||||||
|
if ($params->get('header_show_list', 1)) {
|
||||||
|
$options['headerToolbar']['right'][] = 'list';
|
||||||
|
}
|
||||||
|
|
||||||
|
$options['headerToolbar']['left'] = implode(',', $options['headerToolbar']['left']);
|
||||||
|
$options['headerToolbar']['center'] = implode(',', $options['headerToolbar']['center']);
|
||||||
|
$options['headerToolbar']['right'] = implode(',', $options['headerToolbar']['right']);
|
||||||
|
|
||||||
|
$resourceViews = $params->get('calendar_resource_views');
|
||||||
|
if (!DPCalendarHelper::isFree() && $resourceViews && $resources) {
|
||||||
|
$options['resources'] = $resources;
|
||||||
|
$options['resourceViews'] = $resourceViews;
|
||||||
|
$options['datesAboveResources'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the views
|
||||||
|
$options['views'] = [];
|
||||||
|
$options['views']['month'] = [
|
||||||
|
'titleFormat' => $dateHelper->convertPHPDateToJS($params->get('titleformat_month', 'F Y')),
|
||||||
|
'eventTimeFormat' => $dateHelper->convertPHPDateToJS($params->get('timeformat_month', 'H:i')),
|
||||||
|
'dayHeaderFormat' => $dateHelper->convertPHPDateToJS($params->get('columnformat_month', 'D'))
|
||||||
|
];
|
||||||
|
$options['views']['week'] = [
|
||||||
|
'titleFormat' => $dateHelper->convertPHPDateToJS($params->get('titleformat_week', 'M j Y')),
|
||||||
|
'eventTimeFormat' => $dateHelper->convertPHPDateToJS($params->get('timeformat_week', 'H:i')),
|
||||||
|
'dayHeaderFormat' => $dateHelper->convertPHPDateToJS($params->get('columnformat_week', 'D n/j')),
|
||||||
|
'slotDuration' => $dateHelper->minutesToDuration((int)$params->get('agenda_slot_minutes', 30)),
|
||||||
|
'slotLabelInterval' => $dateHelper->minutesToDuration((int)$params->get('agenda_slot_minutes', 30)),
|
||||||
|
'slotLabelFormat' => $dateHelper->convertPHPDateToJS($params->get('axisformat_week', 'H:i'))
|
||||||
|
];
|
||||||
|
$options['views']['day'] = [
|
||||||
|
'titleFormat' => $dateHelper->convertPHPDateToJS($params->get('titleformat_day', 'F j Y')),
|
||||||
|
'eventTimeFormat' => $dateHelper->convertPHPDateToJS($params->get('timeformat_day', 'H:i')),
|
||||||
|
'dayHeaderFormat' => $dateHelper->convertPHPDateToJS($params->get('columnformat_day', 'l')),
|
||||||
|
'slotDuration' => $dateHelper->minutesToDuration((int)$params->get('agenda_slot_minutes', 30)),
|
||||||
|
'slotLabelInterval' => $dateHelper->minutesToDuration((int)$params->get('agenda_slot_minutes', 30)),
|
||||||
|
'slotLabelFormat' => $dateHelper->convertPHPDateToJS($params->get('axisformat_day', 'H:i'))
|
||||||
|
];
|
||||||
|
$options['views']['list'] = [
|
||||||
|
'titleFormat' => $dateHelper->convertPHPDateToJS($params->get('titleformat_list', 'M j Y')),
|
||||||
|
'eventTimeFormat' => $dateHelper->convertPHPDateToJS($params->get('timeformat_list', 'H:i')),
|
||||||
|
'dayHeaderFormat' => $dateHelper->convertPHPDateToJS($params->get('columnformat_list', 'D')),
|
||||||
|
'listDayFormat' => $dateHelper->convertPHPDateToJS($params->get('dayformat_list', 'l')),
|
||||||
|
'listDaySideFormat' => $dateHelper->convertPHPDateToJS($params->get('dateformat_list', 'F j, Y')),
|
||||||
|
'duration' => ['days' => (int)$params->get('list_range', 30)],
|
||||||
|
'noEventsContent' => $translator->translate('COM_DPCALENDAR_ERROR_EVENT_NOT_FOUND', true)
|
||||||
|
];
|
||||||
|
|
||||||
|
// Some DPCalendar specific options
|
||||||
|
$options['show_event_as_popup'] = $params->get('show_event_as_popup');
|
||||||
|
$options['popupWidth'] = $params->get('popup_width');
|
||||||
|
$options['popupHeight'] = $params->get('popup_height');
|
||||||
|
$options['show_event_tooltip'] = $params->get('show_event_tooltip', 1);
|
||||||
|
$options['show_map'] = $params->get('show_map', 1);
|
||||||
|
$options['event_create_form'] = (int)$params->get('event_create_form', 1);
|
||||||
|
$options['screen_size_list_view'] = $params->get('screen_size_list_view', 500);
|
||||||
|
$options['use_hash'] = false;
|
||||||
|
if (DPCalendarHelper::canCreateEvent()) {
|
||||||
|
$router = new Router();
|
||||||
|
$input = $app->getInput();
|
||||||
|
$returnPage = $input->getInt('Itemid', 0) ? 'index.php?Itemid=' . $input->getInt('Itemid', 0) : null;
|
||||||
|
$options['event_create_url'] = $router->getEventFormRoute('0', $returnPage, null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the actual date
|
||||||
|
$now = DPCalendarHelper::getDate($params->get('start_date'));
|
||||||
|
$options['year'] = $now->format('Y', true);
|
||||||
|
$options['month'] = $now->format('m', true);
|
||||||
|
$options['date'] = $now->format('d', true);
|
||||||
|
|
||||||
|
$document->addScriptOptions('module.mini.' . $module->id . '.options', $options);
|
||||||
50
src/html/mod_dpcalendar_mini/default.php
Normal file
50
src/html/mod_dpcalendar_mini/default.php
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @package DPCalendar
|
||||||
|
* @copyright Copyright (C) 2014 Digital Peak GmbH. <https://www.digital-peak.com>
|
||||||
|
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
\defined('_JEXEC') or die();
|
||||||
|
|
||||||
|
use Joomla\CMS\Helper\ModuleHelper;
|
||||||
|
use Joomla\CMS\HTML\HTMLHelper;
|
||||||
|
|
||||||
|
if ($params->get('show_map', 1)) {
|
||||||
|
$layoutHelper->renderLayout('block.map', $displayData);
|
||||||
|
}
|
||||||
|
|
||||||
|
$document->loadScriptFile('views/calendar/default.js');
|
||||||
|
$document->loadScriptFile('views/calendar/default.js');
|
||||||
|
$document->loadStyleFile('default.css', 'mod_dpcalendar_mini');
|
||||||
|
$document->addStyle($params->get('custom_css', ''));
|
||||||
|
|
||||||
|
require ModuleHelper::getLayoutPath('mod_dpcalendar_mini', '_scripts');
|
||||||
|
|
||||||
|
$compact = $params->get('compact_events', 2) == 1 ? 'compact' : 'expanded';
|
||||||
|
?>
|
||||||
|
<div class="mod-dpcalendar-mini mod-dpcalendar-mini_<?php echo $compact; ?> mod-dpcalendar-mini-<?php echo $module->id; ?>">
|
||||||
|
<div class="mod-dpcalendar-mini__loader">
|
||||||
|
<?php echo $layoutHelper->renderLayout('block.loader', $displayData); ?>
|
||||||
|
</div>
|
||||||
|
<div class="mod-dpcalendar-mini__custom-text">
|
||||||
|
<?php echo HTMLHelper::_('content.prepare', $translator->translate($params->get('textbefore', ''))); ?>
|
||||||
|
</div>
|
||||||
|
<div class="mod-dpcalendar-mini__calendar dp-calendar"
|
||||||
|
data-options="DPCalendar.module.mini.<?php echo $module->id; ?>.options"></div>
|
||||||
|
<?php require ModuleHelper::getLayoutPath('mod_dpcalendar_mini', 'default_map'); ?>
|
||||||
|
<?php require ModuleHelper::getLayoutPath('mod_dpcalendar_mini', 'default_quickadd'); ?>
|
||||||
|
<?php require ModuleHelper::getLayoutPath('mod_dpcalendar_mini', 'default_icons'); ?>
|
||||||
|
<div class="dp-filter">
|
||||||
|
<form class="dp-form">
|
||||||
|
<?php foreach ($ids as $id) { ?>
|
||||||
|
<input type="hidden" name="filter[calendars][]" value="<?php echo $id; ?>">
|
||||||
|
<?php } ?>
|
||||||
|
<input type="hidden" name="list[start-date]">
|
||||||
|
<input type="hidden" name="list[end-date]">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="mod-dpcalendar-mini__custom-text">
|
||||||
|
<?php echo HTMLHelper::_('content.prepare', $translator->translate($params->get('textafter', ''))); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
22
src/html/mod_dpcalendar_mini/default_icons.php
Normal file
22
src/html/mod_dpcalendar_mini/default_icons.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @package DPCalendar
|
||||||
|
* @copyright Copyright (C) 2020 Digital Peak GmbH. <https://www.digital-peak.com>
|
||||||
|
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
\defined('_JEXEC') or die();
|
||||||
|
|
||||||
|
use DigitalPeak\Component\DPCalendar\Administrator\HTML\Block\Icon;
|
||||||
|
?>
|
||||||
|
<div class="mod-dpcalendar-mini__icons">
|
||||||
|
<?php echo $layoutHelper->renderLayout('block.icon', ['icon' => Icon::DELETE]); ?>
|
||||||
|
<?php echo $layoutHelper->renderLayout('block.icon', ['icon' => Icon::EDIT]); ?>
|
||||||
|
<?php echo $layoutHelper->renderLayout('block.icon', ['icon' => Icon::PLUS]); ?>
|
||||||
|
<?php echo $layoutHelper->renderLayout('block.icon', ['icon' => Icon::PRINTING]); ?>
|
||||||
|
<?php echo $layoutHelper->renderLayout('block.icon', ['icon' => Icon::CALENDAR]); ?>
|
||||||
|
<?php echo $layoutHelper->renderLayout('block.icon', ['icon' => Icon::NEXT]); ?>
|
||||||
|
<?php echo $layoutHelper->renderLayout('block.icon', ['icon' => Icon::BACK]); ?>
|
||||||
|
<?php echo $layoutHelper->renderLayout('block.icon', ['icon' => Icon::USERS]); ?>
|
||||||
|
<?php echo $layoutHelper->renderLayout('block.icon', ['icon' => Icon::MONEY]); ?>
|
||||||
|
</div>
|
||||||
20
src/html/mod_dpcalendar_mini/default_map.php
Normal file
20
src/html/mod_dpcalendar_mini/default_map.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @package DPCalendar
|
||||||
|
* @copyright Copyright (C) 2019 Digital Peak GmbH. <https://www.digital-peak.com>
|
||||||
|
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
\defined('_JEXEC') or die();
|
||||||
|
|
||||||
|
if (!$params->get('show_map', 0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<div class="mod-dpcalendar-mini__map dp-map"
|
||||||
|
style="width: <?php echo $params->get('map_width', '100%'); ?>; height: <?php echo $params->get('map_height', '350px'); ?>"
|
||||||
|
data-zoom="<?php echo $params->get('map_zoom', 4); ?>"
|
||||||
|
data-latitude="<?php echo $params->get('map_lat', 47); ?>"
|
||||||
|
data-longitude="<?php echo $params->get('map_long', 4); ?>"
|
||||||
|
data-ask-consent="<?php echo $params->get('map_ask_consent'); ?>">
|
||||||
|
</div>
|
||||||
47
src/html/mod_dpcalendar_mini/default_quickadd.php
Normal file
47
src/html/mod_dpcalendar_mini/default_quickadd.php
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @package DPCalendar
|
||||||
|
* @copyright Copyright (C) 2023 Digital Peak GmbH. <https://www.digital-peak.com>
|
||||||
|
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
\defined('_JEXEC') or die();
|
||||||
|
|
||||||
|
use DigitalPeak\Component\DPCalendar\Administrator\Helper\DPCalendarHelper;
|
||||||
|
use Joomla\CMS\Uri\Uri;
|
||||||
|
use Joomla\CMS\HTML\HTMLHelper;
|
||||||
|
|
||||||
|
if (!DPCalendarHelper::canCreateEvent()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<div class="mod-dpcalendar-mini__quickadd dp-quickadd">
|
||||||
|
<form action="<?php echo $router->getEventFormRoute(0, Uri::getInstance()->toString()); ?>" method="post" class="dp-form form-validate">
|
||||||
|
<?php echo $quickaddForm->renderField('start_date'); ?>
|
||||||
|
<?php echo $quickaddForm->renderField('end_date'); ?>
|
||||||
|
<?php echo $quickaddForm->renderField('title'); ?>
|
||||||
|
<?php echo $quickaddForm->renderField('catid'); ?>
|
||||||
|
<?php echo $quickaddForm->renderField('color'); ?>
|
||||||
|
<input type="hidden" name="task" class="dp-input dp-input-hidden">
|
||||||
|
<input type="hidden" name="urlhash" class="dp-input dp-input-hidden">
|
||||||
|
<input type="hidden" name="jform[capacity]" value="0" class="dp-input dp-input-hidden">
|
||||||
|
<?php if ($params->get('event_create_form', 1) == '1' || $params->get('event_create_form', 1) == '3') { ?>
|
||||||
|
<input type="hidden" name="jform[all_day]" value="0" class="dp-input dp-input-hidden">
|
||||||
|
<?php } ?>
|
||||||
|
<input type="hidden" name="layout" value="edit" class="dp-input dp-input-hidden">
|
||||||
|
<input type="hidden" name="jform[location_ids][]" class="dp-input dp-input-hidden">
|
||||||
|
<input type="hidden" name="jform[rooms][]" class="dp-input dp-input-hidden">
|
||||||
|
<?php echo HTMLHelper::_('form.token'); ?>
|
||||||
|
<div class="dp-quickadd__buttons">
|
||||||
|
<button type="button" class="dp-button dp-quickadd__button-submit">
|
||||||
|
<?php echo $translator->translate('JSAVE'); ?>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="dp-button dp-quickadd__button-edit">
|
||||||
|
<?php echo $translator->translate('JACTION_EDIT'); ?>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="dp-button dp-quickadd__button-cancel">
|
||||||
|
<?php echo $translator->translate('JCANCEL'); ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
1
src/html/mod_dpcalendar_mini/index.html
Normal file
1
src/html/mod_dpcalendar_mini/index.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<!DOCTYPE html><title></title>
|
||||||
22
src/html/mod_dpcalendar_upcoming/_scripts.php
Normal file
22
src/html/mod_dpcalendar_upcoming/_scripts.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @package DPCalendar
|
||||||
|
* @copyright Copyright (C) 2018 Digital Peak GmbH. <https://www.digital-peak.com>
|
||||||
|
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
\defined('_JEXEC') or die();
|
||||||
|
|
||||||
|
// Load the required modal JS libraries
|
||||||
|
if ($params->get('show_as_popup') || $params->get('show_map')) {
|
||||||
|
$document->loadScriptFile('default.js', 'mod_dpcalendar_upcoming');
|
||||||
|
$translator->translateJS('COM_DPCALENDAR_CLOSE');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($params->get('show_map')) {
|
||||||
|
$layoutHelper->renderLayout('block.map', $displayData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the stylesheet
|
||||||
|
$document->loadStyleFile(str_replace('_:', '', (string)$params->get('layout', 'default')) . '.css', 'mod_dpcalendar_upcoming');
|
||||||
|
$document->addStyle($params->get('custom_css', ''));
|
||||||
172
src/html/mod_dpcalendar_upcoming/default.php
Normal file
172
src/html/mod_dpcalendar_upcoming/default.php
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @package DPCalendar
|
||||||
|
* @copyright Copyright (C) 2014 Digital Peak GmbH. <https://www.digital-peak.com>
|
||||||
|
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
\defined('_JEXEC') or die();
|
||||||
|
|
||||||
|
use DigitalPeak\Component\DPCalendar\Administrator\Helper\Booking;
|
||||||
|
use DigitalPeak\Component\DPCalendar\Administrator\Helper\DPCalendarHelper;
|
||||||
|
use DigitalPeak\Component\DPCalendar\Administrator\HTML\Block\Icon;
|
||||||
|
use Joomla\CMS\Helper\ModuleHelper;
|
||||||
|
use Joomla\CMS\HTML\HTMLHelper;
|
||||||
|
|
||||||
|
if (!$events) {
|
||||||
|
echo $translator->translate($params->get('no_events_text', 'MOD_DPCALENDAR_UPCOMING_NO_EVENT_TEXT'));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
require ModuleHelper::getLayoutPath('mod_dpcalendar_upcoming', '_scripts');
|
||||||
|
?>
|
||||||
|
<div class="mod-dpcalendar-upcoming mod-dpcalendar-upcoming-default mod-dpcalendar-upcoming-<?php echo $module->id; ?> dp-locations"
|
||||||
|
data-popup="<?php echo $params->get('show_as_popup', 0); ?>">
|
||||||
|
<div class="mod-dpcalendar-upcoming-default__custom-text">
|
||||||
|
<?php echo HTMLHelper::_('content.prepare', $translator->translate($params->get('textbefore', ''))); ?>
|
||||||
|
</div>
|
||||||
|
<div class="mod-dpcalendar-upcoming-default__events">
|
||||||
|
<?php foreach ($groupedEvents as $groupHeading => $events) { ?>
|
||||||
|
<?php if ($groupHeading) { ?>
|
||||||
|
<div class="mod-dpcalendar-upcoming-default__group">
|
||||||
|
<p class="mod-dpcalendar-upcoming-default__heading dp-group-heading"><?php echo $groupHeading; ?></p>
|
||||||
|
<?php } ?>
|
||||||
|
<?php foreach ($events as $event) { ?>
|
||||||
|
<?php $displayData['event'] = $event; ?>
|
||||||
|
<?php $startDate = $dateHelper->getDate($event->start_date, $event->all_day); ?>
|
||||||
|
<div class="mod-dpcalendar-upcoming-default__event dp-event dp-event_<?php echo $event->ongoing_start_date ? ($event->ongoing_end_date ? 'started' : 'finished') : 'future'; ?>">
|
||||||
|
<?php echo $layoutHelper->renderLayout('block.flatcalendar', ['date' => $startDate, 'color' => $event->color]); ?>
|
||||||
|
<div class="mod-dpcalendar-upcoming-default__information">
|
||||||
|
<?php if ($event->state == 3) { ?>
|
||||||
|
<span class="dp-event_canceled">[<?php echo $translator->translate('MOD_DPCALENDAR_UPCOMING_CANCELED'); ?>]</span>
|
||||||
|
<?php } ?>
|
||||||
|
<a href="<?php echo $event->realUrl; ?>" class="dp-event-url dp-link"><?php echo $event->title; ?></a>
|
||||||
|
<?php if ($params->get('show_display_events') && $event->displayEvent->afterDisplayTitle) { ?>
|
||||||
|
<div class="dp-event-display-after-title"><?php echo $event->displayEvent->afterDisplayTitle; ?></div>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if (($params->get('show_location') || $params->get('show_map')) && isset($event->locations) && $event->locations) { ?>
|
||||||
|
<div class="mod-dpcalendar-upcoming-default__location">
|
||||||
|
<?php if ($params->get('show_location')) { ?>
|
||||||
|
<?php echo $layoutHelper->renderLayout('block.icon', ['icon' => Icon::LOCATION]); ?>
|
||||||
|
<?php } ?>
|
||||||
|
<?php foreach ($event->locations as $location) { ?>
|
||||||
|
<div class="dp-location<?php echo $params->get('show_location') ? '' : ' dp-location_hidden'; ?>">
|
||||||
|
<div class="dp-location__details"
|
||||||
|
data-latitude="<?php echo $location->latitude; ?>"
|
||||||
|
data-longitude="<?php echo $location->longitude; ?>"
|
||||||
|
data-title="<?php echo $location->title; ?>"
|
||||||
|
data-color="<?php echo $event->color; ?>"></div>
|
||||||
|
<?php if ($params->get('show_location')) { ?>
|
||||||
|
<a href="<?php echo $router->getLocationRoute($location); ?>" class="dp-location__url dp-link">
|
||||||
|
<span class="dp-location__title"><?php echo $location->title; ?></span>
|
||||||
|
<?php if (!empty($event->roomTitles[$location->id])) { ?>
|
||||||
|
<span class="dp-location__rooms">[<?php echo implode(', ', $event->roomTitles[$location->id]); ?>]</span>
|
||||||
|
<?php } ?>
|
||||||
|
</a>
|
||||||
|
<?php } ?>
|
||||||
|
<div class="dp-location__description">
|
||||||
|
<?php echo $layoutHelper->renderLayout('event.tooltip', $displayData); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
<div class="mod-dpcalendar-upcoming-default__date">
|
||||||
|
<?php echo $layoutHelper->renderLayout(
|
||||||
|
'block.icon',
|
||||||
|
['icon' => Icon::CLOCK, 'title' => $translator->translate('MOD_DPCALENDAR_UPCOMING_DATE')]
|
||||||
|
); ?>
|
||||||
|
<?php echo $dateHelper->getDateStringFromEvent($event, $params->get('date_format'), $params->get('time_format')); ?>
|
||||||
|
</div>
|
||||||
|
<?php if ($event->rrule) { ?>
|
||||||
|
<div class="mod-dpcalendar-upcoming-default__rrule">
|
||||||
|
<?php echo $layoutHelper->renderLayout(
|
||||||
|
'block.icon',
|
||||||
|
['icon' => Icon::RECURRING, 'title' => $translator->translate('MOD_DPCALENDAR_UPCOMING_SERIES')]
|
||||||
|
); ?>
|
||||||
|
<?php echo nl2br((string) $dateHelper->transformRRuleToString($event->rrule, $event->start_date, $event->exdates)); ?>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ($params->get('show_price') && $event->prices) { ?>
|
||||||
|
<?php foreach ($event->prices as $price) { ?>
|
||||||
|
<?php $discounted = Booking::getPriceWithDiscount($price->value, $event); ?>
|
||||||
|
<div class="mod-dpcalendar-upcoming-default__price dp-event-price">
|
||||||
|
<?php echo $layoutHelper->renderLayout(
|
||||||
|
'block.icon',
|
||||||
|
[
|
||||||
|
'icon' => Icon::MONEY,
|
||||||
|
'title' => $translator->translate('MOD_DPCALENDAR_UPCOMING_PRICES')
|
||||||
|
]
|
||||||
|
); ?>
|
||||||
|
<span class="dp-event-price__label">
|
||||||
|
<?php echo $price->label ?: $translator->translate('MOD_DPCALENDAR_UPCOMING_PRICES'); ?>
|
||||||
|
</span>
|
||||||
|
<span class="dp-event-price__regular<?php echo $discounted != $price->value ? ' dp-event-price__regular_has-discount' : ''; ?>">
|
||||||
|
<?php echo $price->value === '' ? '' : DPCalendarHelper::renderPrice($price->value); ?>
|
||||||
|
</span>
|
||||||
|
<?php if ($discounted != $price->value) { ?>
|
||||||
|
<span class="dp-event-price__discount"><?php echo DPCalendarHelper::renderPrice($discounted); ?></span>
|
||||||
|
<?php } ?>
|
||||||
|
<span class="dp-event-price__description">
|
||||||
|
<?php echo $price->description; ?>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
<?php } ?>
|
||||||
|
</div>
|
||||||
|
<?php if ($params->get('show_image', 1) && $event->images->image_intro) { ?>
|
||||||
|
<div class="mod-dpcalendar-upcoming-default__image">
|
||||||
|
<figure class="dp-figure">
|
||||||
|
<a href="<?php echo $event->realUrl; ?>" class="dp-event-url dp-link">
|
||||||
|
<img class="dp-image" src="<?php echo $event->images->image_intro; ?>"
|
||||||
|
aria-label="<?php echo $event->images->image_intro_alt; ?>"
|
||||||
|
alt="<?php echo $event->images->image_intro_alt; ?>"
|
||||||
|
loading="lazy" <?php echo $event->images->image_intro_dimensions; ?>>
|
||||||
|
</a>
|
||||||
|
<?php if ($event->images->image_intro_caption) { ?>
|
||||||
|
<figcaption class="dp-figure__caption"><?php echo $event->images->image_intro_caption; ?></figcaption>
|
||||||
|
<?php } ?>
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ($params->get('show_booking', 1) && Booking::openForBooking($event)) { ?>
|
||||||
|
<a href="<?php echo $router->getBookingFormRouteFromEvent($event, $return, true, $moduleParams->get('default_menu_item', 0)); ?>"
|
||||||
|
class="dp-link dp-link_cta">
|
||||||
|
<?php echo $layoutHelper->renderLayout('block.icon', ['icon' => Icon::BOOK]); ?>
|
||||||
|
<span class="dp-link__text">
|
||||||
|
<?php echo $translator->translate('MOD_DPCALENDAR_UPCOMING_BOOK'); ?>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ($params->get('show_display_events') && $event->displayEvent->beforeDisplayContent) { ?>
|
||||||
|
<div class="dp-event-display-before-content"><?php echo $event->displayEvent->beforeDisplayContent; ?></div>
|
||||||
|
<?php } ?>
|
||||||
|
<div class="mod-dpcalendar-upcoming-default__description">
|
||||||
|
<?php echo $event->truncatedDescription; ?>
|
||||||
|
</div>
|
||||||
|
<?php if ($params->get('show_display_events') && $event->displayEvent->afterDisplayContent) { ?>
|
||||||
|
<div class="dp-event-display-after-content"><?php echo $event->displayEvent->afterDisplayContent; ?></div>
|
||||||
|
<?php } ?>
|
||||||
|
<?php $displayData['event'] = $event; ?>
|
||||||
|
<?php echo $layoutHelper->renderLayout('schema.event', $displayData); ?>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ($groupHeading) { ?>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
<?php } ?>
|
||||||
|
</div>
|
||||||
|
<?php if ($params->get('show_map')) { ?>
|
||||||
|
<div class="mod-dpcalendar-upcoming-default__map dp-map"
|
||||||
|
style="width: <?php echo $params->get('map_width', '100%'); ?>; height: <?php echo $params->get('map_height', '350px'); ?>"
|
||||||
|
data-zoom="<?php echo $params->get('map_zoom', 4); ?>"
|
||||||
|
data-latitude="<?php echo $params->get('map_lat', 47); ?>"
|
||||||
|
data-longitude="<?php echo $params->get('map_long', 4); ?>"
|
||||||
|
data-ask-consent="<?php echo $params->get('map_ask_consent'); ?>">
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
<div class="mod-dpcalendar-upcoming-default__custom-text">
|
||||||
|
<?php echo HTMLHelper::_('content.prepare', $translator->translate($params->get('textafter', ''))); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
1
src/html/mod_dpcalendar_upcoming/index.html
Normal file
1
src/html/mod_dpcalendar_upcoming/index.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<!DOCTYPE html><title></title>
|
||||||
@@ -9,6 +9,11 @@
|
|||||||
|
|
||||||
defined('_JEXEC') or die;
|
defined('_JEXEC') or die;
|
||||||
|
|
||||||
|
// One-time migration from MokoCassiopeia (runs once, creates .migrated marker)
|
||||||
|
if (!file_exists(__DIR__ . '/.migrated')) {
|
||||||
|
require_once __DIR__ . '/helper/migrate.php';
|
||||||
|
}
|
||||||
|
|
||||||
use Joomla\CMS\Factory;
|
use Joomla\CMS\Factory;
|
||||||
use Joomla\CMS\HTML\HTMLHelper;
|
use Joomla\CMS\HTML\HTMLHelper;
|
||||||
use Joomla\CMS\Language\Text;
|
use Joomla\CMS\Language\Text;
|
||||||
@@ -92,8 +97,8 @@ if ($params_favicon_source) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$faviconOutputDir = JPATH_ROOT . '/images/favicons';
|
$faviconOutputDir = JPATH_ROOT . '/media/templates/site/' . $this->template . '/images/favicons';
|
||||||
$faviconUrlBase = Uri::root(true) . '/images/favicons';
|
$faviconUrlBase = Uri::root(true) . '/media/templates/site/' . $this->template . '/images/favicons';
|
||||||
|
|
||||||
if (MokoFaviconHelper::generate($faviconSourceAbs, $faviconOutputDir)) {
|
if (MokoFaviconHelper::generate($faviconSourceAbs, $faviconOutputDir)) {
|
||||||
$faviconHeadTags = MokoFaviconHelper::getHeadTags($faviconUrlBase);
|
$faviconHeadTags = MokoFaviconHelper::getHeadTags($faviconUrlBase);
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ TPL_MOKOONYX_CSS_VARS_BLOCK_COLORS_LABEL="Block Colour System (top-a / top-b / b
|
|||||||
TPL_MOKOONYX_CSS_VARS_BLOCK_COLORS_DESC="Automatic brand colour palette for modules in <code>top-a</code>, <code>top-b</code>, <code>bottom-a</code>, and <code>bottom-b</code> positions. Colours assigned by <code>:nth-child()</code> order — no classes needed.<br><br><strong>Slot palette</strong><br><code>--block-color-1</code> / <code>--block-text-1</code> — 1st module<br><code>--block-color-2</code> / <code>--block-text-2</code> — 2nd module<br><code>--block-color-3</code> / <code>--block-text-3</code> — 3rd module<br><code>--block-color-4</code> / <code>--block-text-4</code> — 4th module<br><br><strong>Named overrides</strong> (add an ID to the module HTML to bypass slot colour)<br><code>--block-highlight-bg</code> / <code>--block-highlight-text</code> — for <code>#block-highlight</code><br><code>--block-cta-bg</code> / <code>--block-cta-text</code> — for <code>#block-cta</code><br><code>--block-alert-bg</code> / <code>--block-alert-text</code> — for <code>#block-alert</code><br><br><strong>Priority:</strong> Named ID > Slot colour. No <code>!important</code> needed — specificity handles it."
|
TPL_MOKOONYX_CSS_VARS_BLOCK_COLORS_DESC="Automatic brand colour palette for modules in <code>top-a</code>, <code>top-b</code>, <code>bottom-a</code>, and <code>bottom-b</code> positions. Colours assigned by <code>:nth-child()</code> order — no classes needed.<br><br><strong>Slot palette</strong><br><code>--block-color-1</code> / <code>--block-text-1</code> — 1st module<br><code>--block-color-2</code> / <code>--block-text-2</code> — 2nd module<br><code>--block-color-3</code> / <code>--block-text-3</code> — 3rd module<br><code>--block-color-4</code> / <code>--block-text-4</code> — 4th module<br><br><strong>Named overrides</strong> (add an ID to the module HTML to bypass slot colour)<br><code>--block-highlight-bg</code> / <code>--block-highlight-text</code> — for <code>#block-highlight</code><br><code>--block-cta-bg</code> / <code>--block-cta-text</code> — for <code>#block-cta</code><br><code>--block-alert-bg</code> / <code>--block-alert-text</code> — for <code>#block-alert</code><br><br><strong>Priority:</strong> Named ID > Slot colour. No <code>!important</code> needed — specificity handles it."
|
||||||
|
|
||||||
TPL_MOKOONYX_CSS_VARS_HEADER_LABEL="Header Background"
|
TPL_MOKOONYX_CSS_VARS_HEADER_LABEL="Header Background"
|
||||||
TPL_MOKOONYX_CSS_VARS_HEADER_DESC="Controls the background of the topbar/header area.<br><code>--header-background-image</code> — CSS <code>background-image</code> value (default: built-in SVG pattern)<br><code>--header-background-attachment</code> — <code>fixed</code> or <code>scroll</code><br><code>--header-background-repeat</code> — e.g. <code>repeat</code>, <code>no-repeat</code><br><code>--header-background-size</code> — e.g. <code>auto</code>, <code>cover</code>, <code>contain</code>"
|
TPL_MOKOONYX_CSS_VARS_HEADER_DESC="Controls the background of the topbar/header area.<br><code>--header-background-color</code> — fallback colour when no image is set (default: <code>#adadad</code> light / <code>#1a1f2b</code> dark). Set <code>--header-background-image: none;</code> to use a solid colour.<br><code>--header-background-image</code> — CSS <code>background-image</code> value (default: built-in SVG pattern)<br><code>--header-background-attachment</code> — <code>fixed</code> or <code>scroll</code><br><code>--header-background-repeat</code> — e.g. <code>repeat</code>, <code>no-repeat</code><br><code>--header-background-size</code> — e.g. <code>auto</code>, <code>cover</code>, <code>contain</code>"
|
||||||
|
|
||||||
TPL_MOKOONYX_CSS_VARS_CONTAINERS_LABEL="Container Backgrounds"
|
TPL_MOKOONYX_CSS_VARS_CONTAINERS_LABEL="Container Backgrounds"
|
||||||
TPL_MOKOONYX_CSS_VARS_CONTAINERS_DESC="Each layout container has its own background variables. Replace <code>{pos}</code> with: <code>below-topbar</code>, <code>top-a</code>, <code>top-b</code>, <code>sidebar</code>, <code>bottom-a</code>, or <code>bottom-b</code>.<br><br><code>--container-{pos}-bg-image</code> — Background image (default: <code>none</code>)<br><code>--container-{pos}-bg-color</code> — Background colour (default: <code>transparent</code>)<br><code>--container-{pos}-bg-position</code> — Background position<br><code>--container-{pos}-bg-attachment</code> — <code>fixed</code> or <code>scroll</code><br><code>--container-{pos}-bg-repeat</code> — Repeat behaviour<br><code>--container-{pos}-bg-size</code> — e.g. <code>cover</code>, <code>auto</code><br><code>--container-{pos}-border</code> — Border shorthand<br><code>--container-{pos}-border-radius</code> — Border radius<br><br>Also: <code>--container-toc-bg</code> / <code>--container-toc-color</code> for the TOC sidebar."
|
TPL_MOKOONYX_CSS_VARS_CONTAINERS_DESC="Each layout container has its own background variables. Replace <code>{pos}</code> with: <code>below-topbar</code>, <code>top-a</code>, <code>top-b</code>, <code>sidebar</code>, <code>bottom-a</code>, or <code>bottom-b</code>.<br><br><code>--container-{pos}-bg-image</code> — Background image (default: <code>none</code>)<br><code>--container-{pos}-bg-color</code> — Background colour (default: <code>transparent</code>)<br><code>--container-{pos}-bg-position</code> — Background position<br><code>--container-{pos}-bg-attachment</code> — <code>fixed</code> or <code>scroll</code><br><code>--container-{pos}-bg-repeat</code> — Repeat behaviour<br><code>--container-{pos}-bg-size</code> — e.g. <code>cover</code>, <code>auto</code><br><code>--container-{pos}-border</code> — Border shorthand<br><code>--container-{pos}-border-radius</code> — Border radius<br><br>Also: <code>--container-toc-bg</code> / <code>--container-toc-color</code> for the TOC sidebar."
|
||||||
@@ -267,6 +267,13 @@ TPL_MOKOONYX_THEME_PREVIEW_FIELDSET_LABEL="Theme Preview"
|
|||||||
TPL_MOKOONYX_THEME_PREVIEW_INTRO="<p>Live preview of all CSS variables, hero variants, block colours, and Bootstrap components rendered with your active theme. Use the <strong>Toggle Light / Dark</strong> button inside the preview to switch modes. This page is also available as a standalone file at <code>templates/mokoonyx/templates/theme-test.html</code>.</p>"
|
TPL_MOKOONYX_THEME_PREVIEW_INTRO="<p>Live preview of all CSS variables, hero variants, block colours, and Bootstrap components rendered with your active theme. Use the <strong>Toggle Light / Dark</strong> button inside the preview to switch modes. This page is also available as a standalone file at <code>templates/mokoonyx/templates/theme-test.html</code>.</p>"
|
||||||
TPL_MOKOONYX_THEME_PREVIEW_FRAME="<iframe src='../templates/mokoonyx/templates/theme-test.html' style='width:100%;height:80vh;border:1px solid #dee2e6;border-radius:.375rem;' loading='lazy' title='Theme test sheet preview'></iframe>"
|
TPL_MOKOONYX_THEME_PREVIEW_FRAME="<iframe src='../templates/mokoonyx/templates/theme-test.html' style='width:100%;height:80vh;border:1px solid #dee2e6;border-radius:.375rem;' loading='lazy' title='Theme test sheet preview'></iframe>"
|
||||||
|
|
||||||
|
; ===== Migration =====
|
||||||
|
TPL_MOKOONYX_MIGRATION_FIELDSET_LABEL="Migration"
|
||||||
|
TPL_MOKOONYX_MIGRATION_NOTE_LABEL="MokoCassiopeia Migration"
|
||||||
|
TPL_MOKOONYX_MIGRATION_NOTE_DESC="MokoOnyx automatically imports settings from MokoCassiopeia on first page load. If you need to re-run the migration, delete the file <code>templates/mokoonyx/.migrated</code> and visit any frontend page. Check <code>administrator/logs/mokoonyx_migrate.log.php</code> to confirm."
|
||||||
|
TPL_MOKOONYX_MIGRATION_RUN_LABEL="Re-run Migration"
|
||||||
|
TPL_MOKOONYX_MIGRATION_RUN_DESC="<strong>To re-run the migration:</strong> Delete <code>templates/mokoonyx/.migrated</code> via FTP or file manager, then visit any page on your site. The migration will run again automatically.<br><br><strong>To uninstall MokoCassiopeia:</strong> Go to <a href='index.php?option=com_installer&view=manage&filter[search]=mokocassiopeia'>Extensions → Manage</a>, find MokoCassiopeia, and click Uninstall."
|
||||||
|
|
||||||
; ===== Misc =====
|
; ===== Misc =====
|
||||||
MOD_BREADCRUMBS_HERE="YOU ARE HERE:"
|
MOD_BREADCRUMBS_HERE="YOU ARE HERE:"
|
||||||
|
|
||||||
|
|||||||
@@ -28,4 +28,4 @@ TPL_MOKOONYX_POSITION_TOP_B="Top-b"
|
|||||||
TPL_MOKOONYX_POSITION_TOPBAR="Top Bar"
|
TPL_MOKOONYX_POSITION_TOPBAR="Top Bar"
|
||||||
TPL_MOKOONYX_POSITION_DRAWER_LEFT="Drawer-Left"
|
TPL_MOKOONYX_POSITION_DRAWER_LEFT="Drawer-Left"
|
||||||
TPL_MOKOONYX_POSITION_DRAWER_RIGHT="Drawer-Right"
|
TPL_MOKOONYX_POSITION_DRAWER_RIGHT="Drawer-Right"
|
||||||
TPL_MOKOONYX_XML_DESCRIPTION="<h3>MokoOnyx Template Description</h3> <p> <strong>MokoOnyx</strong> continues Joomla’s tradition of space-themed default templates— building on the legacy of <em>Solarflare</em> (Joomla 1.0), <em>Milkyway</em> (Joomla 1.5), and <em>Protostar</em> (Joomla 3.0). </p> <p> This template is a customized fork of the <strong>Cassiopeia</strong> template introduced in Joomla 4, preserving its modern, accessible, and mobile-first foundation while introducing new stylistic enhancements and structural refinements specifically tailored for use by Moko Consulting. </p> <h4>Custom Colour Themes</h4> <p> Starter palette files are included with the template. To create a custom colour scheme, copy <code>templates/mokoonyx/templates/light.custom.css</code> to <code>media/templates/site/mokoonyx/css/theme/light.custom.css</code>, or <code>templates/mokoonyx/templates/dark.custom.css</code> to <code>media/templates/site/mokoonyx/css/theme/dark.custom.css</code>. Customise the CSS variables to match your brand, then activate your palette in <em>System → Site Templates → MokoOnyx → Theme tab</em> by selecting "Custom" for the Light or Dark Mode Palette. A full variable reference is available in the <em>CSS Variables</em> tab in template options. </p> <h4>Custom CSS & JavaScript</h4> <p> For site-specific styles and scripts that should survive template updates, create the following files: </p> <ul> <li><code>media/templates/site/mokoonyx/css/user.css</code> — loaded on every page for custom CSS overrides.</li> <li><code>media/templates/site/mokoonyx/js/user.js</code> — loaded on every page for custom JavaScript.</li> </ul> <p> These files are gitignored and will not be overwritten by template updates. </p> <h4>Code Attribution</h4> <p> This template is based on the original <strong>Cassiopeia</strong> template developed by the <a href=\"https://www.joomla.org\" target=\"_blank\" rel=\"noopener\">Joomla! Project</a> and released under the GNU General Public License. </p> <p> Modifications and enhancements have been made by Moko Consulting in accordance with open-source licensing standards. </p> <p> It includes integration with <a href=\"https://afeld.github.io/bootstrap-toc/\" target=\"_blank\" rel=\"noopener\">Bootstrap TOC</a>, an open-source table of contents generator by A. Feld, licensed under the MIT License. </p> <p> All third-party libraries and assets remain the property of their respective authors and are credited within their source files where applicable. </p>"
|
TPL_MOKOONYX_XML_DESCRIPTION="<p><img src=\"https://img.shields.io/gitea/v/release/MokoConsulting/MokoOnyx?gitea_url=https%3A%2F%2Fgit.mokoconsulting.tech&logo=gitea&logoColor=white&label=version\" alt=\"Version\" /> <img src=\"https://img.shields.io/badge/license-GPL--3.0--or--later-green.svg?logo=gnu&logoColor=white\" alt=\"License\" /> <img src=\"https://img.shields.io/badge/Joomla-5.x%20%7C%206.x-red.svg?logo=joomla&logoColor=white\" alt=\"Joomla\" /> <img src=\"https://img.shields.io/badge/PHP-8.1%2B-777BB4.svg?logo=php&logoColor=white\" alt=\"PHP\" /></p> <h3>MokoOnyx Template Description</h3> <p> <strong>MokoOnyx</strong> continues Joomla’s tradition of space-themed default templates— building on the legacy of <em>Solarflare</em> (Joomla 1.0), <em>Milkyway</em> (Joomla 1.5), and <em>Protostar</em> (Joomla 3.0). </p> <p> This template is a customized fork of the <strong>Cassiopeia</strong> template introduced in Joomla 4, preserving its modern, accessible, and mobile-first foundation while introducing new stylistic enhancements and structural refinements specifically tailored for use by Moko Consulting. </p> <h4>Custom Colour Themes</h4> <p> Starter palette files are included with the template. To create a custom colour scheme, copy <code>templates/mokoonyx/templates/light.custom.css</code> to <code>media/templates/site/mokoonyx/css/theme/light.custom.css</code>, or <code>templates/mokoonyx/templates/dark.custom.css</code> to <code>media/templates/site/mokoonyx/css/theme/dark.custom.css</code>. Customise the CSS variables to match your brand, then activate your palette in <em>System → Site Templates → MokoOnyx → Theme tab</em> by selecting "Custom" for the Light or Dark Mode Palette. A full variable reference is available in the <em>CSS Variables</em> tab in template options. </p> <h4>Custom CSS & JavaScript</h4> <p> For site-specific styles and scripts that should survive template updates, create the following files: </p> <ul> <li><code>media/templates/site/mokoonyx/css/user.css</code> — loaded on every page for custom CSS overrides.</li> <li><code>media/templates/site/mokoonyx/js/user.js</code> — loaded on every page for custom JavaScript.</li> </ul> <p> These files are gitignored and will not be overwritten by template updates. </p> <h4>Code Attribution</h4> <p> This template is based on the original <strong>Cassiopeia</strong> template developed by the <a href=\"https://www.joomla.org\" target=\"_blank\" rel=\"noopener\">Joomla! Project</a> and released under the GNU General Public License. </p> <p> Modifications and enhancements have been made by Moko Consulting in accordance with open-source licensing standards. </p> <p> It includes integration with <a href=\"https://afeld.github.io/bootstrap-toc/\" target=\"_blank\" rel=\"noopener\">Bootstrap TOC</a>, an open-source table of contents generator by A. Feld, licensed under the MIT License. </p> <p> All third-party libraries and assets remain the property of their respective authors and are credited within their source files where applicable. </p>"
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ TPL_MOKOONYX_CSS_VARS_BLOCK_COLORS_LABEL="Block Color System (top-a / top-b / bo
|
|||||||
TPL_MOKOONYX_CSS_VARS_BLOCK_COLORS_DESC="Automatic brand color palette for modules in <code>top-a</code>, <code>top-b</code>, <code>bottom-a</code>, and <code>bottom-b</code> positions. Colors assigned by <code>:nth-child()</code> order — no classes needed.<br><br><strong>Slot palette</strong><br><code>--block-color-1</code> / <code>--block-text-1</code> — 1st module<br><code>--block-color-2</code> / <code>--block-text-2</code> — 2nd module<br><code>--block-color-3</code> / <code>--block-text-3</code> — 3rd module<br><code>--block-color-4</code> / <code>--block-text-4</code> — 4th module<br><br><strong>Named overrides</strong> (add an ID to the module HTML to bypass slot color)<br><code>--block-highlight-bg</code> / <code>--block-highlight-text</code> — for <code>#block-highlight</code><br><code>--block-cta-bg</code> / <code>--block-cta-text</code> — for <code>#block-cta</code><br><code>--block-alert-bg</code> / <code>--block-alert-text</code> — for <code>#block-alert</code><br><br><strong>Priority:</strong> Named ID > Slot color. No <code>!important</code> needed — specificity handles it."
|
TPL_MOKOONYX_CSS_VARS_BLOCK_COLORS_DESC="Automatic brand color palette for modules in <code>top-a</code>, <code>top-b</code>, <code>bottom-a</code>, and <code>bottom-b</code> positions. Colors assigned by <code>:nth-child()</code> order — no classes needed.<br><br><strong>Slot palette</strong><br><code>--block-color-1</code> / <code>--block-text-1</code> — 1st module<br><code>--block-color-2</code> / <code>--block-text-2</code> — 2nd module<br><code>--block-color-3</code> / <code>--block-text-3</code> — 3rd module<br><code>--block-color-4</code> / <code>--block-text-4</code> — 4th module<br><br><strong>Named overrides</strong> (add an ID to the module HTML to bypass slot color)<br><code>--block-highlight-bg</code> / <code>--block-highlight-text</code> — for <code>#block-highlight</code><br><code>--block-cta-bg</code> / <code>--block-cta-text</code> — for <code>#block-cta</code><br><code>--block-alert-bg</code> / <code>--block-alert-text</code> — for <code>#block-alert</code><br><br><strong>Priority:</strong> Named ID > Slot color. No <code>!important</code> needed — specificity handles it."
|
||||||
|
|
||||||
TPL_MOKOONYX_CSS_VARS_HEADER_LABEL="Header Background"
|
TPL_MOKOONYX_CSS_VARS_HEADER_LABEL="Header Background"
|
||||||
TPL_MOKOONYX_CSS_VARS_HEADER_DESC="Controls the background of the topbar/header area.<br><code>--header-background-image</code> — CSS <code>background-image</code> value (default: built-in SVG pattern)<br><code>--header-background-attachment</code> — <code>fixed</code> or <code>scroll</code><br><code>--header-background-repeat</code> — e.g. <code>repeat</code>, <code>no-repeat</code><br><code>--header-background-size</code> — e.g. <code>auto</code>, <code>cover</code>, <code>contain</code>"
|
TPL_MOKOONYX_CSS_VARS_HEADER_DESC="Controls the background of the topbar/header area.<br><code>--header-background-color</code> — fallback color when no image is set (default: <code>#adadad</code> light / <code>#1a1f2b</code> dark). Set <code>--header-background-image: none;</code> to use a solid color.<br><code>--header-background-image</code> — CSS <code>background-image</code> value (default: built-in SVG pattern)<br><code>--header-background-attachment</code> — <code>fixed</code> or <code>scroll</code><br><code>--header-background-repeat</code> — e.g. <code>repeat</code>, <code>no-repeat</code><br><code>--header-background-size</code> — e.g. <code>auto</code>, <code>cover</code>, <code>contain</code>"
|
||||||
|
|
||||||
TPL_MOKOONYX_CSS_VARS_CONTAINERS_LABEL="Container Backgrounds"
|
TPL_MOKOONYX_CSS_VARS_CONTAINERS_LABEL="Container Backgrounds"
|
||||||
TPL_MOKOONYX_CSS_VARS_CONTAINERS_DESC="Each layout container has its own background variables. Replace <code>{pos}</code> with: <code>below-topbar</code>, <code>top-a</code>, <code>top-b</code>, <code>sidebar</code>, <code>bottom-a</code>, or <code>bottom-b</code>.<br><br><code>--container-{pos}-bg-image</code> — Background image (default: <code>none</code>)<br><code>--container-{pos}-bg-color</code> — Background color (default: <code>transparent</code>)<br><code>--container-{pos}-bg-position</code> — Background position<br><code>--container-{pos}-bg-attachment</code> — <code>fixed</code> or <code>scroll</code><br><code>--container-{pos}-bg-repeat</code> — Repeat behavior<br><code>--container-{pos}-bg-size</code> — e.g. <code>cover</code>, <code>auto</code><br><code>--container-{pos}-border</code> — Border shorthand<br><code>--container-{pos}-border-radius</code> — Border radius<br><br>Also: <code>--container-toc-bg</code> / <code>--container-toc-color</code> for the TOC sidebar."
|
TPL_MOKOONYX_CSS_VARS_CONTAINERS_DESC="Each layout container has its own background variables. Replace <code>{pos}</code> with: <code>below-topbar</code>, <code>top-a</code>, <code>top-b</code>, <code>sidebar</code>, <code>bottom-a</code>, or <code>bottom-b</code>.<br><br><code>--container-{pos}-bg-image</code> — Background image (default: <code>none</code>)<br><code>--container-{pos}-bg-color</code> — Background color (default: <code>transparent</code>)<br><code>--container-{pos}-bg-position</code> — Background position<br><code>--container-{pos}-bg-attachment</code> — <code>fixed</code> or <code>scroll</code><br><code>--container-{pos}-bg-repeat</code> — Repeat behavior<br><code>--container-{pos}-bg-size</code> — e.g. <code>cover</code>, <code>auto</code><br><code>--container-{pos}-border</code> — Border shorthand<br><code>--container-{pos}-border-radius</code> — Border radius<br><br>Also: <code>--container-toc-bg</code> / <code>--container-toc-color</code> for the TOC sidebar."
|
||||||
@@ -267,6 +267,13 @@ TPL_MOKOONYX_THEME_PREVIEW_FIELDSET_LABEL="Theme Preview"
|
|||||||
TPL_MOKOONYX_THEME_PREVIEW_INTRO="<p>Live preview of all CSS variables, hero variants, block colors, and Bootstrap components rendered with your active theme. Use the <strong>Toggle Light / Dark</strong> button inside the preview to switch modes. This page is also available as a standalone file at <code>templates/mokoonyx/templates/theme-test.html</code>.</p>"
|
TPL_MOKOONYX_THEME_PREVIEW_INTRO="<p>Live preview of all CSS variables, hero variants, block colors, and Bootstrap components rendered with your active theme. Use the <strong>Toggle Light / Dark</strong> button inside the preview to switch modes. This page is also available as a standalone file at <code>templates/mokoonyx/templates/theme-test.html</code>.</p>"
|
||||||
TPL_MOKOONYX_THEME_PREVIEW_FRAME="<iframe src='../templates/mokoonyx/templates/theme-test.html' style='width:100%;height:80vh;border:1px solid #dee2e6;border-radius:.375rem;' loading='lazy' title='Theme test sheet preview'></iframe>"
|
TPL_MOKOONYX_THEME_PREVIEW_FRAME="<iframe src='../templates/mokoonyx/templates/theme-test.html' style='width:100%;height:80vh;border:1px solid #dee2e6;border-radius:.375rem;' loading='lazy' title='Theme test sheet preview'></iframe>"
|
||||||
|
|
||||||
|
; ===== Migration =====
|
||||||
|
TPL_MOKOONYX_MIGRATION_FIELDSET_LABEL="Migration"
|
||||||
|
TPL_MOKOONYX_MIGRATION_NOTE_LABEL="MokoCassiopeia Migration"
|
||||||
|
TPL_MOKOONYX_MIGRATION_NOTE_DESC="MokoOnyx automatically imports settings from MokoCassiopeia on first page load. If you need to re-run the migration, delete the file <code>templates/mokoonyx/.migrated</code> and visit any frontend page. Check <code>administrator/logs/mokoonyx_migrate.log.php</code> to confirm."
|
||||||
|
TPL_MOKOONYX_MIGRATION_RUN_LABEL="Re-run Migration"
|
||||||
|
TPL_MOKOONYX_MIGRATION_RUN_DESC="<strong>To re-run the migration:</strong> Delete <code>templates/mokoonyx/.migrated</code> via FTP or file manager, then visit any page on your site. The migration will run again automatically.<br><br><strong>To uninstall MokoCassiopeia:</strong> Go to <a href='index.php?option=com_installer&view=manage&filter[search]=mokocassiopeia'>Extensions → Manage</a>, find MokoCassiopeia, and click Uninstall."
|
||||||
|
|
||||||
; ===== Misc =====
|
; ===== Misc =====
|
||||||
MOD_BREADCRUMBS_HERE="YOU ARE HERE:"
|
MOD_BREADCRUMBS_HERE="YOU ARE HERE:"
|
||||||
|
|
||||||
|
|||||||
1
src/media/css/editor.min.css
vendored
1
src/media/css/editor.min.css
vendored
@@ -1 +0,0 @@
|
|||||||
@charset "UTF-8";body{font-size:1rem;font-weight:400;line-height:1.5;color:#22262a;background-color:#fff}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:700;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}h2{font-size:calc(1.325rem + .9vw)}h3{font-size:calc(1.3rem + .6vw)}h4{font-size:calc(1.275rem + .3vw)}h5{font-size:1.25rem}h6{font-size:1rem}a{text-decoration:none}a:link{color:#224faa}a:hover{color:#424077}p{margin-top:0;margin-bottom:1rem}hr#system-readmore{color:red;border:1px dashed red}span[lang]{padding:2px;border:1px dashed #bbb}span[lang]:after{font-size:smaller;color:red;vertical-align:super;content:attr(lang)}
|
|
||||||
@@ -101,26 +101,34 @@ form {
|
|||||||
width: 280px;
|
width: 280px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer-toggle-left{
|
button.drawer-toggle-left,
|
||||||
|
.drawer-toggle-left.btn {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
z-index: 1050;
|
z-index: 1050;
|
||||||
background-color: var(--nav-bg-color);
|
background-color: var(--accent-color, var(--color-primary, #2a69b8)) !important;
|
||||||
color: var(--nav-text-color, gray);
|
color: var(--accent-color-text, #fff) !important;
|
||||||
|
border-color: var(--accent-color, var(--color-primary, #2a69b8)) !important;
|
||||||
padding-left: .5rem;
|
padding-left: .5rem;
|
||||||
padding-right: .5rem;
|
padding-right: .5rem;
|
||||||
|
border-top-left-radius: 0 !important;
|
||||||
|
border-bottom-left-radius: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer-toggle-right{
|
button.drawer-toggle-right,
|
||||||
|
.drawer-toggle-right.btn {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
z-index: 1050;
|
z-index: 1050;
|
||||||
background-color: var(--nav-bg-color);
|
background-color: var(--accent-color, var(--color-primary, #2a69b8)) !important;
|
||||||
color: var(--nav-text-color, gray);
|
color: var(--accent-color-text, #fff) !important;
|
||||||
|
border-color: var(--accent-color, var(--color-primary, #2a69b8)) !important;
|
||||||
padding-left: .5rem;
|
padding-left: .5rem;
|
||||||
padding-right: .5rem;
|
padding-right: .5rem;
|
||||||
|
border-top-right-radius: 0 !important;
|
||||||
|
border-bottom-right-radius: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
@@ -13623,12 +13631,14 @@ a.navbar-brand {
|
|||||||
.btn-primary {
|
.btn-primary {
|
||||||
background-color: var(--color-primary, #112855);
|
background-color: var(--color-primary, #112855);
|
||||||
border-color: var(--color-primary, #112855);
|
border-color: var(--color-primary, #112855);
|
||||||
|
color: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary:active,
|
.btn-primary:active,
|
||||||
.btn-primary:focus {
|
.btn-primary:focus {
|
||||||
background-color: var(--color-active);
|
background-color: var(--color-active);
|
||||||
border-color: var(--color-active);
|
border-color: var(--color-active);
|
||||||
|
color: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-group {
|
.btn-group {
|
||||||
@@ -13925,8 +13935,22 @@ meter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── HERO CARD BASE ── */
|
/* ── HERO WRAPPER (module wrapper, full-width centering container) ── */
|
||||||
.hero {
|
.hero {
|
||||||
|
display: flex;
|
||||||
|
justify-content: var(--hero-justify, center);
|
||||||
|
align-items: var(--hero-align, center);
|
||||||
|
width: 100%;
|
||||||
|
min-height: var(--hero-min-height, 75vh);
|
||||||
|
background-image: var(--hero-bg-image, none);
|
||||||
|
background-repeat: var(--hero-bg-repeat, no-repeat);
|
||||||
|
background-attachment: var(--hero-bg-attachment, fixed);
|
||||||
|
background-position: var(--hero-bg-position, center);
|
||||||
|
background-size: var(--hero-bg-size, cover);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── HERO CARD BASE (the .card inside the hero wrapper) ── */
|
||||||
|
.hero .card {
|
||||||
max-width: var(--hero-card-max-width, 600px);
|
max-width: var(--hero-card-max-width, 600px);
|
||||||
padding: var(--hero-card-padding-y, 3rem) var(--hero-card-padding-x, 2rem);
|
padding: var(--hero-card-padding-y, 3rem) var(--hero-card-padding-x, 2rem);
|
||||||
background-color: var(--hero-card-bg, var(--hero-primary-bg-color, #0d1e3a));
|
background-color: var(--hero-card-bg, var(--hero-primary-bg-color, #0d1e3a));
|
||||||
@@ -13935,17 +13959,21 @@ meter {
|
|||||||
background-position: center;
|
background-position: center;
|
||||||
color: var(--hero-card-color, var(--hero-primary-color, #f1f5f9));
|
color: var(--hero-card-color, var(--hero-primary-color, #f1f5f9));
|
||||||
border-radius: var(--hero-card-border-radius, .5rem);
|
border-radius: var(--hero-card-border-radius, .5rem);
|
||||||
|
text-align: var(--hero-card-text-align, center);
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── PRIMARY VARIANT (uses default card vars) ── */
|
/* ── PRIMARY VARIANT ── */
|
||||||
.hero#primary {
|
.hero#primary .card {
|
||||||
background-color: var(--hero-card-bg, #0d1e3a);
|
background-color: var(--hero-card-bg, #0d1e3a);
|
||||||
background-image: var(--hero-card-overlay, none);
|
background-image: var(--hero-card-overlay, none);
|
||||||
color: var(--hero-card-color, #f1f5f9);
|
color: var(--hero-card-color, #f1f5f9);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── SECONDARY / ALTERNATIVE VARIANT ── */
|
/* ── SECONDARY / ALTERNATIVE VARIANT ── */
|
||||||
.hero#secondary {
|
.hero#secondary .card {
|
||||||
max-width: var(--hero-alt-card-max-width, 600px);
|
max-width: var(--hero-alt-card-max-width, 600px);
|
||||||
padding: var(--hero-alt-card-padding-y, 3rem) var(--hero-alt-card-padding-x, 2rem);
|
padding: var(--hero-alt-card-padding-y, 3rem) var(--hero-alt-card-padding-x, 2rem);
|
||||||
background-color: var(--hero-alt-card-bg, #080f1e);
|
background-color: var(--hero-alt-card-bg, #080f1e);
|
||||||
@@ -13967,14 +13995,14 @@ meter {
|
|||||||
background-image: none;
|
background-image: none;
|
||||||
}
|
}
|
||||||
.hero {
|
.hero {
|
||||||
max-width: 100%;
|
|
||||||
width: 100%;
|
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
min-height: 100dvh;
|
min-height: 100dvh;
|
||||||
border-radius: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
}
|
||||||
|
.hero .card {
|
||||||
|
max-width: 100%;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14193,10 +14221,11 @@ fieldset>* {
|
|||||||
|
|
||||||
.container-header {
|
.container-header {
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
background: var(--header-background-image, url('../../../../../../media/templates/site/mokoonyx/images/bg.svg'));
|
background-color: var(--header-background-color, #adadad);
|
||||||
|
background-image: var(--header-background-image, url('../../../../../../media/templates/site/mokoonyx/images/bg.svg'));
|
||||||
background-size: var(--header-background-size, auto);
|
background-size: var(--header-background-size, auto);
|
||||||
box-shadow: 0 5px 5px hsla(0, 0%, 0%, 0.03) inset;
|
|
||||||
background-repeat: var(--header-background-repeat, repeat);
|
background-repeat: var(--header-background-repeat, repeat);
|
||||||
|
box-shadow: 0 5px 5px hsla(0, 0%, 0%, 0.03) inset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sticky header: override z-index to stay above all content */
|
/* Sticky header: override z-index to stay above all content */
|
||||||
@@ -14994,6 +15023,10 @@ iframe {
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#maincontent {
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
.container-component,
|
.container-component,
|
||||||
.sidebar-left,
|
.sidebar-left,
|
||||||
.sidebar-right {
|
.sidebar-right {
|
||||||
@@ -16955,11 +16988,14 @@ body:not(.has-sidebar-right) .site-grid .container-component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
|
--bs-btn-color: #000;
|
||||||
--bs-btn-bg: #333;
|
--bs-btn-bg: #333;
|
||||||
--bs-btn-border-color: #333;
|
--bs-btn-border-color: #333;
|
||||||
|
--bs-btn-hover-color: #000;
|
||||||
--bs-btn-hover-bg: #555;
|
--bs-btn-hover-bg: #555;
|
||||||
--bs-btn-hover-border-color: #555;
|
--bs-btn-hover-border-color: #555;
|
||||||
--bs-btn-focus-shadow-rgb: 49, 132, 253;
|
--bs-btn-focus-shadow-rgb: 49, 132, 253;
|
||||||
|
--bs-btn-active-color: #000;
|
||||||
--bs-btn-active-bg: #555;
|
--bs-btn-active-bg: #555;
|
||||||
--bs-btn-active-border-color: #555;
|
--bs-btn-active-border-color: #555;
|
||||||
--bs-btn-disabled-bg: #A0A0A0;
|
--bs-btn-disabled-bg: #A0A0A0;
|
||||||
@@ -18945,7 +18981,7 @@ nav[data-toggle=toc] .nav-link.active+ul{
|
|||||||
|
|
||||||
/* ===== BOOTSTRAP & JOOMLA BUTTONS ===== */
|
/* ===== BOOTSTRAP & JOOMLA BUTTONS ===== */
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
--btn-color: white;
|
--btn-color: #000;
|
||||||
--btn-bg: var(--color-primary, #112855);
|
--btn-bg: var(--color-primary, #112855);
|
||||||
--btn-border-color: var(--color-primary, #112855);
|
--btn-border-color: var(--color-primary, #112855);
|
||||||
--btn-hover-bg: color-mix(in srgb, var(--color-primary, #112855) 85%, black);
|
--btn-hover-bg: color-mix(in srgb, var(--color-primary, #112855) 85%, black);
|
||||||
|
|||||||
1
src/media/css/template.min.css
vendored
1
src/media/css/template.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -210,6 +210,7 @@ color-scheme: dark;
|
|||||||
|
|
||||||
|
|
||||||
/* ===== HEADER BACKGROUND ===== */
|
/* ===== HEADER BACKGROUND ===== */
|
||||||
|
--header-background-color: #1a1f2b;
|
||||||
--header-background-image: url('../../../../../../media/templates/site/mokoonyx/images/bg.svg');
|
--header-background-image: url('../../../../../../media/templates/site/mokoonyx/images/bg.svg');
|
||||||
--header-background-attachment: fixed;
|
--header-background-attachment: fixed;
|
||||||
--header-background-repeat: repeat;
|
--header-background-repeat: repeat;
|
||||||
|
|||||||
1
src/media/css/theme/dark.standard.min.css
vendored
1
src/media/css/theme/dark.standard.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -209,6 +209,7 @@ color-scheme: light;
|
|||||||
|
|
||||||
|
|
||||||
/* ===== HEADER BACKGROUND ===== */
|
/* ===== HEADER BACKGROUND ===== */
|
||||||
|
--header-background-color: #adadad;
|
||||||
--header-background-image: url('../../../../../../media/templates/site/mokoonyx/images/bg.svg');
|
--header-background-image: url('../../../../../../media/templates/site/mokoonyx/images/bg.svg');
|
||||||
--header-background-attachment: fixed;
|
--header-background-attachment: fixed;
|
||||||
--header-background-repeat: repeat;
|
--header-background-repeat: repeat;
|
||||||
|
|||||||
1
src/media/css/theme/light.standard.min.css
vendored
1
src/media/css/theme/light.standard.min.css
vendored
File diff suppressed because one or more lines are too long
1
src/media/js/gtm.min.js
vendored
1
src/media/js/gtm.min.js
vendored
@@ -1 +0,0 @@
|
|||||||
(()=>{"use strict";const e=window,t={},n=e=>{const t=(()=>{const e=document.currentScript;return e||(Array.from(document.getElementsByTagName("script")).reverse().find(e=>(e.getAttribute("src")||"").includes("/gtm.js"))||null)})(),n=document.documentElement,o=document.body,a=document.querySelector(`meta[name="moko:gtm-${e}"]`);return t&&t.dataset&&t.dataset[e]||n&&n.dataset&&n.dataset[e]||o&&o.dataset&&o.dataset[e]||a&&a.getAttribute("content")||null},o=(e,t=!1)=>{if(null==e)return t;const n=String(e).trim().toLowerCase();return!!["1","true","yes","y","on"].includes(n)||!["0","false","no","n","off"].includes(n)&&t},a=(...e)=>{if(r.debug)try{console.info("[moko-gtm]",...e)}catch(e){}},r={id:"",dataLayerName:"dataLayer",debug:!1,ignoreDNT:!1,blockOnDev:!0,envAuth:"",envPreview:"",consentDefault:{analytics_storage:"granted",functionality_storage:"granted",security_storage:"granted",ad_storage:"denied",ad_user_data:"denied",ad_personalization:"denied"},pageVars:()=>({})},d=(e,t={})=>{const n={...e};for(const e in t){if(!Object.prototype.hasOwnProperty.call(t,e))continue;const o=t[e];o&&"object"==typeof o&&!Array.isArray(o)?n[e]={...n[e]||{},...o}:void 0!==o&&(n[e]=o)}return n},i=()=>{const t=e.MOKO_GTM_OPTIONS&&"object"==typeof e.MOKO_GTM_OPTIONS?e.MOKO_GTM_OPTIONS:{},a=n("id")||e.MOKO_GTM_ID||"",r=n("dataLayer")||"",d=n("debug"),i=n("ignoreDnt"),c=n("blockOnDev"),s=n("envAuth")||"",u=n("envPreview")||"";return{id:a||t.id||"",dataLayerName:r||t.dataLayerName||void 0,debug:o(d,!!t.debug),ignoreDNT:o(i,!!t.ignoreDNT),blockOnDev:o(c,t.blockOnDev??!0),envAuth:s||t.envAuth||"",envPreview:u||t.envPreview||"",consentDefault:t.consentDefault||void 0,pageVars:"function"==typeof t.pageVars?t.pageVars:void 0}},c=()=>{const t=r.dataLayerName;return e[t]=e[t]||[],e[t]},s=(...e)=>{c().push(arguments.length>1?e:e[0]),a("gtag push:",e)};t.push=(...e)=>s(...e),t.setConsent=e=>{s("consent","update",e||{})},t.isLoaded=()=>!!document.querySelector('script[src*="googletagmanager.com/gtm.js"]'),t.config=()=>({...r});const u=()=>{if(!r.id)return void a("GTM ID missing; aborting load.");if(t.isLoaded())return void a("GTM already loaded; skipping duplicate injection.");c().push({"gtm.start":(new Date).getTime(),event:"gtm.js"});const e=document.getElementsByTagName("script")[0],n=document.createElement("script");n.async=!0,n.src=`https://www.googletagmanager.com/gtm.js?id=${encodeURIComponent(r.id)}${"dataLayer"!==r.dataLayerName?`&l=${encodeURIComponent(r.dataLayerName)}`:""}${(()=>{const e=[];return r.envAuth&&e.push(`gtm_auth=${encodeURIComponent(r.envAuth)}`),r.envPreview&&e.push(`gtm_preview=${encodeURIComponent(r.envPreview)}`,"gtm_cookies_win=x"),e.length?`&${e.join("&")}`:""})()}`,e&&e.parentNode?e.parentNode.insertBefore(n,e):(document.head||document.documentElement).appendChild(n),a("Injected GTM script:",n.src)},g=()=>!r.ignoreDNT&&(()=>{const e=navigator,t=(e.doNotTrack||e.msDoNotTrack||e.navigator&&e.navigator.doNotTrack||"").toString().toLowerCase();return"1"===t||"yes"===t})()?(a("DNT is enabled; blocking GTM load (set ignoreDNT=true to override)."),!1):!r.blockOnDev||!(()=>{const t=e.location&&e.location.hostname||"";return"localhost"===t||"127.0.0.1"===t||t.endsWith(".local")||t.endsWith(".test")})()||(a("Development host detected; blocking GTM load (set blockOnDev=false to override)."),!1);t.init=(e={})=>{const t=i(),n=d(r,d(t,e));Object.assign(r,n),a("Config:",r),c(),s("consent","default",r.consentDefault),a("Applied default consent:",r.consentDefault),(()=>{const e={event:"moko.page_init",page_title:document.title||"",page_language:document.documentElement&&document.documentElement.lang||"",..."function"==typeof r.pageVars&&r.pageVars()||{}};s(e)})(),g()?u():a("GTM load prevented by configuration or environment.")};const m=()=>{!(!i().id&&!e.MOKO_GTM_ID)?t.init():a("No GTM ID detected; awaiting manual init via window.mokoGTM.init({ id: 'GTM-XXXXXXX' }).")};"complete"===document.readyState||"interactive"===document.readyState?setTimeout(m,0):document.addEventListener("DOMContentLoaded",m,{once:!0}),e.mokoGTM=t;try{const e=i();o(e.debug,!1)&&(r.debug=!0,a("Ready. You can call window.mokoGTM.init({ id: 'GTM-XXXXXXX' })."))}catch(e){}})();
|
|
||||||
1
src/media/js/template.min.js
vendored
1
src/media/js/template.min.js
vendored
@@ -1 +0,0 @@
|
|||||||
!function(e,t){"use strict";var a="theme",n=e.matchMedia("(prefers-color-scheme: dark)"),r=t.documentElement;function o(e){r.setAttribute("data-bs-theme",e),r.setAttribute("data-aria-theme",e);try{localStorage.setItem(a,e)}catch(e){}}function d(){try{localStorage.removeItem(a)}catch(e){}}function i(){return n.matches?"dark":"light"}function c(){try{return localStorage.getItem(a)}catch(e){return null}}function l(){if(!t.getElementById("mokoThemeFab")){var a,l=t.createElement("div");l.id="mokoThemeFab",l.className=(a=(t.body.getAttribute("data-theme-fab-pos")||"br").toLowerCase(),/^(br|bl|tr|tl)$/.test(a)||(a="br"),"pos-"+a);var s=t.createElement("span");s.className="label",s.textContent="Light";var u=t.createElement("button");u.id="mokoThemeSwitch",u.type="button",u.setAttribute("role","switch"),u.setAttribute("aria-label","Toggle dark mode"),u.setAttribute("aria-checked","false");var m=t.createElement("span");m.className="switch";var h=t.createElement("span");h.className="knob",m.appendChild(h),u.appendChild(m);var f=t.createElement("span");f.className="label",f.textContent="Dark";var b=t.createElement("button");b.id="mokoThemeAuto",b.type="button",b.className="btn btn-sm btn-link text-decoration-none px-2",b.setAttribute("aria-label","Follow system theme"),b.textContent="Auto",u.addEventListener("click",function(){var e="dark"===(r.getAttribute("data-bs-theme")||"light").toLowerCase()?"light":"dark";o(e),u.setAttribute("aria-checked","dark"===e?"true":"false");var a=t.querySelector('meta[name="theme-color"]');a&&a.setAttribute("content","dark"===e?"#0f1115":"#ffffff")}),b.addEventListener("click",function(){d();var e=i();o(e),u.setAttribute("aria-checked","dark"===e?"true":"false")});var g=function(){if(!c()){var e=i();o(e),u.setAttribute("aria-checked","dark"===e?"true":"false")}};"function"==typeof n.addEventListener?n.addEventListener("change",g):"function"==typeof n.addListener&&n.addListener(g);var p=c()||i();u.setAttribute("aria-checked","dark"===p?"true":"false"),l.appendChild(s),l.appendChild(u),l.appendChild(f),l.appendChild(b),t.body.appendChild(l),e.mokoThemeFabStatus=function(){var a=t.getElementById("mokoThemeFab");if(!a)return{mounted:!1};var n=a.getBoundingClientRect();return{mounted:!0,rect:{top:n.top,left:n.left,width:n.width,height:n.height},zIndex:e.getComputedStyle(a).zIndex,posClass:a.className}},setTimeout(function(){var e=l.getBoundingClientRect();(e.width<10||e.height<10)&&(l.classList.add("debug-outline"),console.warn("[moko] Theme FAB mounted but appears too small — check CSS collisions."))},50)}}function s(){e.scrollY>50?t.body.classList.add("scrolled"):t.body.classList.remove("scrolled")}function u(){var a=t.getElementById("back-top");a&&a.addEventListener("click",function(t){t.preventDefault(),e.scrollTo({top:0,behavior:"smooth"})})}function m(){!function(){var e=c()||i();o(e);var a=function(){c()||o(i())};"function"==typeof n.addEventListener?n.addEventListener("change",a):"function"==typeof n.addListener&&n.addListener(a);var r=t.getElementById("themeSwitch"),l=t.getElementById("themeAuto");r&&(r.checked="dark"===e,r.addEventListener("change",function(){o(r.checked?"dark":"light")})),l&&l.addEventListener("click",function(){d(),o(i())})}(),"1"===t.body.getAttribute("data-theme-fab-enabled")&&l(),s(),e.addEventListener("scroll",s),t.querySelector(".drawer-toggle-left")||t.querySelector(".drawer-toggle-right"),u()}"loading"===t.readyState?t.addEventListener("DOMContentLoaded",m):m()}(window,document);
|
|
||||||
374
src/script.php
374
src/script.php
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
*
|
*
|
||||||
* This file is part of a Moko Consulting project.
|
* This file is part of a Moko Consulting project.
|
||||||
*
|
*
|
||||||
@@ -12,45 +12,33 @@
|
|||||||
* Joomla calls the methods in this class automatically during template
|
* Joomla calls the methods in this class automatically during template
|
||||||
* install, update, and uninstall via the <scriptfile> element in
|
* install, update, and uninstall via the <scriptfile> element in
|
||||||
* templateDetails.xml.
|
* templateDetails.xml.
|
||||||
* Joomla 5 and 6 compatible — uses the InstallerScriptInterface when
|
*
|
||||||
* available, falls back to the legacy class-based approach otherwise.
|
* On first install, detects MokoCassiopeia and migrates template styles,
|
||||||
|
* parameters, menu assignments, and user files automatically.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
defined('_JEXEC') or die;
|
defined('_JEXEC') or die;
|
||||||
|
|
||||||
use Joomla\CMS\Factory;
|
use Joomla\CMS\Factory;
|
||||||
use Joomla\CMS\Installer\InstallerAdapter;
|
use Joomla\CMS\Installer\InstallerAdapter;
|
||||||
|
use Joomla\CMS\Installer\InstallerScriptInterface;
|
||||||
use Joomla\CMS\Log\Log;
|
use Joomla\CMS\Log\Log;
|
||||||
|
|
||||||
class Tpl_MokoonyxInstallerScript
|
class Tpl_MokoonyxInstallerScript implements InstallerScriptInterface
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Minimum PHP version required by this template.
|
|
||||||
*/
|
|
||||||
private const MIN_PHP = '8.1.0';
|
private const MIN_PHP = '8.1.0';
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimum Joomla version required by this template.
|
|
||||||
*/
|
|
||||||
private const MIN_JOOMLA = '4.4.0';
|
private const MIN_JOOMLA = '4.4.0';
|
||||||
|
|
||||||
/**
|
private const OLD_NAME = 'mokocassiopeia';
|
||||||
* Called before install/update/uninstall.
|
private const NEW_NAME = 'mokoonyx';
|
||||||
*
|
private const OLD_DISPLAY = 'MokoCassiopeia';
|
||||||
* @param string $type install, update, discover_install, or uninstall.
|
private const NEW_DISPLAY = 'MokoOnyx';
|
||||||
* @param InstallerAdapter $parent The adapter calling this method.
|
|
||||||
*
|
|
||||||
* @return bool True to proceed, false to abort.
|
|
||||||
*/
|
|
||||||
public function preflight(string $type, InstallerAdapter $parent): bool
|
public function preflight(string $type, InstallerAdapter $parent): bool
|
||||||
{
|
{
|
||||||
if (version_compare(PHP_VERSION, self::MIN_PHP, '<')) {
|
if (version_compare(PHP_VERSION, self::MIN_PHP, '<')) {
|
||||||
Factory::getApplication()->enqueueMessage(
|
Factory::getApplication()->enqueueMessage(
|
||||||
sprintf(
|
sprintf('MokoOnyx requires PHP %s or later. You are running PHP %s.', self::MIN_PHP, PHP_VERSION),
|
||||||
'MokoOnyx requires PHP %s or later. You are running PHP %s.',
|
|
||||||
self::MIN_PHP,
|
|
||||||
PHP_VERSION
|
|
||||||
),
|
|
||||||
'error'
|
'error'
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
@@ -58,11 +46,7 @@ class Tpl_MokoonyxInstallerScript
|
|||||||
|
|
||||||
if (version_compare(JVERSION, self::MIN_JOOMLA, '<')) {
|
if (version_compare(JVERSION, self::MIN_JOOMLA, '<')) {
|
||||||
Factory::getApplication()->enqueueMessage(
|
Factory::getApplication()->enqueueMessage(
|
||||||
sprintf(
|
sprintf('MokoOnyx requires Joomla %s or later. You are running Joomla %s.', self::MIN_JOOMLA, JVERSION),
|
||||||
'MokoOnyx requires Joomla %s or later. You are running Joomla %s.',
|
|
||||||
self::MIN_JOOMLA,
|
|
||||||
JVERSION
|
|
||||||
),
|
|
||||||
'error'
|
'error'
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
@@ -71,37 +55,17 @@ class Tpl_MokoonyxInstallerScript
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Called after a successful install.
|
|
||||||
*
|
|
||||||
* @param InstallerAdapter $parent The adapter calling this method.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function install(InstallerAdapter $parent): bool
|
public function install(InstallerAdapter $parent): bool
|
||||||
{
|
{
|
||||||
$this->logMessage('MokoOnyx template installed.');
|
$this->logMessage('MokoOnyx template installed.');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Called after a successful update.
|
|
||||||
*
|
|
||||||
* This is where the CSS variable sync runs — it detects variables that
|
|
||||||
* were added in the new version and injects them into the user's custom
|
|
||||||
* palette files without overwriting existing values.
|
|
||||||
*
|
|
||||||
* @param InstallerAdapter $parent The adapter calling this method.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function update(InstallerAdapter $parent): bool
|
public function update(InstallerAdapter $parent): bool
|
||||||
{
|
{
|
||||||
$this->logMessage('MokoOnyx template updated.');
|
$this->logMessage('MokoOnyx template updated.');
|
||||||
|
|
||||||
// Run CSS variable sync to inject any new variables into user's custom palettes.
|
|
||||||
$synced = $this->syncCustomVariables($parent);
|
$synced = $this->syncCustomVariables($parent);
|
||||||
|
|
||||||
if ($synced > 0) {
|
if ($synced > 0) {
|
||||||
Factory::getApplication()->enqueueMessage(
|
Factory::getApplication()->enqueueMessage(
|
||||||
sprintf(
|
sprintf(
|
||||||
@@ -116,47 +80,273 @@ class Tpl_MokoonyxInstallerScript
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Called after a successful uninstall.
|
|
||||||
*
|
|
||||||
* @param InstallerAdapter $parent The adapter calling this method.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function uninstall(InstallerAdapter $parent): bool
|
public function uninstall(InstallerAdapter $parent): bool
|
||||||
{
|
{
|
||||||
$this->logMessage('MokoOnyx template uninstalled.');
|
$this->logMessage('MokoOnyx template uninstalled.');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Called after install/update completes (regardless of type).
|
|
||||||
*
|
|
||||||
* @param string $type install, update, or discover_install.
|
|
||||||
* @param InstallerAdapter $parent The adapter calling this method.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function postflight(string $type, InstallerAdapter $parent): bool
|
public function postflight(string $type, InstallerAdapter $parent): bool
|
||||||
{
|
{
|
||||||
|
// On install or update: migrate from MokoCassiopeia if it exists
|
||||||
|
if ($type === 'install' || $type === 'update') {
|
||||||
|
$this->migrateFromCassiopeia();
|
||||||
|
$this->replaceCassiopeiaReferences();
|
||||||
|
$this->clearFaviconStamp();
|
||||||
|
$this->lockExtension();
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the CSS variable sync utility.
|
* Replace MokoCassiopeia references in article content and module content.
|
||||||
*
|
|
||||||
* Loads sync_custom_vars.php from the template directory and calls
|
|
||||||
* MokoCssVarSync::run() to detect and inject missing variables.
|
|
||||||
*
|
|
||||||
* @param InstallerAdapter $parent The adapter calling this method.
|
|
||||||
*
|
|
||||||
* @return int Number of variables added across all files.
|
|
||||||
*/
|
*/
|
||||||
|
private function replaceCassiopeiaReferences(): void
|
||||||
|
{
|
||||||
|
$db = Factory::getDbo();
|
||||||
|
|
||||||
|
// Replace in article content (introtext + fulltext)
|
||||||
|
foreach (['introtext', 'fulltext'] as $col) {
|
||||||
|
try {
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->update('#__content')
|
||||||
|
->set(
|
||||||
|
$db->quoteName($col) . ' = REPLACE(REPLACE('
|
||||||
|
. $db->quoteName($col) . ', '
|
||||||
|
. $db->quote(self::OLD_DISPLAY) . ', '
|
||||||
|
. $db->quote(self::NEW_DISPLAY) . '), '
|
||||||
|
. $db->quote(self::OLD_NAME) . ', '
|
||||||
|
. $db->quote(self::NEW_NAME) . ')'
|
||||||
|
)
|
||||||
|
->where(
|
||||||
|
'(' . $db->quoteName($col) . ' LIKE ' . $db->quote('%' . self::OLD_DISPLAY . '%')
|
||||||
|
. ' OR ' . $db->quoteName($col) . ' LIKE ' . $db->quote('%' . self::OLD_NAME . '%') . ')'
|
||||||
|
);
|
||||||
|
$db->setQuery($query)->execute();
|
||||||
|
$n = $db->getAffectedRows();
|
||||||
|
if ($n > 0) {
|
||||||
|
$this->logMessage("Replaced MokoCassiopeia in {$n} content row(s) ({$col}).");
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->logMessage('Content replacement failed (' . $col . '): ' . $e->getMessage(), 'warning');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace in module content (custom HTML modules etc.)
|
||||||
|
try {
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->update('#__modules')
|
||||||
|
->set(
|
||||||
|
$db->quoteName('content') . ' = REPLACE(REPLACE('
|
||||||
|
. $db->quoteName('content') . ', '
|
||||||
|
. $db->quote(self::OLD_DISPLAY) . ', '
|
||||||
|
. $db->quote(self::NEW_DISPLAY) . '), '
|
||||||
|
. $db->quote(self::OLD_NAME) . ', '
|
||||||
|
. $db->quote(self::NEW_NAME) . ')'
|
||||||
|
)
|
||||||
|
->where(
|
||||||
|
'(' . $db->quoteName('content') . ' LIKE ' . $db->quote('%' . self::OLD_DISPLAY . '%')
|
||||||
|
. ' OR ' . $db->quoteName('content') . ' LIKE ' . $db->quote('%' . self::OLD_NAME . '%') . ')'
|
||||||
|
);
|
||||||
|
$db->setQuery($query)->execute();
|
||||||
|
$n = $db->getAffectedRows();
|
||||||
|
if ($n > 0) {
|
||||||
|
$this->logMessage("Replaced MokoCassiopeia in {$n} module(s).");
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->logMessage('Module replacement failed: ' . $e->getMessage(), 'warning');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the favicon stamp file so favicons and site.webmanifest
|
||||||
|
* are regenerated on the next page load after install/update.
|
||||||
|
* Also removes the old /images/favicons/ location.
|
||||||
|
*/
|
||||||
|
private function clearFaviconStamp(): void
|
||||||
|
{
|
||||||
|
// Clear new location stamp
|
||||||
|
$stampFile = JPATH_ROOT . '/media/templates/site/' . self::NEW_NAME . '/images/favicons/.favicon_generated';
|
||||||
|
if (is_file($stampFile)) {
|
||||||
|
@unlink($stampFile);
|
||||||
|
$this->logMessage('Cleared favicon stamp — will regenerate on next page load.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove old /images/favicons/ directory from previous versions
|
||||||
|
$oldDir = JPATH_ROOT . '/images/favicons';
|
||||||
|
if (is_dir($oldDir)) {
|
||||||
|
$files = glob($oldDir . '/*');
|
||||||
|
if ($files) {
|
||||||
|
foreach ($files as $file) {
|
||||||
|
@unlink($file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@unlink($oldDir . '/.favicon_generated');
|
||||||
|
@rmdir($oldDir);
|
||||||
|
$this->logMessage('Removed old favicon directory: images/favicons/');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any favicon files left in the site root
|
||||||
|
$rootFavicons = ['favicon.ico', 'favicon.png', 'apple-touch-icon.png', 'site.webmanifest'];
|
||||||
|
foreach ($rootFavicons as $file) {
|
||||||
|
$path = JPATH_ROOT . '/' . $file;
|
||||||
|
if (is_file($path)) {
|
||||||
|
@unlink($path);
|
||||||
|
$this->logMessage('Removed root favicon: ' . $file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect MokoCassiopeia and create matching MokoOnyx styles with the same params.
|
||||||
|
* Creates a MokoOnyx style copy for each MokoCassiopeia style.
|
||||||
|
*/
|
||||||
|
private function migrateFromCassiopeia(): void
|
||||||
|
{
|
||||||
|
$db = Factory::getDbo();
|
||||||
|
|
||||||
|
// Get all MokoCassiopeia styles
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->select('*')
|
||||||
|
->from('#__template_styles')
|
||||||
|
->where($db->quoteName('template') . ' = ' . $db->quote(self::OLD_NAME))
|
||||||
|
->where($db->quoteName('client_id') . ' = 0');
|
||||||
|
$oldStyles = $db->setQuery($query)->loadObjectList();
|
||||||
|
|
||||||
|
if (empty($oldStyles)) {
|
||||||
|
$this->logMessage('No MokoCassiopeia styles found — fresh install.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logMessage('MokoCassiopeia detected — creating ' . count($oldStyles) . ' matching MokoOnyx style(s).');
|
||||||
|
|
||||||
|
// Get the installer-created default MokoOnyx style (to apply params to it)
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->select('id')
|
||||||
|
->from('#__template_styles')
|
||||||
|
->where($db->quoteName('template') . ' = ' . $db->quote(self::NEW_NAME))
|
||||||
|
->where($db->quoteName('client_id') . ' = 0')
|
||||||
|
->order($db->quoteName('id') . ' ASC');
|
||||||
|
$defaultOnyxId = (int) $db->setQuery($query, 0, 1)->loadResult();
|
||||||
|
|
||||||
|
$firstStyle = true;
|
||||||
|
|
||||||
|
foreach ($oldStyles as $oldStyle) {
|
||||||
|
$newTitle = str_replace(self::OLD_DISPLAY, self::NEW_DISPLAY, $oldStyle->title);
|
||||||
|
$newTitle = str_replace(self::OLD_NAME, self::NEW_NAME, $newTitle);
|
||||||
|
|
||||||
|
$params = is_string($oldStyle->params)
|
||||||
|
? str_replace(self::OLD_NAME, self::NEW_NAME, $oldStyle->params)
|
||||||
|
: $oldStyle->params;
|
||||||
|
|
||||||
|
if ($firstStyle && $defaultOnyxId) {
|
||||||
|
// Update the installer-created default style with the first MokoCassiopeia style's params
|
||||||
|
$update = $db->getQuery(true)
|
||||||
|
->update('#__template_styles')
|
||||||
|
->set($db->quoteName('params') . ' = ' . $db->quote($params))
|
||||||
|
->set($db->quoteName('title') . ' = ' . $db->quote($newTitle))
|
||||||
|
->where('id = ' . $defaultOnyxId);
|
||||||
|
$db->setQuery($update)->execute();
|
||||||
|
|
||||||
|
// Set as default if MokoCassiopeia was default
|
||||||
|
if ($oldStyle->home == 1) {
|
||||||
|
$db->setQuery(
|
||||||
|
$db->getQuery(true)
|
||||||
|
->update('#__template_styles')
|
||||||
|
->set($db->quoteName('home') . ' = 1')
|
||||||
|
->where('id = ' . $defaultOnyxId)
|
||||||
|
)->execute();
|
||||||
|
|
||||||
|
$db->setQuery(
|
||||||
|
$db->getQuery(true)
|
||||||
|
->update('#__template_styles')
|
||||||
|
->set($db->quoteName('home') . ' = 0')
|
||||||
|
->where('id = ' . (int) $oldStyle->id)
|
||||||
|
)->execute();
|
||||||
|
|
||||||
|
$this->logMessage('Set MokoOnyx as default site template.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logMessage("Updated default MokoOnyx style with params: {$newTitle}");
|
||||||
|
$firstStyle = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For additional styles: create new MokoOnyx style copies
|
||||||
|
$newStyle = clone $oldStyle;
|
||||||
|
unset($newStyle->id);
|
||||||
|
$newStyle->template = self::NEW_NAME;
|
||||||
|
$newStyle->title = $newTitle;
|
||||||
|
$newStyle->home = 0;
|
||||||
|
$newStyle->params = $params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db->insertObject('#__template_styles', $newStyle, 'id');
|
||||||
|
$this->logMessage("Created MokoOnyx style: {$newTitle}");
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->logMessage("Failed to create style {$newTitle}: " . $e->getMessage(), 'warning');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Copy user files (custom themes, user.css, user.js)
|
||||||
|
$this->copyUserFiles();
|
||||||
|
|
||||||
|
// 3. Notify admin
|
||||||
|
Factory::getApplication()->enqueueMessage(
|
||||||
|
'<strong>MokoOnyx has been installed as a replacement for MokoCassiopeia.</strong><br>'
|
||||||
|
. 'Your template settings and custom files have been migrated automatically. '
|
||||||
|
. 'MokoOnyx is now your active site template. '
|
||||||
|
. 'You can safely uninstall MokoCassiopeia from Extensions → Manage.',
|
||||||
|
'success'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy user-specific files from MokoCassiopeia media to MokoOnyx media.
|
||||||
|
*/
|
||||||
|
private function copyUserFiles(): void
|
||||||
|
{
|
||||||
|
$oldMedia = JPATH_ROOT . '/media/templates/site/' . self::OLD_NAME;
|
||||||
|
$newMedia = JPATH_ROOT . '/media/templates/site/' . self::NEW_NAME;
|
||||||
|
|
||||||
|
if (!is_dir($oldMedia) || !is_dir($newMedia)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$userFiles = [
|
||||||
|
'css/theme/light.custom.css',
|
||||||
|
'css/theme/dark.custom.css',
|
||||||
|
'css/theme/light.custom.min.css',
|
||||||
|
'css/theme/dark.custom.min.css',
|
||||||
|
'css/user.css',
|
||||||
|
'css/user.min.css',
|
||||||
|
'js/user.js',
|
||||||
|
'js/user.min.js',
|
||||||
|
];
|
||||||
|
|
||||||
|
$copied = 0;
|
||||||
|
foreach ($userFiles as $relPath) {
|
||||||
|
$src = $oldMedia . '/' . $relPath;
|
||||||
|
$dst = $newMedia . '/' . $relPath;
|
||||||
|
if (is_file($src) && !is_file($dst)) {
|
||||||
|
$dstDir = dirname($dst);
|
||||||
|
if (!is_dir($dstDir)) {
|
||||||
|
mkdir($dstDir, 0755, true);
|
||||||
|
}
|
||||||
|
copy($src, $dst);
|
||||||
|
$copied++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($copied > 0) {
|
||||||
|
$this->logMessage("Copied {$copied} user file(s) from MokoCassiopeia.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function syncCustomVariables(InstallerAdapter $parent): int
|
private function syncCustomVariables(InstallerAdapter $parent): int
|
||||||
{
|
{
|
||||||
$templateDir = $parent->getParent()->getPath('source');
|
$templateDir = $parent->getParent()->getPath('source');
|
||||||
|
|
||||||
// The sync script lives alongside this script in the template root.
|
|
||||||
$syncScript = $templateDir . '/sync_custom_vars.php';
|
$syncScript = $templateDir . '/sync_custom_vars.php';
|
||||||
|
|
||||||
if (!is_file($syncScript)) {
|
if (!is_file($syncScript)) {
|
||||||
@@ -172,20 +362,13 @@ class Tpl_MokoonyxInstallerScript
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$joomlaRoot = JPATH_ROOT;
|
$results = MokoCssVarSync::run(JPATH_ROOT);
|
||||||
$results = MokoCssVarSync::run($joomlaRoot);
|
|
||||||
|
|
||||||
$totalAdded = 0;
|
$totalAdded = 0;
|
||||||
foreach ($results as $filePath => $result) {
|
foreach ($results as $filePath => $result) {
|
||||||
$totalAdded += count($result['added']);
|
$totalAdded += count($result['added']);
|
||||||
if (!empty($result['added'])) {
|
if (!empty($result['added'])) {
|
||||||
$this->logMessage(
|
$this->logMessage(sprintf('CSS sync: added %d variable(s) to %s', count($result['added']), basename($filePath)));
|
||||||
sprintf(
|
|
||||||
'CSS sync: added %d variable(s) to %s',
|
|
||||||
count($result['added']),
|
|
||||||
basename($filePath)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,11 +380,28 @@ class Tpl_MokoonyxInstallerScript
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log a message to Joomla's log system.
|
* Lock the extension to prevent uninstallation via Extension Manager.
|
||||||
*
|
|
||||||
* @param string $message The log message.
|
|
||||||
* @param string $priority Log priority (info, warning, error).
|
|
||||||
*/
|
*/
|
||||||
|
private function lockExtension(): void
|
||||||
|
{
|
||||||
|
$db = Factory::getDbo();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->update('#__extensions')
|
||||||
|
->set($db->quoteName('locked') . ' = 1')
|
||||||
|
->where($db->quoteName('element') . ' = ' . $db->quote(self::NEW_NAME))
|
||||||
|
->where($db->quoteName('type') . ' = ' . $db->quote('template'));
|
||||||
|
$db->setQuery($query)->execute();
|
||||||
|
|
||||||
|
if ($db->getAffectedRows() > 0) {
|
||||||
|
$this->logMessage('MokoOnyx extension locked.');
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->logMessage('Failed to lock extension: ' . $e->getMessage(), 'warning');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function logMessage(string $message, string $priority = 'info'): void
|
private function logMessage(string $message, string $priority = 'info'): void
|
||||||
{
|
{
|
||||||
$priorities = [
|
$priorities = [
|
||||||
|
|||||||
@@ -39,13 +39,13 @@
|
|||||||
</server>
|
</server>
|
||||||
</updateservers>
|
</updateservers>
|
||||||
<name>MokoOnyx</name>
|
<name>MokoOnyx</name>
|
||||||
<version>01.00.00</version>
|
<version>01.00.25</version>
|
||||||
<scriptfile>script.php</scriptfile>
|
<scriptfile>script.php</scriptfile>
|
||||||
<creationDate>2026-04-15</creationDate>
|
<creationDate>2026-04-15</creationDate>
|
||||||
<author>Jonathan Miller || Moko Consulting</author>
|
<author>Jonathan Miller || Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
<copyright>(C)GNU General Public License Version 3 - 2026 Moko Consulting</copyright>
|
<copyright>(C)GNU General Public License Version 3 - 2026 Moko Consulting</copyright>
|
||||||
<description><![CDATA[<p><img src="https://img.shields.io/badge/version-01.00.00-blue.svg?logo=v&logoColor=white" alt="Version 01.00.00" /> <img src="https://img.shields.io/badge/license-GPL--3.0--or--later-green.svg?logo=gnu&logoColor=white" alt="License" /> <img src="https://img.shields.io/badge/Joomla-5.x%20%7C%206.x-red.svg?logo=joomla&logoColor=white" alt="Joomla" /> <img src="https://img.shields.io/badge/PHP-8.1%2B-777BB4.svg?logo=php&logoColor=white" alt="PHP" /></p> <h3>MokoOnyx Template Description</h3> <p> <strong>MokoOnyx</strong> continues Joomla's tradition of space-themed default templates— building on the legacy of <em>Solarflare</em> (Joomla 1.0), <em>Milkyway</em> (Joomla 1.5), and <em>Protostar</em> (Joomla 3.0). </p> <p> This template is a customized fork of the <strong>Cassiopeia</strong> template introduced in Joomla 4, preserving its modern, accessible, and mobile-first foundation while introducing new stylistic enhancements and structural refinements specifically tailored for use by Moko Consulting. </p> <h4>Custom Colour Themes</h4> <p> Starter palette files are included with the template. To create a custom colour scheme, copy <code>templates/mokoonyx/templates/light.custom.css</code> to <code>media/templates/site/mokoonyx/css/theme/light.custom.css</code>, or <code>templates/mokoonyx/templates/dark.custom.css</code> to <code>media/templates/site/mokoonyx/css/theme/dark.custom.css</code>. Customise the CSS variables to match your brand, then activate your palette in <em>System → Site Templates → MokoOnyx → Theme tab</em> by selecting "Custom" for the Light or Dark Mode Palette. A full variable reference is available in the <em>CSS Variables</em> tab in template options. </p> <h4>Custom CSS & JavaScript</h4> <p> For site-specific styles and scripts that should survive template updates, create the following files: </p> <ul> <li><code>media/templates/site/mokoonyx/css/user.css</code> — loaded on every page for custom CSS overrides.</li> <li><code>media/templates/site/mokoonyx/js/user.js</code> — loaded on every page for custom JavaScript.</li> </ul> <p> These files are gitignored and will not be overwritten by template updates. </p> <h4>Code Attribution</h4> <p> This template is based on the original <strong>Cassiopeia</strong> template developed by the <a href="https://www.joomla.org" target="_blank" rel="noopener">Joomla! Project</a> and released under the GNU General Public License. </p> <p> Modifications and enhancements have been made by Moko Consulting in accordance with open-source licensing standards. </p> <p> It includes integration with <a href="https://afeld.github.io/bootstrap-toc/" target="_blank" rel="noopener">Bootstrap TOC</a>, an open-source table of contents generator by A. Feld, licensed under the MIT License. </p> <p> All third-party libraries and assets remain the property of their respective authors and are credited within their source files where applicable. </p>]]></description>
|
<description><![CDATA[<div style="padding:1rem;border:2px solid #059669;border-radius:8px;background:#ecfdf5;margin-bottom:1rem;"> <h3 style="color:#065f46;margin:0 0 .5rem;">Migrating from MokoCassiopeia?</h3> <p style="margin:0 0 .75rem;">MokoOnyx automatically imports your MokoCassiopeia settings on first use. To trigger the migration:</p> <ol style="margin:0 0 .75rem 1.5rem;"> <li>Install MokoOnyx via <strong>System → Install → Extensions</strong></li> <li>Go to <strong>System → Site Templates</strong> and set <strong>MokoOnyx</strong> as your default template</li> <li><strong>Visit any page on your site</strong> — the migration runs automatically on first page load</li> <li>Check <strong>administrator/logs/mokoonyx_migrate.log.php</strong> to confirm migration completed</li> <li>Uninstall MokoCassiopeia from <strong>Extensions → Manage</strong></li> </ol> <p style="margin:0;font-size:.9em;color:#065f46;"> <strong>What gets migrated:</strong> template style params, custom colour palettes (light.custom.css, dark.custom.css), user.css, user.js, and update server URL. To re-run the migration, delete <code>templates/mokoonyx/.migrated</code> and reload any page. </p></div> <p><img src="https://img.shields.io/gitea/v/release/MokoConsulting/MokoOnyx?gitea_url=https%3A%2F%2Fgit.mokoconsulting.tech&logo=gitea&logoColor=white&label=version" alt="Version" /> <img src="https://img.shields.io/badge/license-GPL--3.0--or--later-green.svg?logo=gnu&logoColor=white" alt="License" /> <img src="https://img.shields.io/badge/Joomla-5.x%20%7C%206.x-red.svg?logo=joomla&logoColor=white" alt="Joomla" /> <img src="https://img.shields.io/badge/PHP-8.1%2B-777BB4.svg?logo=php&logoColor=white" alt="PHP" /></p> <h3>MokoOnyx</h3> <p> <strong>MokoOnyx</strong> (formerly MokoCassiopeia) is a modern, lightweight enhancement layer built on Joomla's Cassiopeia template. It adds Font Awesome 7, Bootstrap 5 helpers, automatic Table of Contents, advanced Dark Mode theming, and optional Google Tag Manager / GA4 integration — all with minimal core overrides for maximum upgrade compatibility. </p> <h4>Custom Colour Themes</h4> <p> Copy <code>templates/mokoonyx/templates/light.custom.css</code> to <code>media/templates/site/mokoonyx/css/theme/light.custom.css</code> (or dark equivalent), customise the CSS variables, then select "Custom" in the Theme tab. </p> <h4>Custom CSS & JavaScript</h4> <ul> <li><code>media/templates/site/mokoonyx/css/user.css</code> — custom CSS overrides</li> <li><code>media/templates/site/mokoonyx/js/user.js</code> — custom JavaScript</li> </ul> <p>These files survive template updates.</p> <h4>CSS Variables Reference</h4> <p>For a complete list of CSS variables, see the <a href="https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx" target="_blank">MokoOnyx repository documentation</a>.</p>]]></description>
|
||||||
<inheritable>1</inheritable>
|
<inheritable>1</inheritable>
|
||||||
<files>
|
<files>
|
||||||
<filename>component.php</filename>
|
<filename>component.php</filename>
|
||||||
@@ -108,6 +108,10 @@
|
|||||||
|
|
||||||
<fields name="params">
|
<fields name="params">
|
||||||
<!-- Advanced tab (non-theme/system options only) -->
|
<!-- Advanced tab (non-theme/system options only) -->
|
||||||
|
<fieldset name="migration" label="TPL_MOKOONYX_MIGRATION_FIELDSET_LABEL">
|
||||||
|
<field name="migration_note" type="note" label="TPL_MOKOONYX_MIGRATION_NOTE_LABEL" description="TPL_MOKOONYX_MIGRATION_NOTE_DESC" />
|
||||||
|
<field name="migration_run" type="note" label="TPL_MOKOONYX_MIGRATION_RUN_LABEL" description="TPL_MOKOONYX_MIGRATION_RUN_DESC" />
|
||||||
|
</fieldset>
|
||||||
<fieldset name="advanced">
|
<fieldset name="advanced">
|
||||||
<field name="developmentmode" type="radio" label="TPL_MOKOONYX_DEVELOPMENTMODE_LABEL" description="TPL_MOKOONYX_DEVELOPMENTMODE_DESC" default="0" layout="joomla.form.field.radio.switcher" filter="boolean">
|
<field name="developmentmode" type="radio" label="TPL_MOKOONYX_DEVELOPMENTMODE_LABEL" description="TPL_MOKOONYX_DEVELOPMENTMODE_DESC" default="0" layout="joomla.form.field.radio.switcher" filter="boolean">
|
||||||
<option value="0">JNO</option>
|
<option value="0">JNO</option>
|
||||||
@@ -323,57 +327,11 @@
|
|||||||
</field>
|
</field>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<!-- CSS Variables reference tab -->
|
<!-- CSS Variables: see repo documentation -->
|
||||||
<fieldset name="css_variables" label="TPL_MOKOONYX_CSS_VARS_FIELDSET_LABEL">
|
<fieldset name="css_variables" label="TPL_MOKOONYX_CSS_VARS_FIELDSET_LABEL">
|
||||||
<field name="css_vars_intro" type="note" description="TPL_MOKOONYX_CSS_VARS_INTRO" />
|
<field name="css_vars_docs_link" type="note"
|
||||||
<field name="css_vars_brand" type="note" label="TPL_MOKOONYX_CSS_VARS_BRAND_LABEL" description="TPL_MOKOONYX_CSS_VARS_BRAND_DESC" class="alert alert-light" />
|
description="For a full list of CSS variables and theming options, see the <a href='https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx' target='_blank'>MokoOnyx documentation</a>."
|
||||||
<field name="css_vars_links" type="note" label="TPL_MOKOONYX_CSS_VARS_LINKS_LABEL" description="TPL_MOKOONYX_CSS_VARS_LINKS_DESC" class="alert alert-light" />
|
class="alert alert-info" />
|
||||||
<field name="css_vars_typography" type="note" label="TPL_MOKOONYX_CSS_VARS_TYPO_LABEL" description="TPL_MOKOONYX_CSS_VARS_TYPO_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_nav" type="note" label="TPL_MOKOONYX_CSS_VARS_NAV_LABEL" description="TPL_MOKOONYX_CSS_VARS_NAV_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_layout" type="note" label="TPL_MOKOONYX_CSS_VARS_LAYOUT_LABEL" description="TPL_MOKOONYX_CSS_VARS_LAYOUT_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_breakpoints" type="note" label="TPL_MOKOONYX_CSS_VARS_BP_LABEL" description="TPL_MOKOONYX_CSS_VARS_BP_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_bootstrap" type="note" label="TPL_MOKOONYX_CSS_VARS_BS_LABEL" description="TPL_MOKOONYX_CSS_VARS_BS_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_bootstrap_states" type="note" label="TPL_MOKOONYX_CSS_VARS_BS_STATES_LABEL" description="TPL_MOKOONYX_CSS_VARS_BS_STATES_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_alert_list" type="note" label="TPL_MOKOONYX_CSS_VARS_ALERT_LIST_LABEL" description="TPL_MOKOONYX_CSS_VARS_ALERT_LIST_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_colors" type="note" label="TPL_MOKOONYX_CSS_VARS_COLORS_LABEL" description="TPL_MOKOONYX_CSS_VARS_COLORS_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_hero" type="note" label="TPL_MOKOONYX_CSS_VARS_HERO_LABEL" description="TPL_MOKOONYX_CSS_VARS_HERO_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_hero_variants" type="note" label="TPL_MOKOONYX_CSS_VARS_HERO_VARIANTS_LABEL" description="TPL_MOKOONYX_CSS_VARS_HERO_VARIANTS_DESC" class="alert alert-info" />
|
|
||||||
<field name="css_vars_block_colors" type="note" label="TPL_MOKOONYX_CSS_VARS_BLOCK_COLORS_LABEL" description="TPL_MOKOONYX_CSS_VARS_BLOCK_COLORS_DESC" class="alert alert-info" />
|
|
||||||
<field name="css_vars_header" type="note" label="TPL_MOKOONYX_CSS_VARS_HEADER_LABEL" description="TPL_MOKOONYX_CSS_VARS_HEADER_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_containers" type="note" label="TPL_MOKOONYX_CSS_VARS_CONTAINERS_LABEL" description="TPL_MOKOONYX_CSS_VARS_CONTAINERS_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_borders" type="note" label="TPL_MOKOONYX_CSS_VARS_BORDERS_LABEL" description="TPL_MOKOONYX_CSS_VARS_BORDERS_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_shadows" type="note" label="TPL_MOKOONYX_CSS_VARS_SHADOWS_LABEL" description="TPL_MOKOONYX_CSS_VARS_SHADOWS_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_forms" type="note" label="TPL_MOKOONYX_CSS_VARS_FORMS_LABEL" description="TPL_MOKOONYX_CSS_VARS_FORMS_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_buttons" type="note" label="TPL_MOKOONYX_CSS_VARS_BUTTONS_LABEL" description="TPL_MOKOONYX_CSS_VARS_BUTTONS_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_cards" type="note" label="TPL_MOKOONYX_CSS_VARS_CARDS_LABEL" description="TPL_MOKOONYX_CSS_VARS_CARDS_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_accordion" type="note" label="TPL_MOKOONYX_CSS_VARS_ACCORDION_LABEL" description="TPL_MOKOONYX_CSS_VARS_ACCORDION_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_alert_base" type="note" label="TPL_MOKOONYX_CSS_VARS_ALERT_BASE_LABEL" description="TPL_MOKOONYX_CSS_VARS_ALERT_BASE_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_badge" type="note" label="TPL_MOKOONYX_CSS_VARS_BADGE_LABEL" description="TPL_MOKOONYX_CSS_VARS_BADGE_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_breadcrumb" type="note" label="TPL_MOKOONYX_CSS_VARS_BREADCRUMB_LABEL" description="TPL_MOKOONYX_CSS_VARS_BREADCRUMB_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_dropdown_menu" type="note" label="TPL_MOKOONYX_CSS_VARS_DROPDOWN_MENU_LABEL" description="TPL_MOKOONYX_CSS_VARS_DROPDOWN_MENU_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_list_group" type="note" label="TPL_MOKOONYX_CSS_VARS_LIST_GROUP_LABEL" description="TPL_MOKOONYX_CSS_VARS_LIST_GROUP_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_modal" type="note" label="TPL_MOKOONYX_CSS_VARS_MODAL_LABEL" description="TPL_MOKOONYX_CSS_VARS_MODAL_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_backdrop" type="note" label="TPL_MOKOONYX_CSS_VARS_BACKDROP_LABEL" description="TPL_MOKOONYX_CSS_VARS_BACKDROP_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_nav_tabs" type="note" label="TPL_MOKOONYX_CSS_VARS_NAV_TABS_LABEL" description="TPL_MOKOONYX_CSS_VARS_NAV_TABS_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_nav_pills" type="note" label="TPL_MOKOONYX_CSS_VARS_NAV_PILLS_LABEL" description="TPL_MOKOONYX_CSS_VARS_NAV_PILLS_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_pagination" type="note" label="TPL_MOKOONYX_CSS_VARS_PAGINATION_LABEL" description="TPL_MOKOONYX_CSS_VARS_PAGINATION_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_popover" type="note" label="TPL_MOKOONYX_CSS_VARS_POPOVER_LABEL" description="TPL_MOKOONYX_CSS_VARS_POPOVER_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_progress" type="note" label="TPL_MOKOONYX_CSS_VARS_PROGRESS_LABEL" description="TPL_MOKOONYX_CSS_VARS_PROGRESS_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_spinner" type="note" label="TPL_MOKOONYX_CSS_VARS_SPINNER_LABEL" description="TPL_MOKOONYX_CSS_VARS_SPINNER_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_table" type="note" label="TPL_MOKOONYX_CSS_VARS_TABLE_LABEL" description="TPL_MOKOONYX_CSS_VARS_TABLE_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_toast" type="note" label="TPL_MOKOONYX_CSS_VARS_TOAST_LABEL" description="TPL_MOKOONYX_CSS_VARS_TOAST_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_tooltip" type="note" label="TPL_MOKOONYX_CSS_VARS_TOOLTIP_LABEL" description="TPL_MOKOONYX_CSS_VARS_TOOLTIP_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_components" type="note" label="TPL_MOKOONYX_CSS_VARS_COMPONENTS_LABEL" description="TPL_MOKOONYX_CSS_VARS_COMPONENTS_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_offcanvas" type="note" label="TPL_MOKOONYX_CSS_VARS_OFFCANVAS_LABEL" description="TPL_MOKOONYX_CSS_VARS_OFFCANVAS_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_virtuemart" type="note" label="TPL_MOKOONYX_CSS_VARS_VM_LABEL" description="TPL_MOKOONYX_CSS_VARS_VM_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_gable" type="note" label="TPL_MOKOONYX_CSS_VARS_GABLE_LABEL" description="TPL_MOKOONYX_CSS_VARS_GABLE_DESC" class="alert alert-light" />
|
|
||||||
<field name="css_vars_footer" type="note" label="TPL_MOKOONYX_CSS_VARS_FOOTER_LABEL" description="TPL_MOKOONYX_CSS_VARS_FOOTER_DESC" class="alert alert-light" />
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<!-- Theme Preview tab — embedded test sheet -->
|
|
||||||
<fieldset name="theme_preview" label="TPL_MOKOONYX_THEME_PREVIEW_FIELDSET_LABEL">
|
|
||||||
<field name="theme_preview_intro" type="note" description="TPL_MOKOONYX_THEME_PREVIEW_INTRO" />
|
|
||||||
<field name="theme_preview_frame" type="note" description="TPL_MOKOONYX_THEME_PREVIEW_FRAME" />
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</fields>
|
</fields>
|
||||||
</config>
|
</config>
|
||||||
|
|||||||
@@ -210,6 +210,7 @@ color-scheme: dark;
|
|||||||
|
|
||||||
|
|
||||||
/* ===== HEADER BACKGROUND ===== */
|
/* ===== HEADER BACKGROUND ===== */
|
||||||
|
--header-background-color: #1a1f2b;
|
||||||
--header-background-image: url('../../../../../../media/templates/site/mokoonyx/images/bg.svg');
|
--header-background-image: url('../../../../../../media/templates/site/mokoonyx/images/bg.svg');
|
||||||
--header-background-attachment: fixed;
|
--header-background-attachment: fixed;
|
||||||
--header-background-repeat: repeat;
|
--header-background-repeat: repeat;
|
||||||
|
|||||||
@@ -209,6 +209,7 @@ color-scheme: light;
|
|||||||
|
|
||||||
|
|
||||||
/* ===== HEADER BACKGROUND ===== */
|
/* ===== HEADER BACKGROUND ===== */
|
||||||
|
--header-background-color: #adadad;
|
||||||
--header-background-image: url('../../../../../../media/templates/site/mokoonyx/images/bg.svg');
|
--header-background-image: url('../../../../../../media/templates/site/mokoonyx/images/bg.svg');
|
||||||
--header-background-attachment: fixed;
|
--header-background-attachment: fixed;
|
||||||
--header-background-repeat: repeat;
|
--header-background-repeat: repeat;
|
||||||
|
|||||||
@@ -1,836 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
|
|
||||||
This file is part of a Moko Consulting project.
|
|
||||||
|
|
||||||
SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
-->
|
|
||||||
<html lang="en" data-bs-theme="light">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>MokoOnyx — Theme Test Sheet</title>
|
|
||||||
<!-- Load the template CSS -->
|
|
||||||
<link rel="stylesheet" href="../media/css/template.css">
|
|
||||||
<!-- Load the light custom palette (swap to dark.custom.css for dark mode testing) -->
|
|
||||||
<link rel="stylesheet" href="light.custom.css">
|
|
||||||
<style>
|
|
||||||
/* ── Test Page Layout ── */
|
|
||||||
body { font-family: var(--body-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif); color: var(--body-color, #22262a); background: var(--body-bg, #fff); margin: 0; padding: 0; }
|
|
||||||
.test-container { max-width: 1200px; margin: 0 auto; padding: 1rem 1.5rem; }
|
|
||||||
h1, h2, h3, h4, h5, h6 { color: var(--heading-color, inherit); }
|
|
||||||
h1 { font-size: 2.25rem; margin-bottom: .25rem; }
|
|
||||||
h2 { font-size: 1.75rem; margin-top: 2.5rem; border-bottom: 2px solid var(--border-color, #dfe3e7); padding-bottom: .5rem; }
|
|
||||||
h3 { font-size: 1.25rem; margin-top: 1.5rem; }
|
|
||||||
p.lead { font-size: 1.15rem; color: var(--muted-color, #6d757e); }
|
|
||||||
hr { border: 0; border-top: 1px solid var(--border-color, #dfe3e7); margin: 2rem 0; }
|
|
||||||
a { color: var(--link-color, #224faa); text-decoration: var(--link-decoration, underline); }
|
|
||||||
a:hover { color: var(--link-hover-color, #424077); }
|
|
||||||
code { color: var(--code-color, #e93f8e); background: var(--secondary-bg, #eaedf0); padding: .15em .4em; border-radius: .2rem; font-size: .875em; }
|
|
||||||
pre { background: var(--secondary-bg, #eaedf0); color: var(--body-color); padding: 1rem; border-radius: var(--border-radius, .25rem); overflow-x: auto; font-size: .875rem; }
|
|
||||||
|
|
||||||
/* ── Swatch Grid ── */
|
|
||||||
.swatch-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: .75rem; margin: 1rem 0; }
|
|
||||||
.swatch { border-radius: var(--border-radius, .25rem); border: 1px solid var(--border-color, #dfe3e7); overflow: hidden; }
|
|
||||||
.swatch-color { height: 60px; }
|
|
||||||
.swatch-label { padding: .4rem .6rem; font-size: .75rem; background: var(--body-bg, #fff); }
|
|
||||||
.swatch-label code { font-size: .7rem; }
|
|
||||||
|
|
||||||
/* ── Variable Table ── */
|
|
||||||
.var-table { width: 100%; border-collapse: collapse; margin: 1rem 0; font-size: .875rem; }
|
|
||||||
.var-table th, .var-table td { padding: .5rem .75rem; border: 1px solid var(--border-color, #dfe3e7); text-align: left; }
|
|
||||||
.var-table th { background: var(--secondary-bg, #eaedf0); font-weight: 600; }
|
|
||||||
.var-table tr:nth-child(even) td { background: var(--tertiary-bg, #f9fafb); }
|
|
||||||
|
|
||||||
/* ── Flex row helper ── */
|
|
||||||
.row { display: flex; flex-wrap: wrap; gap: 1rem; }
|
|
||||||
.col { flex: 1; min-width: 200px; }
|
|
||||||
|
|
||||||
/* ── Theme Toggle ── */
|
|
||||||
.theme-toggle { position: fixed; top: 1rem; right: 1.5rem; z-index: 1000; }
|
|
||||||
.theme-toggle button { padding: .5rem 1rem; border: 1px solid var(--border-color); border-radius: var(--border-radius); background: var(--body-bg); color: var(--body-color); cursor: pointer; font-size: .875rem; }
|
|
||||||
|
|
||||||
/* ── Block Color Demo ── */
|
|
||||||
.block-demo { display: flex; gap: .75rem; flex-wrap: wrap; margin: 1rem 0; }
|
|
||||||
.block-demo .card { flex: 1; min-width: 180px; padding: 1.25rem; border-radius: var(--border-radius, .25rem); border: 1px solid var(--border-color, #dfe3e7); }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div class="theme-toggle">
|
|
||||||
<button onclick="toggleTheme()">Toggle Light / Dark</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="test-container">
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
HEADER
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h1>MokoOnyx Theme Test Sheet</h1>
|
|
||||||
<p class="lead">Visual reference for CSS variables, Bootstrap components, hero variants, and block color system. Toggle light/dark mode with the button in the top-right corner.</p>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
1. BRAND COLORS
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>1. Brand & Theme Colors</h2>
|
|
||||||
<div class="swatch-grid">
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--color-primary)"></div>
|
|
||||||
<div class="swatch-label"><code>--color-primary</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--accent-color-primary)"></div>
|
|
||||||
<div class="swatch-label"><code>--accent-color-primary</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--accent-color-secondary)"></div>
|
|
||||||
<div class="swatch-label"><code>--accent-color-secondary</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--body-bg)"></div>
|
|
||||||
<div class="swatch-label"><code>--body-bg</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--body-color)"></div>
|
|
||||||
<div class="swatch-label"><code>--body-color</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--secondary-bg)"></div>
|
|
||||||
<div class="swatch-label"><code>--secondary-bg</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--tertiary-bg)"></div>
|
|
||||||
<div class="swatch-label"><code>--tertiary-bg</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--border-color)"></div>
|
|
||||||
<div class="swatch-label"><code>--border-color</code></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
2. BOOTSTRAP PALETTE
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>2. Bootstrap Color Palette</h2>
|
|
||||||
<div class="swatch-grid">
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--primary)"></div>
|
|
||||||
<div class="swatch-label"><code>--primary</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--secondary)"></div>
|
|
||||||
<div class="swatch-label"><code>--secondary</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--success)"></div>
|
|
||||||
<div class="swatch-label"><code>--success</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--info)"></div>
|
|
||||||
<div class="swatch-label"><code>--info</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--warning)"></div>
|
|
||||||
<div class="swatch-label"><code>--warning</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--danger)"></div>
|
|
||||||
<div class="swatch-label"><code>--danger</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--light)"></div>
|
|
||||||
<div class="swatch-label"><code>--light</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--dark)"></div>
|
|
||||||
<div class="swatch-label"><code>--dark</code></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
3. GRAY SCALE
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>3. Gray Scale</h2>
|
|
||||||
<div class="swatch-grid">
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--gray-100)"></div>
|
|
||||||
<div class="swatch-label"><code>--gray-100</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--gray-200)"></div>
|
|
||||||
<div class="swatch-label"><code>--gray-200</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--gray-300)"></div>
|
|
||||||
<div class="swatch-label"><code>--gray-300</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--gray-400)"></div>
|
|
||||||
<div class="swatch-label"><code>--gray-400</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--gray-500)"></div>
|
|
||||||
<div class="swatch-label"><code>--gray-500</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--gray-600)"></div>
|
|
||||||
<div class="swatch-label"><code>--gray-600</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--gray-700)"></div>
|
|
||||||
<div class="swatch-label"><code>--gray-700</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--gray-800)"></div>
|
|
||||||
<div class="swatch-label"><code>--gray-800</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--gray-900)"></div>
|
|
||||||
<div class="swatch-label"><code>--gray-900</code></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
4. STANDARD COLORS
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>4. Standard Colors</h2>
|
|
||||||
<div class="swatch-grid">
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--blue)"></div>
|
|
||||||
<div class="swatch-label"><code>--blue</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--indigo)"></div>
|
|
||||||
<div class="swatch-label"><code>--indigo</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--purple)"></div>
|
|
||||||
<div class="swatch-label"><code>--purple</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--pink)"></div>
|
|
||||||
<div class="swatch-label"><code>--pink</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--red)"></div>
|
|
||||||
<div class="swatch-label"><code>--red</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--orange)"></div>
|
|
||||||
<div class="swatch-label"><code>--orange</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--yellow)"></div>
|
|
||||||
<div class="swatch-label"><code>--yellow</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--green)"></div>
|
|
||||||
<div class="swatch-label"><code>--green</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--teal)"></div>
|
|
||||||
<div class="swatch-label"><code>--teal</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--cyan)"></div>
|
|
||||||
<div class="swatch-label"><code>--cyan</code></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
5. TYPOGRAPHY
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>5. Typography</h2>
|
|
||||||
<div>
|
|
||||||
<h1>Heading 1 <small style="color: var(--muted-color); font-size: .5em;">h1</small></h1>
|
|
||||||
<h2 style="border: none; padding: 0; margin-top: .5rem;">Heading 2 <small style="color: var(--muted-color); font-size: .5em;">h2</small></h2>
|
|
||||||
<h3>Heading 3 <small style="color: var(--muted-color); font-size: .6em;">h3</small></h3>
|
|
||||||
<h4>Heading 4 <small style="color: var(--muted-color); font-size: .6em;">h4</small></h4>
|
|
||||||
<h5>Heading 5 <small style="color: var(--muted-color); font-size: .6em;">h5</small></h5>
|
|
||||||
<h6>Heading 6 <small style="color: var(--muted-color); font-size: .6em;">h6</small></h6>
|
|
||||||
</div>
|
|
||||||
<p>This is regular body text using <code>--body-color</code> on <code>--body-bg</code>. Font family: <code>--body-font-family</code>. Size: <code>--body-font-size</code> (1rem).</p>
|
|
||||||
<p><strong>Bold text.</strong> <em>Italic text.</em> <a href="#">This is a link</a>. <code>Inline code</code>. <mark style="background: var(--highlight-bg); color: var(--highlight-color);">Highlighted text</mark>.</p>
|
|
||||||
<p class="lead">This is lead text styled with <code>--muted-color</code>.</p>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
6. LINKS
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>6. Link Colors</h2>
|
|
||||||
<table class="var-table">
|
|
||||||
<tr><th>Variable</th><th>Preview</th></tr>
|
|
||||||
<tr><td><code>--link-color</code></td><td><a href="#" style="color: var(--link-color)">Sample link</a></td></tr>
|
|
||||||
<tr><td><code>--link-hover-color</code></td><td><span style="color: var(--link-hover-color); text-decoration: underline; cursor: pointer;">Hover state</span></td></tr>
|
|
||||||
<tr><td><code>--color-link</code></td><td><span style="color: var(--color-link)">color-link value</span></td></tr>
|
|
||||||
<tr><td><code>--color-hover</code></td><td><span style="color: var(--color-hover)">color-hover value</span></td></tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
7. BUTTONS (Bootstrap-style)
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>7. Buttons</h2>
|
|
||||||
<div style="display: flex; flex-wrap: wrap; gap: .5rem; margin: 1rem 0;">
|
|
||||||
<button class="btn btn-primary">Primary</button>
|
|
||||||
<button class="btn btn-secondary">Secondary</button>
|
|
||||||
<button class="btn btn-success">Success</button>
|
|
||||||
<button class="btn btn-danger">Danger</button>
|
|
||||||
<button class="btn btn-warning">Warning</button>
|
|
||||||
<button class="btn btn-info">Info</button>
|
|
||||||
<button class="btn btn-light">Light</button>
|
|
||||||
<button class="btn btn-dark">Dark</button>
|
|
||||||
</div>
|
|
||||||
<div style="display: flex; flex-wrap: wrap; gap: .5rem; margin: 1rem 0;">
|
|
||||||
<button class="btn btn-outline-primary">Outline Primary</button>
|
|
||||||
<button class="btn btn-outline-secondary">Outline Secondary</button>
|
|
||||||
<button class="btn btn-outline-success">Outline Success</button>
|
|
||||||
<button class="btn btn-outline-danger">Outline Danger</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
8. CARDS
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>8. Cards</h2>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<div class="card" style="background: var(--card-bg); border: var(--card-border-width) solid var(--card-border-color); border-radius: var(--card-border-radius); padding: 0;">
|
|
||||||
<div style="padding: var(--card-cap-padding-y) var(--card-cap-padding-x); background: var(--card-cap-bg); color: var(--card-cap-color); border-bottom: 1px solid var(--card-border-color); font-weight: 600;">Card Header</div>
|
|
||||||
<div style="padding: var(--card-spacer-y) var(--card-spacer-x); color: var(--card-color);">
|
|
||||||
<h5 style="margin-top: 0;">Card Title</h5>
|
|
||||||
<p style="margin-bottom: .5rem;">Card body using <code>--card-bg</code>, <code>--card-color</code>, and <code>--card-border-color</code>.</p>
|
|
||||||
<button class="btn btn-primary" style="font-size: .875rem;">Action</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
<div class="card" style="background: var(--card-bg); border: var(--card-border-width) solid var(--card-border-color); border-radius: var(--card-border-radius); padding: var(--card-spacer-y) var(--card-spacer-x); color: var(--card-color);">
|
|
||||||
<h5 style="margin-top: 0;">Simple Card</h5>
|
|
||||||
<p>No header, just body content. Uses the same card variables.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
9. FORMS
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>9. Form Elements</h2>
|
|
||||||
<div style="max-width: 480px;">
|
|
||||||
<div style="margin-bottom: 1rem;">
|
|
||||||
<label style="display: block; margin-bottom: .25rem; font-weight: 500;">Text Input</label>
|
|
||||||
<input type="text" placeholder="Placeholder text" style="width: 100%; padding: .375rem .75rem; border: 1px solid var(--input-border-color, #ced4da); border-radius: var(--border-radius); background: var(--input-bg, #fff); color: var(--input-color, #22262a); font-size: 1rem;">
|
|
||||||
</div>
|
|
||||||
<div style="margin-bottom: 1rem;">
|
|
||||||
<label style="display: block; margin-bottom: .25rem; font-weight: 500;">Select</label>
|
|
||||||
<select style="width: 100%; padding: .375rem .75rem; border: 1px solid var(--input-border-color, #ced4da); border-radius: var(--border-radius); background: var(--input-bg, #fff); color: var(--input-color, #22262a); font-size: 1rem;">
|
|
||||||
<option>Option 1</option>
|
|
||||||
<option>Option 2</option>
|
|
||||||
<option>Option 3</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div style="margin-bottom: 1rem;">
|
|
||||||
<label style="display: block; margin-bottom: .25rem; font-weight: 500;">Textarea</label>
|
|
||||||
<textarea rows="3" style="width: 100%; padding: .375rem .75rem; border: 1px solid var(--input-border-color, #ced4da); border-radius: var(--border-radius); background: var(--input-bg, #fff); color: var(--input-color, #22262a); font-size: 1rem;">Sample text content</textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
10. ALERTS (Bootstrap-style)
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>10. Alerts</h2>
|
|
||||||
<div style="padding: .75rem 1rem; margin-bottom: .75rem; border-radius: var(--border-radius); background: var(--primary-bg-subtle); color: var(--primary-text-emphasis); border: 1px solid var(--primary-border-subtle);">
|
|
||||||
<strong>Primary alert.</strong> Uses <code>--primary-bg-subtle</code> and <code>--primary-text-emphasis</code>.
|
|
||||||
</div>
|
|
||||||
<div style="padding: .75rem 1rem; margin-bottom: .75rem; border-radius: var(--border-radius); background: var(--success-bg-subtle); color: var(--success-text-emphasis); border: 1px solid var(--success-border-subtle);">
|
|
||||||
<strong>Success alert.</strong> Uses <code>--success-bg-subtle</code> and <code>--success-text-emphasis</code>.
|
|
||||||
</div>
|
|
||||||
<div style="padding: .75rem 1rem; margin-bottom: .75rem; border-radius: var(--border-radius); background: var(--warning-bg-subtle); color: var(--warning-text-emphasis); border: 1px solid var(--warning-border-subtle);">
|
|
||||||
<strong>Warning alert.</strong> Uses <code>--warning-bg-subtle</code> and <code>--warning-text-emphasis</code>.
|
|
||||||
</div>
|
|
||||||
<div style="padding: .75rem 1rem; margin-bottom: .75rem; border-radius: var(--border-radius); background: var(--danger-bg-subtle); color: var(--danger-text-emphasis); border: 1px solid var(--danger-border-subtle);">
|
|
||||||
<strong>Danger alert.</strong> Uses <code>--danger-bg-subtle</code> and <code>--danger-text-emphasis</code>.
|
|
||||||
</div>
|
|
||||||
<div style="padding: .75rem 1rem; margin-bottom: .75rem; border-radius: var(--border-radius); background: var(--info-bg-subtle); color: var(--info-text-emphasis); border: 1px solid var(--info-border-subtle);">
|
|
||||||
<strong>Info alert.</strong> Uses <code>--info-bg-subtle</code> and <code>--info-text-emphasis</code>.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
11. BORDERS & SHADOWS
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>11. Borders & Shadows</h2>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col" style="padding: 1.5rem; border: var(--border-width) var(--border-style) var(--border-color); border-radius: var(--border-radius); margin-bottom: 1rem;">
|
|
||||||
Default border: <code>--border-width</code> / <code>--border-color</code> / <code>--border-radius</code>
|
|
||||||
</div>
|
|
||||||
<div class="col" style="padding: 1.5rem; border-radius: var(--border-radius); box-shadow: var(--box-shadow-sm); margin-bottom: 1rem;">
|
|
||||||
<code>--box-shadow-sm</code>
|
|
||||||
</div>
|
|
||||||
<div class="col" style="padding: 1.5rem; border-radius: var(--border-radius); box-shadow: var(--box-shadow); margin-bottom: 1rem;">
|
|
||||||
<code>--box-shadow</code>
|
|
||||||
</div>
|
|
||||||
<div class="col" style="padding: 1.5rem; border-radius: var(--border-radius); box-shadow: var(--box-shadow-lg); margin-bottom: 1rem;">
|
|
||||||
<code>--box-shadow-lg</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
12. NAVIGATION COLORS
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>12. Navigation Colors</h2>
|
|
||||||
<div class="swatch-grid">
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--nav-bg-color)"></div>
|
|
||||||
<div class="swatch-label"><code>--nav-bg-color</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--nav-text-color); border: 1px solid var(--border-color)"></div>
|
|
||||||
<div class="swatch-label"><code>--nav-text-color</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--mainmenu-nav-link-color); border: 1px solid var(--border-color)"></div>
|
|
||||||
<div class="swatch-label"><code>--mainmenu-nav-link-color</code></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
13. CONTAINER BACKGROUNDS
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>13. Container Background Variables</h2>
|
|
||||||
<table class="var-table">
|
|
||||||
<tr><th>Container</th><th>BG Color</th><th>BG Image</th><th>Border</th></tr>
|
|
||||||
<tr><td>below-topbar</td><td><code>--container-below-topbar-bg-color</code></td><td><code>--container-below-topbar-bg-image</code></td><td><code>--container-below-topbar-border</code></td></tr>
|
|
||||||
<tr><td>top-a</td><td><code>--container-top-a-bg-color</code></td><td><code>--container-top-a-bg-image</code></td><td><code>--container-top-a-border</code></td></tr>
|
|
||||||
<tr><td>top-b</td><td><code>--container-top-b-bg-color</code></td><td><code>--container-top-b-bg-image</code></td><td><code>--container-top-b-border</code></td></tr>
|
|
||||||
<tr><td>bottom-a</td><td><code>--container-bottom-a-bg-color</code></td><td><code>--container-bottom-a-bg-image</code></td><td><code>--container-bottom-a-border</code></td></tr>
|
|
||||||
<tr><td>bottom-b</td><td><code>--container-bottom-b-bg-color</code></td><td><code>--container-bottom-b-bg-image</code></td><td><code>--container-bottom-b-border</code></td></tr>
|
|
||||||
<tr><td>sidebar</td><td><code>--container-sidebar-bg-color</code></td><td><code>--container-sidebar-bg-image</code></td><td><code>--container-sidebar-border</code></td></tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
14. HERO VARIANTS (NEW)
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>14. Hero Variants <span style="font-size: .65em; color: var(--success); font-weight: normal;">NEW</span></h2>
|
|
||||||
<p>The <code>.hero#primary</code> and <code>.hero#secondary</code> variants use CSS variables for background color, overlay gradient, and text color. Each adapts automatically with the active theme.</p>
|
|
||||||
|
|
||||||
<h3>Primary Variant — <code>.hero#primary</code></h3>
|
|
||||||
<div class="hero" id="primary" style="background-image: url('data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%22400%22 height=%22200%22><rect fill=%22%23a3cde2%22 width=%22400%22 height=%22200%22/><text x=%2250%25%22 y=%2250%25%22 text-anchor=%22middle%22 dy=%22.3em%22 font-family=%22sans-serif%22 font-size=%2216%22 fill=%22%23112855%22>Hero Background Image Area</text></svg>'); padding: 0;">
|
|
||||||
<div style="padding: 3rem 2rem; text-align: center;">
|
|
||||||
<h2 style="border: none; padding: 0; margin: 0 0 .5rem 0; font-size: 2rem;">Primary Hero</h2>
|
|
||||||
<p style="margin: 0; font-size: 1.1rem;">Homepage & main landing pages — sky blue tint, softer overlay</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>Secondary Variant — <code>.hero#secondary</code></h3>
|
|
||||||
<div class="hero" id="secondary" style="background-image: url('data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%22400%22 height=%22200%22><rect fill=%22%23112855%22 width=%22400%22 height=%22200%22/><text x=%2250%25%22 y=%2250%25%22 text-anchor=%22middle%22 dy=%22.3em%22 font-family=%22sans-serif%22 font-size=%2216%22 fill=%22%23f1f5f9%22>Hero Background Image Area</text></svg>'); padding: 0;">
|
|
||||||
<div style="padding: 3rem 2rem; text-align: center;">
|
|
||||||
<h2 style="border: none; padding: 0; margin: 0 0 .5rem 0; font-size: 2rem;">Secondary Hero</h2>
|
|
||||||
<p style="margin: 0; font-size: 1.1rem;">Inner pages, events, about — navy overlay, lighter text</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>Hero Variable Reference</h3>
|
|
||||||
<table class="var-table">
|
|
||||||
<tr><th>Variable</th><th>Variant</th><th>Purpose</th></tr>
|
|
||||||
<tr><td><code>--hero-primary-bg-color</code></td><td>Primary</td><td>Fallback background color</td></tr>
|
|
||||||
<tr><td><code>--hero-primary-overlay</code></td><td>Primary</td><td>Gradient overlay tint</td></tr>
|
|
||||||
<tr><td><code>--hero-primary-color</code></td><td>Primary</td><td>Text color</td></tr>
|
|
||||||
<tr><td><code>--hero-secondary-bg-color</code></td><td>Secondary</td><td>Fallback background color</td></tr>
|
|
||||||
<tr><td><code>--hero-secondary-overlay</code></td><td>Secondary</td><td>Gradient overlay tint</td></tr>
|
|
||||||
<tr><td><code>--hero-secondary-color</code></td><td>Secondary</td><td>Text color</td></tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
15. BLOCK COLOR SYSTEM (NEW)
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>15. Block Color System <span style="font-size: .65em; color: var(--success); font-weight: normal;">NEW</span></h2>
|
|
||||||
<p>Modules in <code>top-a</code>, <code>top-b</code>, <code>bottom-a</code>, and <code>bottom-b</code> positions automatically receive brand colors based on their order. No classes needed — <code>:nth-child()</code> handles assignment.</p>
|
|
||||||
|
|
||||||
<h3>Slot Palette Preview</h3>
|
|
||||||
<div class="block-demo">
|
|
||||||
<div class="card" style="background-color: var(--block-color-1); color: var(--block-text-1);">
|
|
||||||
<strong>Slot 1</strong><br>
|
|
||||||
<code style="color: inherit; background: rgba(255,255,255,.15);">--block-color-1</code>
|
|
||||||
</div>
|
|
||||||
<div class="card" style="background-color: var(--block-color-2); color: var(--block-text-2);">
|
|
||||||
<strong>Slot 2</strong><br>
|
|
||||||
<code style="color: inherit; background: rgba(255,255,255,.15);">--block-color-2</code>
|
|
||||||
</div>
|
|
||||||
<div class="card" style="background-color: var(--block-color-3); color: var(--block-text-3);">
|
|
||||||
<strong>Slot 3</strong><br>
|
|
||||||
<code style="color: inherit; background: rgba(255,255,255,.15);">--block-color-3</code>
|
|
||||||
</div>
|
|
||||||
<div class="card" style="background-color: var(--block-color-4); color: var(--block-text-4);">
|
|
||||||
<strong>Slot 4</strong><br>
|
|
||||||
<code style="color: inherit; background: rgba(255,255,255,.15);">--block-color-4</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>Named Override Preview</h3>
|
|
||||||
<div class="block-demo">
|
|
||||||
<div class="card" style="background-color: var(--block-highlight-bg); color: var(--block-highlight-text);">
|
|
||||||
<strong>#block-highlight</strong><br>
|
|
||||||
<code style="color: inherit; background: rgba(255,255,255,.15);">--block-highlight-bg</code>
|
|
||||||
</div>
|
|
||||||
<div class="card" style="background-color: var(--block-cta-bg); color: var(--block-cta-text);">
|
|
||||||
<strong>#block-cta</strong><br>
|
|
||||||
<code style="color: inherit; background: rgba(255,255,255,.15);">--block-cta-bg</code>
|
|
||||||
</div>
|
|
||||||
<div class="card" style="background-color: var(--block-alert-bg); color: var(--block-alert-text);">
|
|
||||||
<strong>#block-alert</strong><br>
|
|
||||||
<code style="color: inherit; background: rgba(255,255,255,.15);">--block-alert-bg</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>Block Variable Reference</h3>
|
|
||||||
<table class="var-table">
|
|
||||||
<tr><th>Variable</th><th>Purpose</th></tr>
|
|
||||||
<tr><td><code>--block-color-1</code> / <code>--block-text-1</code></td><td>1st module in position (automatic)</td></tr>
|
|
||||||
<tr><td><code>--block-color-2</code> / <code>--block-text-2</code></td><td>2nd module in position (automatic)</td></tr>
|
|
||||||
<tr><td><code>--block-color-3</code> / <code>--block-text-3</code></td><td>3rd module in position (automatic)</td></tr>
|
|
||||||
<tr><td><code>--block-color-4</code> / <code>--block-text-4</code></td><td>4th module in position (automatic)</td></tr>
|
|
||||||
<tr><td><code>--block-highlight-bg</code> / <code>--block-highlight-text</code></td><td>Named override for <code>#block-highlight</code></td></tr>
|
|
||||||
<tr><td><code>--block-cta-bg</code> / <code>--block-cta-text</code></td><td>Named override for <code>#block-cta</code></td></tr>
|
|
||||||
<tr><td><code>--block-alert-bg</code> / <code>--block-alert-text</code></td><td>Named override for <code>#block-alert</code></td></tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<h3>Override Priority</h3>
|
|
||||||
<table class="var-table">
|
|
||||||
<tr><th>Priority</th><th>Method</th><th>How Applied</th></tr>
|
|
||||||
<tr><td>1 (highest)</td><td>Named module ID (<code>#block-highlight</code>)</td><td>ID in module HTML + named variable</td></tr>
|
|
||||||
<tr><td>2 (default)</td><td>Slot color (<code>--block-color-N</code>)</td><td>Automatic by <code>:nth-child()</code> order</td></tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
16. VIRTUEMART COLORS
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>16. VirtueMart Surface Colors</h2>
|
|
||||||
<div class="swatch-grid">
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--vm-surface, #fff)"></div>
|
|
||||||
<div class="swatch-label"><code>--vm-surface</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--vm-surface-2, #f8f9fa)"></div>
|
|
||||||
<div class="swatch-label"><code>--vm-surface-2</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--vm-price-color, #448344)"></div>
|
|
||||||
<div class="swatch-label"><code>--vm-price-color</code></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
17. GABLE COLORS
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>17. Gable Colors</h2>
|
|
||||||
<div class="swatch-grid">
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--gab-blue)"></div>
|
|
||||||
<div class="swatch-label"><code>--gab-blue</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--gab-green)"></div>
|
|
||||||
<div class="swatch-label"><code>--gab-green</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--gab-red)"></div>
|
|
||||||
<div class="swatch-label"><code>--gab-red</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--gab-orange)"></div>
|
|
||||||
<div class="swatch-label"><code>--gab-orange</code></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
18. CODE SAMPLES
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>18. Code & Preformatted Text</h2>
|
|
||||||
<p>Inline code: <code>var(--color-primary)</code></p>
|
|
||||||
<pre>/* Example: overriding block slot 1 in colors_custom.css */
|
|
||||||
--block-color-1: var(--accent-color-primary);
|
|
||||||
--block-text-1: #fff;
|
|
||||||
|
|
||||||
/* Hero variant usage in module HTML */
|
|
||||||
<div class="hero" id="primary"
|
|
||||||
style="background-image:url('/images/hero/main.jpg')">
|
|
||||||
<div class="col-12 py-5 px-4 text-center">
|
|
||||||
...content...
|
|
||||||
</div>
|
|
||||||
</div></pre>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
19. OPACITY UTILITIES
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>19. Opacity Scale</h2>
|
|
||||||
<div style="display: flex; gap: .5rem; flex-wrap: wrap;">
|
|
||||||
<div style="width: 60px; height: 60px; background: var(--color-primary); opacity: var(--opacity-5); border-radius: var(--border-radius); display: flex; align-items: center; justify-content: center; font-size: .7rem;">5%</div>
|
|
||||||
<div style="width: 60px; height: 60px; background: var(--color-primary); opacity: var(--opacity-10); border-radius: var(--border-radius); display: flex; align-items: center; justify-content: center; font-size: .7rem;">10%</div>
|
|
||||||
<div style="width: 60px; height: 60px; background: var(--color-primary); opacity: var(--opacity-15); border-radius: var(--border-radius); display: flex; align-items: center; justify-content: center; font-size: .7rem;">15%</div>
|
|
||||||
<div style="width: 60px; height: 60px; background: var(--color-primary); opacity: var(--opacity-25); border-radius: var(--border-radius); display: flex; align-items: center; justify-content: center; font-size: .7rem; color: #fff;">25%</div>
|
|
||||||
<div style="width: 60px; height: 60px; background: var(--color-primary); opacity: var(--opacity-50); border-radius: var(--border-radius); display: flex; align-items: center; justify-content: center; font-size: .7rem; color: #fff;">50%</div>
|
|
||||||
<div style="width: 60px; height: 60px; background: var(--color-primary); opacity: var(--opacity-75); border-radius: var(--border-radius); display: flex; align-items: center; justify-content: center; font-size: .7rem; color: #fff;">75%</div>
|
|
||||||
<div style="width: 60px; height: 60px; background: var(--color-primary); opacity: var(--opacity-100); border-radius: var(--border-radius); display: flex; align-items: center; justify-content: center; font-size: .7rem; color: #fff;">100%</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
21. BRANDED BOOTSTRAP 5 SHOWCASE
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>21. Branded Bootstrap 5 Showcase</h2>
|
|
||||||
<p>Comprehensive component demos using MokoOnyx's brand variables. Mirrors the live Joomla article at <code>/style/branded-bootstrap5</code>.</p>
|
|
||||||
|
|
||||||
<!-- BRAND HEADER (gradient + border via variables) -->
|
|
||||||
<div class="text-center p-4 mb-4" style="background: var(--header-background-image); background-position: center; background-attachment: fixed; background-repeat: repeat; background-size: auto; border-bottom: var(--border, 5px) solid var(--accent-color-primary); color: var(--color-primary);">
|
|
||||||
<h3 class="mb-1" style="border: none; padding: 0; margin: 0;">Brand + Bootstrap Showcase</h3>
|
|
||||||
<p class="lead mb-0" style="color: inherit;">Comprehensive components with toggleable code samples</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- NAV SAMPLE (brand colors) -->
|
|
||||||
<nav class="d-flex align-items-center gap-3 px-3 py-2 mb-4" style="background: var(--nav-bg-color); border-radius: var(--border-radius, .25rem);">
|
|
||||||
<span class="fw-bold" style="color: var(--nav-text-color);">Brand Nav</span>
|
|
||||||
<a href="#" class="text-decoration-none" style="color: var(--mainmenu-nav-link-color);">Home</a>
|
|
||||||
<a href="#" class="text-decoration-none" style="color: var(--mainmenu-nav-link-color);">About</a>
|
|
||||||
<a href="#" class="text-decoration-none ms-auto fw-semibold" style="color: var(--accent-color-secondary);">Contact</a>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<!-- BREADCRUMB -->
|
|
||||||
<nav aria-label="Breadcrumbs" style="margin-bottom: 1.5rem;">
|
|
||||||
<ol class="breadcrumb px-3 py-2" style="background: var(--secondary-bg, #eaedf0); border-radius: var(--border-radius, .25rem); margin: 0; list-style: none; display: flex; flex-wrap: wrap; padding: .5rem 1rem; font-size: .875rem;">
|
|
||||||
<li class="breadcrumb-item"><a href="#">Home</a></li>
|
|
||||||
<li class="breadcrumb-item"><a href="#">Style</a></li>
|
|
||||||
<li class="breadcrumb-item active" style="color: var(--muted-color);">Branded Bootstrap5</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<!-- TYPOGRAPHY SECTION -->
|
|
||||||
<h3>Typography</h3>
|
|
||||||
<div class="row" style="margin-bottom: 2rem;">
|
|
||||||
<div class="col">
|
|
||||||
<h1 style="border: none; padding: 0; margin: .5rem 0;">H1 Heading</h1>
|
|
||||||
<h2 style="border: none; padding: 0; margin: .5rem 0;">H2 Heading</h2>
|
|
||||||
<h3 style="margin: .5rem 0;">H3 Heading</h3>
|
|
||||||
<h4>H4 Heading</h4>
|
|
||||||
<h5>H5 Heading</h5>
|
|
||||||
<h6>H6 Heading</h6>
|
|
||||||
<p class="lead">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.</p>
|
|
||||||
<p>Curabitur <strong>ullamcorper</strong> nec <em>nisi</em> a <a href="#">themed link</a>. Nulla vitae <code><section></code> purus.</p>
|
|
||||||
<blockquote style="border-left: var(--border, 5px) solid var(--accent-color-primary); padding-left: 1rem; margin: 1rem 0;">
|
|
||||||
<p style="margin-bottom: .25rem;">"Design is intelligence made visible."</p>
|
|
||||||
<footer style="color: var(--muted-color); font-size: .875rem;">— Alina Wheeler</footer>
|
|
||||||
</blockquote>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- BUTTONS & GROUPS -->
|
|
||||||
<h3>Buttons & Button Groups</h3>
|
|
||||||
<div style="display: flex; flex-wrap: wrap; gap: .5rem; margin-bottom: 1rem;">
|
|
||||||
<button class="btn btn-primary">Primary</button>
|
|
||||||
<button class="btn btn-secondary">Secondary</button>
|
|
||||||
<button class="btn btn-success">Success</button>
|
|
||||||
<button class="btn btn-info">Info</button>
|
|
||||||
<button class="btn btn-warning">Warning</button>
|
|
||||||
<button class="btn btn-danger">Danger</button>
|
|
||||||
<button class="btn btn-light">Light</button>
|
|
||||||
<button class="btn btn-dark">Dark</button>
|
|
||||||
<button class="btn" style="background: var(--accent-color-primary); color: var(--color-primary); border: none;">Accent</button>
|
|
||||||
</div>
|
|
||||||
<div style="display: inline-flex; margin-bottom: 1.5rem;">
|
|
||||||
<button class="btn btn-outline-primary" style="border-radius: var(--border-radius, .25rem) 0 0 var(--border-radius, .25rem);">Left</button>
|
|
||||||
<button class="btn btn-outline-primary" style="border-radius: 0;">Middle</button>
|
|
||||||
<button class="btn btn-outline-primary" style="border-radius: 0 var(--border-radius, .25rem) var(--border-radius, .25rem) 0;">Right</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- BADGES & ALERTS -->
|
|
||||||
<h3>Badges & Alerts</h3>
|
|
||||||
<div style="display: flex; flex-wrap: wrap; gap: .35rem; margin-bottom: 1rem;">
|
|
||||||
<span style="display: inline-block; padding: .25em .5em; font-size: .75em; font-weight: 700; border-radius: var(--border-radius, .25rem); background: var(--primary); color: #fff;">Primary</span>
|
|
||||||
<span style="display: inline-block; padding: .25em .5em; font-size: .75em; font-weight: 700; border-radius: var(--border-radius, .25rem); background: var(--secondary); color: #fff;">Secondary</span>
|
|
||||||
<span style="display: inline-block; padding: .25em .5em; font-size: .75em; font-weight: 700; border-radius: var(--border-radius, .25rem); background: var(--success); color: #fff;">Success</span>
|
|
||||||
<span style="display: inline-block; padding: .25em .5em; font-size: .75em; font-weight: 700; border-radius: var(--border-radius, .25rem); background: var(--warning); color: var(--body-color);">Warning</span>
|
|
||||||
<span style="display: inline-block; padding: .25em .5em; font-size: .75em; font-weight: 700; border-radius: var(--border-radius, .25rem); background: var(--danger); color: #fff;">Danger</span>
|
|
||||||
<span style="display: inline-block; padding: .25em .5em; font-size: .75em; font-weight: 700; border-radius: var(--border-radius, .25rem); background: var(--accent-color-primary); color: var(--color-primary);">Accent</span>
|
|
||||||
</div>
|
|
||||||
<div style="padding: .75rem 1rem; margin-bottom: .75rem; border-radius: var(--border-radius); background: var(--primary-bg-subtle); color: var(--primary-text-emphasis); border: 1px solid var(--primary-border-subtle);">
|
|
||||||
<strong>Primary:</strong> Vivamus sagittis lacus vel augue.
|
|
||||||
</div>
|
|
||||||
<div style="padding: .75rem 1rem; margin-bottom: .75rem; border-radius: var(--border-radius); background: var(--warning-bg-subtle); color: var(--warning-text-emphasis); border: 1px solid var(--warning-border-subtle);">
|
|
||||||
Cras mattis consectetur purus sit amet fermentum.
|
|
||||||
</div>
|
|
||||||
<div style="padding: .75rem 1rem; margin-bottom: 1.5rem; border-radius: var(--border-radius); background: var(--accent-color-primary); color: var(--color-primary); border: var(--border, 5px) solid var(--accent-color-secondary);">
|
|
||||||
Brand alert — Aenean lacinia bibendum nulla sed consectetur.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- TABLES -->
|
|
||||||
<h3>Tables</h3>
|
|
||||||
<div style="overflow-x: auto; margin-bottom: 1.5rem;">
|
|
||||||
<table class="var-table">
|
|
||||||
<thead><tr style="background: var(--dark, #212529); color: #fff;"><th>#</th><th>Name</th><th>Status</th><th>Notes</th></tr></thead>
|
|
||||||
<tbody>
|
|
||||||
<tr><td>1</td><td>Alpha</td><td><span style="display: inline-block; padding: .15em .4em; font-size: .75em; font-weight: 700; border-radius: var(--border-radius); background: var(--success); color: #fff;">Active</span></td><td>Lorem ipsum dolor sit amet.</td></tr>
|
|
||||||
<tr><td>2</td><td>Beta</td><td><span style="display: inline-block; padding: .15em .4em; font-size: .75em; font-weight: 700; border-radius: var(--border-radius); background: var(--warning); color: var(--body-color);">Pending</span></td><td>Integer posuere erat a ante.</td></tr>
|
|
||||||
<tr><td>3</td><td>Gamma</td><td><span style="display: inline-block; padding: .15em .4em; font-size: .75em; font-weight: 700; border-radius: var(--border-radius); background: var(--danger); color: #fff;">Blocked</span></td><td>Donec id elit non mi porta.</td></tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- FORMS (BRANDED) -->
|
|
||||||
<h3>Branded Forms</h3>
|
|
||||||
<div style="max-width: 600px; margin-bottom: 1.5rem;">
|
|
||||||
<div style="display: flex; flex-wrap: wrap; gap: 1rem; margin-bottom: 1rem;">
|
|
||||||
<div style="flex: 1; min-width: 200px;">
|
|
||||||
<label style="display: block; margin-bottom: .25rem; font-weight: 500;">Email</label>
|
|
||||||
<input type="email" placeholder="name@example.com" style="width: 100%; padding: .375rem .75rem; border: 1px solid var(--input-border-color, #ced4da); border-radius: var(--border-radius); background: var(--input-bg, #fff); color: var(--input-color, #22262a);">
|
|
||||||
</div>
|
|
||||||
<div style="flex: 1; min-width: 200px;">
|
|
||||||
<label style="display: block; margin-bottom: .25rem; font-weight: 500;">Password</label>
|
|
||||||
<input type="password" placeholder="••••••••" style="width: 100%; padding: .375rem .75rem; border: 1px solid var(--input-border-color, #ced4da); border-radius: var(--border-radius); background: var(--input-bg, #fff); color: var(--input-color, #22262a);">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="margin-bottom: 1rem;">
|
|
||||||
<label style="display: block; margin-bottom: .25rem; font-weight: 500;">Input Group</label>
|
|
||||||
<div style="display: flex;">
|
|
||||||
<span style="display: flex; align-items: center; padding: .375rem .75rem; background: var(--secondary-bg, #eaedf0); border: 1px solid var(--input-border-color, #ced4da); border-right: none; border-radius: var(--border-radius) 0 0 var(--border-radius);">@</span>
|
|
||||||
<input type="text" placeholder="username" style="flex: 1; padding: .375rem .75rem; border: 1px solid var(--input-border-color, #ced4da); background: var(--input-bg, #fff); color: var(--input-color, #22262a);">
|
|
||||||
<button class="btn btn-outline-secondary" style="border-radius: 0 var(--border-radius) var(--border-radius) 0;">Search</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-primary">Submit</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- CARDS & LIST GROUPS -->
|
|
||||||
<h3>Branded Cards & List Groups</h3>
|
|
||||||
<div class="row" style="margin-bottom: 1.5rem;">
|
|
||||||
<div class="col">
|
|
||||||
<div style="border: 1px solid var(--card-border-color, var(--border-color)); border-radius: var(--card-border-radius, var(--border-radius)); overflow: hidden; background: var(--card-bg, var(--body-bg));">
|
|
||||||
<div style="padding: .5rem 1rem; background: var(--accent-color-primary); color: var(--color-primary); font-weight: 600;">Featured</div>
|
|
||||||
<div style="padding: var(--card-spacer-y, 1rem) var(--card-spacer-x, 1rem); color: var(--card-color, var(--body-color));">
|
|
||||||
<h5 style="margin-top: 0;">Card title</h5>
|
|
||||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.</p>
|
|
||||||
<button class="btn btn-primary" style="font-size: .875rem;">Go somewhere</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
<div style="border: 1px solid var(--border-color); border-radius: var(--border-radius); overflow: hidden;">
|
|
||||||
<div style="padding: .5rem 1rem; background: var(--primary); color: #fff; font-weight: 500;">Active item</div>
|
|
||||||
<div style="padding: .5rem 1rem; border-bottom: 1px solid var(--border-color);">Second item</div>
|
|
||||||
<div style="padding: .5rem 1rem; display: flex; justify-content: space-between; align-items: center;">With badge <span style="display: inline-block; padding: .15em .5em; font-size: .75em; font-weight: 700; border-radius: 50rem; background: var(--primary); color: #fff;">4</span></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- BREADCRUMB & PAGINATION -->
|
|
||||||
<h3>Breadcrumb & Pagination</h3>
|
|
||||||
<nav aria-label="breadcrumb" style="margin-bottom: 1rem;">
|
|
||||||
<ol style="list-style: none; display: flex; flex-wrap: wrap; padding: .5rem 1rem; margin: 0; background: var(--secondary-bg, #eaedf0); border-radius: var(--border-radius); font-size: .875rem; gap: .5rem;">
|
|
||||||
<li><a href="#">Home</a> /</li>
|
|
||||||
<li><a href="#">Library</a> /</li>
|
|
||||||
<li style="color: var(--muted-color);">Data</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
<nav aria-label="Pagination" style="margin-bottom: 1.5rem;">
|
|
||||||
<ul style="list-style: none; display: flex; padding: 0; margin: 0; gap: 0;">
|
|
||||||
<li><span style="display: block; padding: .375rem .75rem; border: 1px solid var(--border-color); color: var(--muted-color); border-radius: var(--border-radius) 0 0 var(--border-radius);">Previous</span></li>
|
|
||||||
<li><a href="#" style="display: block; padding: .375rem .75rem; border: 1px solid var(--border-color); border-left: none; text-decoration: none;">1</a></li>
|
|
||||||
<li><a href="#" style="display: block; padding: .375rem .75rem; border: 1px solid var(--border-color); border-left: none; text-decoration: none;">2</a></li>
|
|
||||||
<li><a href="#" style="display: block; padding: .375rem .75rem; border: 1px solid var(--border-color); border-left: none; text-decoration: none;">3</a></li>
|
|
||||||
<li><a href="#" style="display: block; padding: .375rem .75rem; border: 1px solid var(--border-color); border-left: none; text-decoration: none; border-radius: 0 var(--border-radius) var(--border-radius) 0;">Next</a></li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<!-- PROGRESS BARS -->
|
|
||||||
<h3>Progress Bars</h3>
|
|
||||||
<div style="margin-bottom: 1.5rem;">
|
|
||||||
<div style="height: 20px; background: var(--secondary-bg, #eaedf0); border-radius: var(--border-radius); margin-bottom: .5rem; overflow: hidden;">
|
|
||||||
<div style="width: 25%; height: 100%; background: var(--accent-color-primary); color: var(--color-primary); display: flex; align-items: center; justify-content: center; font-size: .75rem; font-weight: 600;">25%</div>
|
|
||||||
</div>
|
|
||||||
<div style="height: 20px; background: var(--secondary-bg, #eaedf0); border-radius: var(--border-radius); overflow: hidden;">
|
|
||||||
<div style="width: 65%; height: 100%; background: var(--success); color: #fff; display: flex; align-items: center; justify-content: center; font-size: .75rem; font-weight: 600;">65%</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- CSS VARIABLE SWATCHES (COMPUTED VALUES) -->
|
|
||||||
<h3>CSS Variable Swatches (Computed)</h3>
|
|
||||||
<p style="color: var(--muted-color);">Visual preview of key variables with their resolved values displayed via JavaScript.</p>
|
|
||||||
<div class="swatch-grid" id="computed-swatches">
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--color-primary)"></div>
|
|
||||||
<div class="swatch-label"><code>--color-primary</code><br><small class="computed-val" data-var="--color-primary"></small></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--color-link)"></div>
|
|
||||||
<div class="swatch-label"><code>--color-link</code><br><small class="computed-val" data-var="--color-link"></small></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--color-hover)"></div>
|
|
||||||
<div class="swatch-label"><code>--color-hover</code><br><small class="computed-val" data-var="--color-hover"></small></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--accent-color-primary)"></div>
|
|
||||||
<div class="swatch-label"><code>--accent-color-primary</code><br><small class="computed-val" data-var="--accent-color-primary"></small></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--accent-color-secondary)"></div>
|
|
||||||
<div class="swatch-label"><code>--accent-color-secondary</code><br><small class="computed-val" data-var="--accent-color-secondary"></small></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--nav-bg-color)"></div>
|
|
||||||
<div class="swatch-label"><code>--nav-bg-color</code><br><small class="computed-val" data-var="--nav-bg-color"></small></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--body-bg); border: 1px solid var(--border-color);"></div>
|
|
||||||
<div class="swatch-label"><code>--body-bg</code><br><small class="computed-val" data-var="--body-bg"></small></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--body-color)"></div>
|
|
||||||
<div class="swatch-label"><code>--body-color</code><br><small class="computed-val" data-var="--body-color"></small></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--border-color)"></div>
|
|
||||||
<div class="swatch-label"><code>--border-color</code><br><small class="computed-val" data-var="--border-color"></small></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
<p style="color: var(--muted-color); font-size: .8rem; text-align: center; margin: 2rem 0;">
|
|
||||||
MokoOnyx Theme Test Sheet — v03.09.02 — © 2026 Moko Consulting
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</div><!-- /.test-container -->
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Populate computed CSS variable values
|
|
||||||
function updateComputedValues() {
|
|
||||||
const root = document.documentElement;
|
|
||||||
const style = getComputedStyle(root);
|
|
||||||
document.querySelectorAll('.computed-val').forEach(el => {
|
|
||||||
const varName = el.dataset.var;
|
|
||||||
if (varName) {
|
|
||||||
el.textContent = style.getPropertyValue(varName).trim() || '(not set)';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
updateComputedValues();
|
|
||||||
|
|
||||||
function toggleTheme() {
|
|
||||||
const html = document.documentElement;
|
|
||||||
const current = html.getAttribute('data-bs-theme');
|
|
||||||
const next = current === 'light' ? 'dark' : 'light';
|
|
||||||
html.setAttribute('data-bs-theme', next);
|
|
||||||
|
|
||||||
// Swap stylesheet
|
|
||||||
const links = document.querySelectorAll('link[rel="stylesheet"]');
|
|
||||||
links.forEach(link => {
|
|
||||||
if (link.href.includes('light.custom.css')) {
|
|
||||||
link.href = link.href.replace('light.custom.css', 'dark.custom.css');
|
|
||||||
} else if (link.href.includes('dark.custom.css')) {
|
|
||||||
link.href = link.href.replace('dark.custom.css', 'light.custom.css');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Refresh computed values after stylesheet loads
|
|
||||||
setTimeout(updateComputedValues, 200);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
40
updates.xml
40
updates.xml
@@ -1,7 +1,7 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
SPDX-License-Identifier: GPL-3.0-or-later
|
SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
VERSION: 01.00.00
|
VERSION: 01.00.07
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<updates>
|
<updates>
|
||||||
@@ -13,13 +13,13 @@
|
|||||||
<element>mokoonyx</element>
|
<element>mokoonyx</element>
|
||||||
<type>template</type>
|
<type>template</type>
|
||||||
<client>site</client>
|
<client>site</client>
|
||||||
<version>01.00.00</version>
|
<version>01.00.24</version>
|
||||||
<creationDate>2026-04-19</creationDate>
|
<creationDate>2026-04-23</creationDate>
|
||||||
<infourl title='MokoOnyx Dev'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/development</infourl>
|
<infourl title='MokoOnyx Dev'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/development</infourl>
|
||||||
<downloads>
|
<downloads>
|
||||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/development/mokoonyx-01.00.00-dev.zip</downloadurl>
|
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/development/mokoonyx-01.00.24-dev.zip</downloadurl>
|
||||||
</downloads>
|
</downloads>
|
||||||
<sha256></sha256>
|
<sha256>a2d215c19b3487eeeae62735a618e183c2e665ff75f9031bdb5df4b3c8f299a6</sha256>
|
||||||
<tags><tag>development</tag></tags>
|
<tags><tag>development</tag></tags>
|
||||||
<maintainer>Moko Consulting</maintainer>
|
<maintainer>Moko Consulting</maintainer>
|
||||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||||
@@ -34,11 +34,11 @@
|
|||||||
<element>mokoonyx</element>
|
<element>mokoonyx</element>
|
||||||
<type>template</type>
|
<type>template</type>
|
||||||
<client>site</client>
|
<client>site</client>
|
||||||
<version>01.00.00</version>
|
<version>01.00.07</version>
|
||||||
<creationDate>2026-04-19</creationDate>
|
<creationDate>2026-04-21</creationDate>
|
||||||
<infourl title='MokoOnyx Alpha'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/alpha</infourl>
|
<infourl title='MokoOnyx Alpha'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/alpha</infourl>
|
||||||
<downloads>
|
<downloads>
|
||||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/alpha/mokoonyx-01.00.00-alpha.zip</downloadurl>
|
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/alpha/mokoonyx-01.00.07-alpha.zip</downloadurl>
|
||||||
</downloads>
|
</downloads>
|
||||||
<sha256></sha256>
|
<sha256></sha256>
|
||||||
<tags><tag>alpha</tag></tags>
|
<tags><tag>alpha</tag></tags>
|
||||||
@@ -55,11 +55,11 @@
|
|||||||
<element>mokoonyx</element>
|
<element>mokoonyx</element>
|
||||||
<type>template</type>
|
<type>template</type>
|
||||||
<client>site</client>
|
<client>site</client>
|
||||||
<version>01.00.00</version>
|
<version>01.00.07</version>
|
||||||
<creationDate>2026-04-19</creationDate>
|
<creationDate>2026-04-21</creationDate>
|
||||||
<infourl title='MokoOnyx Beta'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/beta</infourl>
|
<infourl title='MokoOnyx Beta'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/beta</infourl>
|
||||||
<downloads>
|
<downloads>
|
||||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/beta/mokoonyx-01.00.00-beta.zip</downloadurl>
|
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/beta/mokoonyx-01.00.07-beta.zip</downloadurl>
|
||||||
</downloads>
|
</downloads>
|
||||||
<sha256></sha256>
|
<sha256></sha256>
|
||||||
<tags><tag>beta</tag></tags>
|
<tags><tag>beta</tag></tags>
|
||||||
@@ -76,12 +76,11 @@
|
|||||||
<element>mokoonyx</element>
|
<element>mokoonyx</element>
|
||||||
<type>template</type>
|
<type>template</type>
|
||||||
<client>site</client>
|
<client>site</client>
|
||||||
<version>01.00.00</version>
|
<version>01.00.07</version>
|
||||||
<creationDate>2026-04-19</creationDate>
|
<creationDate>2026-04-21</creationDate>
|
||||||
<infourl title='MokoOnyx RC'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/release-candidate</infourl>
|
<infourl title='MokoOnyx RC'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/release-candidate</infourl>
|
||||||
<downloads>
|
<downloads>
|
||||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/release-candidate/mokoonyx-01.00.00-rc.zip</downloadurl>
|
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/release-candidate/mokoonyx-01.00.07-rc.zip</downloadurl>
|
||||||
<downloadurl type='full' format='zip'>https://github.com/mokoconsulting-tech/MokoOnyx/releases/download/release-candidate/mokoonyx-01.00.00-rc.zip</downloadurl>
|
|
||||||
</downloads>
|
</downloads>
|
||||||
<sha256></sha256>
|
<sha256></sha256>
|
||||||
<tags><tag>rc</tag></tags>
|
<tags><tag>rc</tag></tags>
|
||||||
@@ -98,14 +97,13 @@
|
|||||||
<element>mokoonyx</element>
|
<element>mokoonyx</element>
|
||||||
<type>template</type>
|
<type>template</type>
|
||||||
<client>site</client>
|
<client>site</client>
|
||||||
<version>01.00.00</version>
|
<version>01.00.25</version>
|
||||||
<creationDate>2026-04-19</creationDate>
|
<creationDate>2026-04-23</creationDate>
|
||||||
<infourl title='MokoOnyx'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/v01</infourl>
|
<infourl title='MokoOnyx'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/stable</infourl>
|
||||||
<downloads>
|
<downloads>
|
||||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/v01/mokoonyx-01.00.00.zip</downloadurl>
|
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/stable/mokoonyx-01.00.25.zip</downloadurl>
|
||||||
<downloadurl type='full' format='zip'>https://github.com/mokoconsulting-tech/MokoOnyx/releases/download/v01/mokoonyx-01.00.00.zip</downloadurl>
|
|
||||||
</downloads>
|
</downloads>
|
||||||
<sha256></sha256>
|
<sha256>d111a027bdd5f1eae1fe973933fa4043be2740d62fea3527fb856275f8fb3be4</sha256>
|
||||||
<tags><tag>stable</tag></tags>
|
<tags><tag>stable</tag></tags>
|
||||||
<maintainer>Moko Consulting</maintainer>
|
<maintainer>Moko Consulting</maintainer>
|
||||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||||
|
|||||||
Reference in New Issue
Block a user