Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -191,16 +191,6 @@ 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
|
||||||
@@ -489,27 +479,39 @@ jobs:
|
|||||||
# Push to current branch
|
# Push to current branch
|
||||||
git push || true
|
git push || true
|
||||||
|
|
||||||
# Sync updates.xml to main via direct API
|
# Also update updates.xml on main via Gitea API (git push blocked by branch protection)
|
||||||
GA_TOKEN="${{ secrets.GA_TOKEN }}"
|
if [ "$CURRENT_BRANCH" != "main" ]; then
|
||||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
GA_TOKEN="${{ secrets.GA_TOKEN }}"
|
||||||
|
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||||
|
|
||||||
FILE_SHA=$(curl -sf -H "Authorization: token ${GA_TOKEN}" \
|
# Get current file SHA on main (required for update)
|
||||||
"${API}/contents/updates.xml?ref=main" | jq -r '.sha // empty')
|
FILE_SHA=$(curl -sf -H "Authorization: token ${GA_TOKEN}" \
|
||||||
|
"${API}/contents/updates.xml?ref=main" | jq -r '.sha // empty')
|
||||||
|
|
||||||
if [ -n "$FILE_SHA" ]; then
|
if [ -n "$FILE_SHA" ]; then
|
||||||
CONTENT=$(base64 -w0 updates.xml)
|
# Base64-encode the updates.xml content from working tree (has updated SHA)
|
||||||
curl -sf -X PUT -H "Authorization: token ${GA_TOKEN}" \
|
CONTENT=$(base64 -w0 updates.xml)
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"${API}/contents/updates.xml" \
|
RESPONSE=$(curl -s -w "\n%{http_code}" -X PUT -H "Authorization: token ${GA_TOKEN}" \
|
||||||
-d "$(jq -n \
|
-H "Content-Type: application/json" \
|
||||||
--arg content "$CONTENT" \
|
"${API}/contents/updates.xml" \
|
||||||
--arg sha "$FILE_SHA" \
|
-d "$(jq -n \
|
||||||
--arg msg "chore: sync updates.xml ${STABILITY} ${VERSION} [skip ci]" \
|
--arg content "$CONTENT" \
|
||||||
--arg branch "main" \
|
--arg sha "$FILE_SHA" \
|
||||||
'{content: $content, sha: $sha, message: $msg, branch: $branch}'
|
--arg msg "chore: update ${STABILITY} channel to ${VERSION} on main [skip ci]" \
|
||||||
)" > /dev/null 2>&1 \
|
--arg branch "main" \
|
||||||
&& echo "updates.xml synced to main" \
|
'{content: $content, sha: $sha, message: $msg, branch: $branch}'
|
||||||
|| echo "WARNING: failed to sync updates.xml to main"
|
)")
|
||||||
|
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
|
||||||
|
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ]; then
|
||||||
|
echo "updates.xml synced to main via API (HTTP ${HTTP_CODE})"
|
||||||
|
else
|
||||||
|
echo "WARNING: failed to sync updates.xml to main (HTTP ${HTTP_CODE})"
|
||||||
|
echo "$RESPONSE" | head -5
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "WARNING: could not get file SHA for updates.xml on main"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Summary
|
- name: Summary
|
||||||
|
|||||||
345
.github/workflows/auto-release.yml
vendored
345
.github/workflows/auto-release.yml
vendored
@@ -5,7 +5,7 @@
|
|||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: MokoStandards.Release
|
# INGROUP: MokoStandards.Release
|
||||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||||
# PATH: /templates/workflows/joomla/auto-release.yml.template
|
# PATH: /templates/workflows/joomla/auto-release.yml.template
|
||||||
# VERSION: 04.06.00
|
# VERSION: 04.06.00
|
||||||
# BRIEF: Joomla build & release — ZIP package, updates.xml, SHA-256 checksum
|
# BRIEF: Joomla build & release — ZIP package, updates.xml, SHA-256 checksum
|
||||||
@@ -22,15 +22,13 @@
|
|||||||
# | 4. Update [VERSION: XX.YY.ZZ] badges in markdown files |
|
# | 4. Update [VERSION: XX.YY.ZZ] badges in markdown files |
|
||||||
# | 5. Write updates.xml (Joomla update server XML) |
|
# | 5. Write updates.xml (Joomla update server XML) |
|
||||||
# | 6. Create git tag vXX.YY.ZZ |
|
# | 6. Create git tag vXX.YY.ZZ |
|
||||||
# | 7a. Patch: update existing Gitea Release for this minor |
|
# | 7a. Patch: update existing GitHub Release for this minor |
|
||||||
# | 8. Build ZIP, upload asset, write SHA-256 to updates.xml |
|
# | 8. Build ZIP, upload asset, write SHA-256 to updates.xml |
|
||||||
# | |
|
# | |
|
||||||
# | Every version change: archives main -> version/XX.YY branch |
|
# | Every version change: archives main -> version/XX.YY branch |
|
||||||
# | All patches release (including 00). Patch 00/01 = full pipeline. |
|
# | Patch 00 = development (no release). First release = patch 01. |
|
||||||
# | First release only (patch == 01): |
|
# | First release only (patch == 01): |
|
||||||
# | 7b. Create new Gitea Release |
|
# | 7b. Create new GitHub Release |
|
||||||
# | |
|
|
||||||
# | GitHub mirror: stable/rc releases only (continue-on-error) |
|
|
||||||
# | |
|
# | |
|
||||||
# +========================================================================+
|
# +========================================================================+
|
||||||
|
|
||||||
@@ -48,9 +46,6 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
|
||||||
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
|
||||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -66,21 +61,19 @@ jobs:
|
|||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GA_TOKEN }}
|
token: ${{ secrets.GA_TOKEN || github.token }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set authenticated push URL
|
||||||
|
run: git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
|
|
||||||
- name: Setup MokoStandards tools
|
- name: Setup MokoStandards tools
|
||||||
env:
|
env:
|
||||||
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
|
GA_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
|
||||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN }}"}}'
|
|
||||||
run: |
|
run: |
|
||||||
# Ensure PHP + Composer are available
|
git clone --depth 1 --branch version/04 --quiet \
|
||||||
if ! command -v composer &> /dev/null; then
|
"https://x-access-token:${GH_TOKEN}@git.mokoconsulting.tech/MokoConsulting/MokoStandards-API.git" \
|
||||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
|
||||||
fi
|
|
||||||
git clone --depth 1 --branch main --quiet \
|
|
||||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
|
|
||||||
/tmp/mokostandards-api
|
/tmp/mokostandards-api
|
||||||
cd /tmp/mokostandards-api
|
cd /tmp/mokostandards-api
|
||||||
composer install --no-dev --no-interaction --quiet
|
composer install --no-dev --no-interaction --quiet
|
||||||
@@ -106,15 +99,20 @@ jobs:
|
|||||||
echo "branch=version/${MAJOR}" >> "$GITHUB_OUTPUT"
|
echo "branch=version/${MAJOR}" >> "$GITHUB_OUTPUT"
|
||||||
echo "minor=$MINOR" >> "$GITHUB_OUTPUT"
|
echo "minor=$MINOR" >> "$GITHUB_OUTPUT"
|
||||||
echo "major=$MAJOR" >> "$GITHUB_OUTPUT"
|
echo "major=$MAJOR" >> "$GITHUB_OUTPUT"
|
||||||
echo "release_tag=stable" >> "$GITHUB_OUTPUT"
|
echo "release_tag=v${MAJOR}" >> "$GITHUB_OUTPUT"
|
||||||
echo "stability=stable" >> "$GITHUB_OUTPUT"
|
if [ "$PATCH" = "00" ]; then
|
||||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||||
if [ "$PATCH" = "00" ] || [ "$PATCH" = "01" ]; then
|
|
||||||
echo "is_minor=true" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Version: $VERSION (first release for this minor — full pipeline)"
|
|
||||||
else
|
|
||||||
echo "is_minor=false" >> "$GITHUB_OUTPUT"
|
echo "is_minor=false" >> "$GITHUB_OUTPUT"
|
||||||
echo "Version: $VERSION (patch — platform version + badges only)"
|
echo "Version: $VERSION (patch 00 = development — skipping release)"
|
||||||
|
else
|
||||||
|
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||||
|
if [ "$PATCH" = "01" ]; then
|
||||||
|
echo "is_minor=true" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Version: $VERSION (first release — full pipeline)"
|
||||||
|
else
|
||||||
|
echo "is_minor=false" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Version: $VERSION (patch — platform version + badges only)"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Check if already released
|
- name: Check if already released
|
||||||
@@ -133,8 +131,11 @@ jobs:
|
|||||||
echo "tag_exists=$TAG_EXISTS" >> "$GITHUB_OUTPUT"
|
echo "tag_exists=$TAG_EXISTS" >> "$GITHUB_OUTPUT"
|
||||||
echo "branch_exists=$BRANCH_EXISTS" >> "$GITHUB_OUTPUT"
|
echo "branch_exists=$BRANCH_EXISTS" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
# Tag and branch may persist across patch releases — never skip
|
if [ "$TAG_EXISTS" = "true" ] && [ "$BRANCH_EXISTS" = "true" ]; then
|
||||||
echo "already_released=false" >> "$GITHUB_OUTPUT"
|
echo "already_released=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "already_released=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
# -- SANITY CHECKS -------------------------------------------------------
|
# -- SANITY CHECKS -------------------------------------------------------
|
||||||
- name: "Sanity: Pre-release validation"
|
- name: "Sanity: Pre-release validation"
|
||||||
@@ -290,15 +291,9 @@ jobs:
|
|||||||
[ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}"
|
[ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}"
|
||||||
[ -z "$EXT_TYPE" ] && EXT_TYPE="component"
|
[ -z "$EXT_TYPE" ] && EXT_TYPE="component"
|
||||||
|
|
||||||
# Derive element if not in manifest:
|
# Templates/modules don't have <element> — derive from <name> (lowercased)
|
||||||
# 1. Try XML filename (e.g. mokowaas.xml → mokowaas)
|
|
||||||
# 2. Fall back to repo name (lowercased)
|
|
||||||
if [ -z "$EXT_ELEMENT" ]; then
|
if [ -z "$EXT_ELEMENT" ]; then
|
||||||
EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
|
EXT_ELEMENT=$(echo "$EXT_NAME" | tr '[:upper:]' '[:lower:]' | tr -d ' ')
|
||||||
# If filename is generic (templateDetails, manifest), use repo name
|
|
||||||
case "$EXT_ELEMENT" in
|
|
||||||
templatedetails|manifest|*.xml) EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;;
|
|
||||||
esac
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Build client tag: plugins and frontend modules need <client>site</client>
|
# Build client tag: plugins and frontend modules need <client>site</client>
|
||||||
@@ -317,7 +312,7 @@ jobs:
|
|||||||
|
|
||||||
# Build targetplatform (fallback to Joomla 5 if not in manifest)
|
# Build targetplatform (fallback to Joomla 5 if not in manifest)
|
||||||
if [ -z "$TARGET_PLATFORM" ]; then
|
if [ -z "$TARGET_PLATFORM" ]; then
|
||||||
TARGET_PLATFORM=$(printf '<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" %s>' "/")
|
TARGET_PLATFORM=$(printf '<targetplatform name="joomla" version="5.*" %s>' "/")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Build php_minimum tag
|
# Build php_minimum tag
|
||||||
@@ -326,12 +321,11 @@ jobs:
|
|||||||
PHP_TAG="<php_minimum>${PHP_MINIMUM}</php_minimum>"
|
PHP_TAG="<php_minimum>${PHP_MINIMUM}</php_minimum>"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/stable/${EXT_ELEMENT}-${VERSION}.zip"
|
DOWNLOAD_URL="https://git.mokoconsulting.tech/${{ github.repository }}/releases/download/v${VERSION}/${EXT_ELEMENT}-${VERSION}.zip"
|
||||||
INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/stable"
|
INFO_URL="https://git.mokoconsulting.tech/${{ github.repository }}/releases/tag/v${VERSION}"
|
||||||
|
|
||||||
# -- Build update entry for a given stability tag
|
# -- Build stable entry to temp file ─────────────────────────
|
||||||
build_entry() {
|
{
|
||||||
local TAG_NAME="$1"
|
|
||||||
printf '%s\n' ' <update>'
|
printf '%s\n' ' <update>'
|
||||||
printf '%s\n' " <name>${EXT_NAME}</name>"
|
printf '%s\n' " <name>${EXT_NAME}</name>"
|
||||||
printf '%s\n' " <description>${EXT_NAME} update</description>"
|
printf '%s\n' " <description>${EXT_NAME} update</description>"
|
||||||
@@ -340,7 +334,9 @@ jobs:
|
|||||||
printf '%s\n' " <version>${VERSION}</version>"
|
printf '%s\n' " <version>${VERSION}</version>"
|
||||||
[ -n "$CLIENT_TAG" ] && printf '%s\n' " ${CLIENT_TAG}"
|
[ -n "$CLIENT_TAG" ] && printf '%s\n' " ${CLIENT_TAG}"
|
||||||
[ -n "$FOLDER_TAG" ] && printf '%s\n' " ${FOLDER_TAG}"
|
[ -n "$FOLDER_TAG" ] && printf '%s\n' " ${FOLDER_TAG}"
|
||||||
printf '%s\n' " <tags><tag>${TAG_NAME}</tag></tags>"
|
printf '%s\n' ' <tags>'
|
||||||
|
printf '%s\n' ' <tag>stable</tag>'
|
||||||
|
printf '%s\n' ' </tags>'
|
||||||
printf '%s\n' " <infourl title=\"${EXT_NAME}\">${INFO_URL}</infourl>"
|
printf '%s\n' " <infourl title=\"${EXT_NAME}\">${INFO_URL}</infourl>"
|
||||||
printf '%s\n' ' <downloads>'
|
printf '%s\n' ' <downloads>'
|
||||||
printf '%s\n' " <downloadurl type=\"full\" format=\"zip\">${DOWNLOAD_URL}</downloadurl>"
|
printf '%s\n' " <downloadurl type=\"full\" format=\"zip\">${DOWNLOAD_URL}</downloadurl>"
|
||||||
@@ -350,27 +346,35 @@ jobs:
|
|||||||
printf '%s\n' ' <maintainer>Moko Consulting</maintainer>'
|
printf '%s\n' ' <maintainer>Moko Consulting</maintainer>'
|
||||||
printf '%s\n' ' <maintainerurl>https://mokoconsulting.tech</maintainerurl>'
|
printf '%s\n' ' <maintainerurl>https://mokoconsulting.tech</maintainerurl>'
|
||||||
printf '%s\n' ' </update>'
|
printf '%s\n' ' </update>'
|
||||||
}
|
} > /tmp/stable_entry.xml
|
||||||
|
|
||||||
|
# -- Write updates.xml preserving dev/rc entries ──────────────
|
||||||
|
# Extract existing entries for other stability levels
|
||||||
|
# Order reflects release workflow: development → alpha → beta → rc → stable
|
||||||
|
if [ -f "updates.xml" ]; then
|
||||||
|
printf 'import re, sys\n' > /tmp/extract.py
|
||||||
|
printf 'with open("updates.xml") as f: c = f.read()\n' >> /tmp/extract.py
|
||||||
|
printf 'tag = sys.argv[1]\n' >> /tmp/extract.py
|
||||||
|
printf 'm = re.search(r"( <update>.*?<tag>" + re.escape(tag) + r"</tag>.*?</update>)", c, re.DOTALL)\n' >> /tmp/extract.py
|
||||||
|
printf 'if m: print(m.group(1))\n' >> /tmp/extract.py
|
||||||
|
fi
|
||||||
|
DEV_ENTRY=$(python3 /tmp/extract.py development 2>/dev/null || true)
|
||||||
|
ALPHA_ENTRY=$(python3 /tmp/extract.py alpha 2>/dev/null || true)
|
||||||
|
BETA_ENTRY=$(python3 /tmp/extract.py beta 2>/dev/null || true)
|
||||||
|
RC_ENTRY=$(python3 /tmp/extract.py rc 2>/dev/null || true)
|
||||||
|
|
||||||
# -- Write updates.xml with cascading channels
|
|
||||||
# Stable release updates ALL channels (development, alpha, beta, rc, stable)
|
|
||||||
{
|
{
|
||||||
printf '%s\n' "<?xml version='1.0' encoding='UTF-8'?>"
|
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>'
|
||||||
printf '%s\n' "<!-- Copyright (C) $(date +%Y) Moko Consulting <hello@mokoconsulting.tech>"
|
|
||||||
printf '%s\n' " SPDX-License-Identifier: GPL-3.0-or-later"
|
|
||||||
printf '%s\n' " VERSION: ${VERSION}"
|
|
||||||
printf '%s\n' " -->"
|
|
||||||
printf '%s\n' ""
|
|
||||||
printf '%s\n' '<updates>'
|
printf '%s\n' '<updates>'
|
||||||
build_entry "development"
|
[ -n "$DEV_ENTRY" ] && echo "$DEV_ENTRY"
|
||||||
build_entry "alpha"
|
[ -n "$ALPHA_ENTRY" ] && echo "$ALPHA_ENTRY"
|
||||||
build_entry "beta"
|
[ -n "$BETA_ENTRY" ] && echo "$BETA_ENTRY"
|
||||||
build_entry "rc"
|
[ -n "$RC_ENTRY" ] && echo "$RC_ENTRY"
|
||||||
build_entry "stable"
|
cat /tmp/stable_entry.xml
|
||||||
printf '%s\n' '</updates>'
|
printf '%s\n' '</updates>'
|
||||||
} > updates.xml
|
} > updates.xml
|
||||||
|
|
||||||
echo "updates.xml: ${VERSION} (all channels updated to stable)" >> $GITHUB_STEP_SUMMARY
|
echo "updates.xml: ${VERSION} (stable + rc/dev preserved)" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# -- Commit all changes ---------------------------------------------------
|
# -- Commit all changes ---------------------------------------------------
|
||||||
- name: Commit release changes
|
- name: Commit release changes
|
||||||
@@ -385,12 +389,10 @@ jobs:
|
|||||||
VERSION="${{ steps.version.outputs.version }}"
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||||
git config --local user.name "gitea-actions[bot]"
|
git config --local user.name "gitea-actions[bot]"
|
||||||
# Set push URL with token for branch-protected repos
|
|
||||||
git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
|
||||||
git add -A
|
git add -A
|
||||||
git commit -m "chore(release): build ${VERSION} [skip ci]" \
|
git commit -m "chore(release): build ${VERSION} [skip ci]" \
|
||||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||||
git push -u origin HEAD
|
git push
|
||||||
|
|
||||||
# -- STEP 6: Create tag ---------------------------------------------------
|
# -- STEP 6: Create tag ---------------------------------------------------
|
||||||
- name: "Step 6: Create git tag"
|
- name: "Step 6: Create git tag"
|
||||||
@@ -410,75 +412,69 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "Tag: ${TAG}" >> $GITHUB_STEP_SUMMARY
|
echo "Tag: ${TAG}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# -- STEP 7: Create or update Gitea Release --------------------------------
|
# -- STEP 7: Create or update GitHub Release ------------------------------
|
||||||
- name: "Step 7: Gitea Release"
|
- name: "Step 7: GitHub Release"
|
||||||
if: >-
|
if: >-
|
||||||
steps.version.outputs.skip != 'true'
|
steps.version.outputs.skip != 'true' &&
|
||||||
|
steps.check.outputs.tag_exists != 'true'
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||||
run: |
|
run: |
|
||||||
VERSION="${{ steps.version.outputs.version }}"
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||||
BRANCH="${{ steps.version.outputs.branch }}"
|
BRANCH="${{ steps.version.outputs.branch }}"
|
||||||
MAJOR="${{ steps.version.outputs.major }}"
|
MAJOR="${{ steps.version.outputs.major }}"
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
|
||||||
|
|
||||||
NOTES=$(php /tmp/mokostandards-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null)
|
NOTES=$(php /tmp/mokostandards-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null)
|
||||||
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
|
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
|
||||||
|
echo "$NOTES" > /tmp/release_notes.md
|
||||||
|
|
||||||
# Check if the major release already exists
|
# Check if the major release already exists
|
||||||
EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
EXISTING=$(gh release view "$RELEASE_TAG" --json tagName -q .tagName 2>/dev/null || true)
|
||||||
"${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null || true)
|
|
||||||
EXISTING_ID=$(echo "$EXISTING" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null || true)
|
|
||||||
|
|
||||||
if [ -z "$EXISTING_ID" ]; then
|
if [ -z "$EXISTING" ]; then
|
||||||
# First release for this major
|
# First release for this major
|
||||||
curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
gh release create "$RELEASE_TAG" \
|
||||||
-H "Content-Type: application/json" \
|
--title "v${MAJOR} (latest: ${VERSION})" \
|
||||||
"${API_BASE}/releases" \
|
--notes-file /tmp/release_notes.md \
|
||||||
-d "$(python3 -c "import json; print(json.dumps({
|
--target "$BRANCH"
|
||||||
'tag_name': '${RELEASE_TAG}',
|
|
||||||
'name': 'v${MAJOR} (latest: ${VERSION})',
|
|
||||||
'body': '''${NOTES}''',
|
|
||||||
'target_commitish': '${BRANCH}'
|
|
||||||
}))")"
|
|
||||||
echo "Release created: ${RELEASE_TAG} (${VERSION})" >> $GITHUB_STEP_SUMMARY
|
echo "Release created: ${RELEASE_TAG} (${VERSION})" >> $GITHUB_STEP_SUMMARY
|
||||||
else
|
else
|
||||||
# Append version notes to existing major release
|
# Append version notes to existing major release
|
||||||
CURRENT_BODY=$(echo "$EXISTING" | python3 -c "import sys,json; print(json.load(sys.stdin).get('body',''))" 2>/dev/null || true)
|
CURRENT_NOTES=$(gh release view "$RELEASE_TAG" --json body -q .body 2>/dev/null || true)
|
||||||
UPDATED_BODY="${CURRENT_BODY}
|
{
|
||||||
|
echo "$CURRENT_NOTES"
|
||||||
|
echo ""
|
||||||
|
echo "---"
|
||||||
|
echo "### ${VERSION}"
|
||||||
|
echo ""
|
||||||
|
cat /tmp/release_notes.md
|
||||||
|
} > /tmp/updated_notes.md
|
||||||
|
|
||||||
---
|
gh release edit "$RELEASE_TAG" \
|
||||||
### ${VERSION}
|
--title "v${MAJOR} (latest: ${VERSION})" \
|
||||||
|
--notes-file /tmp/updated_notes.md
|
||||||
${NOTES}"
|
|
||||||
|
|
||||||
curl -sf -X PATCH -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"${API_BASE}/releases/${EXISTING_ID}" \
|
|
||||||
-d "$(python3 -c "import json,sys; print(json.dumps({
|
|
||||||
'name': 'v${MAJOR} (latest: ${VERSION})',
|
|
||||||
'body': sys.stdin.read()
|
|
||||||
}))" <<< "$UPDATED_BODY")"
|
|
||||||
echo "Release updated: ${RELEASE_TAG} -> ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
echo "Release updated: ${RELEASE_TAG} -> ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# -- STEP 8: Build Joomla install ZIP + SHA-256 checksum ------------------
|
# -- STEP 8: Build Joomla install ZIP + SHA-256 checksum ------------------
|
||||||
|
# Every patch builds an install-ready ZIP and uploads it to the minor release.
|
||||||
|
# Result: one Release per minor version with a ZIP for each patch.
|
||||||
- name: "Step 8: Build Joomla package and update checksum"
|
- name: "Step 8: Build Joomla package and update checksum"
|
||||||
if: >-
|
if: >-
|
||||||
steps.version.outputs.skip != 'true'
|
steps.version.outputs.skip != 'true'
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||||
run: |
|
run: |
|
||||||
VERSION="${{ steps.version.outputs.version }}"
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||||
REPO="${{ github.repository }}"
|
REPO="${{ github.repository }}"
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
|
||||||
|
|
||||||
# All ZIPs upload to the major release tag (vXX)
|
# All ZIPs upload to the major release tag (vXX)
|
||||||
RELEASE_JSON=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
gh release view "$RELEASE_TAG" --json tagName > /dev/null 2>&1 || {
|
||||||
"${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null || true)
|
|
||||||
RELEASE_ID=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
|
||||||
if [ -z "$RELEASE_ID" ]; then
|
|
||||||
echo "No release ${RELEASE_TAG} found — skipping ZIP upload"
|
echo "No release ${RELEASE_TAG} found — skipping ZIP upload"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
}
|
||||||
|
|
||||||
# Find extension element name from manifest
|
# Find extension element name from manifest
|
||||||
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1 || true)
|
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1 || true)
|
||||||
@@ -512,109 +508,27 @@ jobs:
|
|||||||
SHA256_ZIP=$(sha256sum "/tmp/${ZIP_NAME}" | cut -d' ' -f1)
|
SHA256_ZIP=$(sha256sum "/tmp/${ZIP_NAME}" | cut -d' ' -f1)
|
||||||
SHA256_TAR=$(sha256sum "/tmp/${TAR_NAME}" | cut -d' ' -f1)
|
SHA256_TAR=$(sha256sum "/tmp/${TAR_NAME}" | cut -d' ' -f1)
|
||||||
|
|
||||||
# -- Delete existing assets with same name before uploading ------
|
|
||||||
ASSETS=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
|
||||||
"${API_BASE}/releases/${RELEASE_ID}/assets" 2>/dev/null || echo "[]")
|
|
||||||
for ASSET_NAME in "$ZIP_NAME" "$TAR_NAME"; do
|
|
||||||
ASSET_ID=$(echo "$ASSETS" | python3 -c "
|
|
||||||
import sys,json
|
|
||||||
assets = json.load(sys.stdin)
|
|
||||||
for a in assets:
|
|
||||||
if a['name'] == '${ASSET_NAME}':
|
|
||||||
print(a['id']); break
|
|
||||||
" 2>/dev/null || true)
|
|
||||||
if [ -n "$ASSET_ID" ]; then
|
|
||||||
curl -sf -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
|
||||||
"${API_BASE}/releases/${RELEASE_ID}/assets/${ASSET_ID}" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# -- Upload both to release tag ----------------------------------
|
# -- Upload both to release tag ----------------------------------
|
||||||
curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
gh release upload "$RELEASE_TAG" "/tmp/${ZIP_NAME}" --clobber 2>/dev/null || true
|
||||||
-H "Content-Type: application/octet-stream" \
|
gh release upload "$RELEASE_TAG" "/tmp/${TAR_NAME}" --clobber 2>/dev/null || true
|
||||||
--data-binary @"/tmp/${ZIP_NAME}" \
|
|
||||||
"${API_BASE}/releases/${RELEASE_ID}/assets?name=${ZIP_NAME}" > /dev/null 2>&1 || true
|
|
||||||
|
|
||||||
curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
|
||||||
-H "Content-Type: application/octet-stream" \
|
|
||||||
--data-binary @"/tmp/${TAR_NAME}" \
|
|
||||||
"${API_BASE}/releases/${RELEASE_ID}/assets?name=${TAR_NAME}" > /dev/null 2>&1 || true
|
|
||||||
|
|
||||||
# -- Update updates.xml with both download formats ---------------
|
# -- Update updates.xml with both download formats ---------------
|
||||||
if [ -f "updates.xml" ]; then
|
if [ -f "updates.xml" ]; then
|
||||||
ZIP_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${ZIP_NAME}"
|
ZIP_URL="https://git.mokoconsulting.tech/${{ github.repository }}/releases/download/${RELEASE_TAG}/${ZIP_NAME}"
|
||||||
TAR_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${TAR_NAME}"
|
TAR_URL="https://git.mokoconsulting.tech/${{ github.repository }}/releases/download/${RELEASE_TAG}/${TAR_NAME}"
|
||||||
|
|
||||||
# Use Python to update only the stable entry's downloads + sha256
|
# Replace downloads block with both formats + SHA
|
||||||
export PY_ZIP_URL="$ZIP_URL" PY_TAR_URL="$TAR_URL" PY_SHA="$SHA256_ZIP"
|
sed -i "s|<downloads>.*</downloads>|<downloads>\n <downloadurl type=\"full\" format=\"zip\">${ZIP_URL}</downloadurl>\n <downloadurl type=\"full\" format=\"tar.gz\">${TAR_URL}</downloadurl>\n </downloads>|" updates.xml 2>/dev/null || true
|
||||||
python3 << 'PYEOF'
|
if grep -q '<sha256>' updates.xml; then
|
||||||
import re, os
|
sed -i "s|<sha256>.*</sha256>|<sha256>${SHA256_ZIP}</sha256>|" updates.xml
|
||||||
|
else
|
||||||
|
sed -i "s|</downloads>|</downloads>\n <sha256>${SHA256_ZIP}</sha256>|" updates.xml
|
||||||
|
fi
|
||||||
|
|
||||||
with open("updates.xml") as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
zip_url = os.environ["PY_ZIP_URL"]
|
|
||||||
tar_url = os.environ["PY_TAR_URL"]
|
|
||||||
sha = os.environ["PY_SHA"]
|
|
||||||
|
|
||||||
# Find the stable update block and replace its downloads + sha256
|
|
||||||
def replace_stable(m):
|
|
||||||
block = m.group(0)
|
|
||||||
# Replace downloads block
|
|
||||||
new_downloads = (
|
|
||||||
" <downloads>\n"
|
|
||||||
f" <downloadurl type=\"full\" format=\"zip\">{zip_url}</downloadurl>\n"
|
|
||||||
" </downloads>"
|
|
||||||
)
|
|
||||||
block = re.sub(r' <downloads>.*?</downloads>', new_downloads, block, flags=re.DOTALL)
|
|
||||||
# Add or replace sha256
|
|
||||||
if '<sha256>' in block:
|
|
||||||
block = re.sub(r' <sha256>.*?</sha256>', f' <sha256>{sha}</sha256>', block)
|
|
||||||
else:
|
|
||||||
block = block.replace('</downloads>', f'</downloads>\n <sha256>{sha}</sha256>')
|
|
||||||
return block
|
|
||||||
|
|
||||||
content = re.sub(
|
|
||||||
r' <update>.*?<tag>stable</tag>.*?</update>',
|
|
||||||
replace_stable,
|
|
||||||
content,
|
|
||||||
flags=re.DOTALL
|
|
||||||
)
|
|
||||||
|
|
||||||
with open("updates.xml", "w") as f:
|
|
||||||
f.write(content)
|
|
||||||
PYEOF
|
|
||||||
|
|
||||||
CURRENT_BRANCH="${{ github.ref_name }}"
|
|
||||||
git add updates.xml
|
git add updates.xml
|
||||||
git commit -m "chore(release): ZIP + tar.gz for ${VERSION} [skip ci]" \
|
git commit -m "chore(release): ZIP + tar.gz for ${VERSION} [skip ci]" \
|
||||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>" || true
|
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>" || true
|
||||||
git push || true
|
git push || true
|
||||||
|
|
||||||
# Sync updates.xml to main via direct API (always runs — may be on version/XX branch)
|
|
||||||
GA_TOKEN="${{ secrets.GA_TOKEN }}"
|
|
||||||
API="${GITEA_URL:-https://git.mokoconsulting.tech}/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 ${VERSION} [skip ci]" \
|
|
||||||
--arg branch "main" \
|
|
||||||
'{content: $content, sha: $sha, message: $msg, branch: $branch}'
|
|
||||||
)" > /dev/null 2>&1 \
|
|
||||||
&& echo "updates.xml synced to main via API" \
|
|
||||||
|| echo "WARNING: failed to sync updates.xml to main"
|
|
||||||
else
|
|
||||||
echo "WARNING: could not get updates.xml SHA from main"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "### Joomla Packages" >> $GITHUB_STEP_SUMMARY
|
echo "### Joomla Packages" >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -624,50 +538,7 @@ jobs:
|
|||||||
echo "| \`${ZIP_NAME}\` | ${ZIP_SIZE} | \`${SHA256_ZIP}\` |" >> $GITHUB_STEP_SUMMARY
|
echo "| \`${ZIP_NAME}\` | ${ZIP_SIZE} | \`${SHA256_ZIP}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| \`${TAR_NAME}\` | ${TAR_SIZE} | \`${SHA256_TAR}\` |" >> $GITHUB_STEP_SUMMARY
|
echo "| \`${TAR_NAME}\` | ${TAR_SIZE} | \`${SHA256_TAR}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Release | \`${RELEASE_TAG}\` | |" >> $GITHUB_STEP_SUMMARY
|
echo "| Release | \`${RELEASE_TAG}\` | |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Download | [${ZIP_NAME}](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${ZIP_NAME}) |" >> $GITHUB_STEP_SUMMARY
|
echo "| Download | [${PACKAGE_NAME}](https://git.mokoconsulting.tech/${{ github.repository }}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}) |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
|
|
||||||
- name: "Step 9: Mirror release to GitHub"
|
|
||||||
if: >-
|
|
||||||
steps.version.outputs.skip != 'true' &&
|
|
||||||
steps.version.outputs.stability == 'stable' &&
|
|
||||||
secrets.GH_TOKEN != ''
|
|
||||||
continue-on-error: true
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
|
||||||
run: |
|
|
||||||
VERSION="${{ steps.version.outputs.version }}"
|
|
||||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
|
||||||
MAJOR="${{ steps.version.outputs.major }}"
|
|
||||||
BRANCH="${{ steps.version.outputs.branch }}"
|
|
||||||
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
|
||||||
|
|
||||||
NOTES=$(php /tmp/mokostandards-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null || true)
|
|
||||||
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
|
|
||||||
echo "$NOTES" > /tmp/release_notes.md
|
|
||||||
|
|
||||||
EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/tags/$RELEASE_TAG" 2>/dev/null | jq -r ".tag_name // empty" || true)
|
|
||||||
|
|
||||||
if [ -z "$EXISTING" ]; then
|
|
||||||
gh release create "$RELEASE_TAG" \
|
|
||||||
--repo "$GH_REPO" \
|
|
||||||
--title "v${MAJOR} (latest: ${VERSION})" \
|
|
||||||
--notes-file /tmp/release_notes.md \
|
|
||||||
--target "$BRANCH" || true
|
|
||||||
else
|
|
||||||
gh release edit "$RELEASE_TAG" \
|
|
||||||
--repo "$GH_REPO" \
|
|
||||||
--title "v${MAJOR} (latest: ${VERSION})" || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Upload assets to GitHub mirror
|
|
||||||
for PKG in /tmp/${EXT_ELEMENT:-pkg}-${VERSION}.*; do
|
|
||||||
if [ -f "$PKG" ]; then
|
|
||||||
_RELID=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/tags/$RELEASE_TAG" 2>/dev/null | jq -r ".id // empty")
|
|
||||||
[ -n "$_RELID" ] && curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" -H "Content-Type: application/octet-stream" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/${_RELID}/assets?name=$(basename $PKG)" --data-binary "@$PKG" > /dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo "GitHub mirror updated: ${GH_REPO} ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
|
||||||
# -- Summary --------------------------------------------------------------
|
# -- Summary --------------------------------------------------------------
|
||||||
- name: Pipeline Summary
|
- name: Pipeline Summary
|
||||||
@@ -688,5 +559,5 @@ jobs:
|
|||||||
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
|
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
|
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Release | [View](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
|
echo "| Release | [View](https://github.com/${{ github.repository }}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
|
||||||
fi
|
fi
|
||||||
|
|||||||
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@@ -103,16 +103,6 @@ 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
|
||||||
|
|||||||
284
.github/workflows/update-server.yml
vendored
284
.github/workflows/update-server.yml
vendored
@@ -5,7 +5,7 @@
|
|||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: MokoStandards.Joomla
|
# INGROUP: MokoStandards.Joomla
|
||||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||||
# PATH: /templates/workflows/joomla/update-server.yml.template
|
# PATH: /templates/workflows/joomla/update-server.yml.template
|
||||||
# VERSION: 04.06.00
|
# VERSION: 04.06.00
|
||||||
# BRIEF: Update Joomla update server XML feed with stable/rc/dev entries
|
# BRIEF: Update Joomla update server XML feed with stable/rc/dev entries
|
||||||
@@ -13,27 +13,16 @@
|
|||||||
# Writes updates.xml with multiple <update> entries:
|
# Writes updates.xml with multiple <update> entries:
|
||||||
# - <tag>stable</tag> on push to main (from auto-release)
|
# - <tag>stable</tag> on push to main (from auto-release)
|
||||||
# - <tag>rc</tag> on push to rc/**
|
# - <tag>rc</tag> on push to rc/**
|
||||||
# - <tag>development</tag> on push to dev or dev/**
|
# - <tag>development</tag> on push to dev/**
|
||||||
#
|
#
|
||||||
# Joomla filters by user's "Minimum Stability" setting.
|
# Joomla filters by user's "Minimum Stability" setting.
|
||||||
|
|
||||||
name: Update Joomla Update Server XML Feed
|
name: Update Joomla Update Server XML Feed
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- 'dev'
|
|
||||||
- 'dev/**'
|
|
||||||
- 'alpha/**'
|
|
||||||
- 'beta/**'
|
|
||||||
- 'rc/**'
|
|
||||||
paths:
|
|
||||||
- 'src/**'
|
|
||||||
- 'htdocs/**'
|
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [closed]
|
types: [closed]
|
||||||
branches:
|
branches:
|
||||||
- 'dev'
|
|
||||||
- 'dev/**'
|
- 'dev/**'
|
||||||
- 'alpha/**'
|
- 'alpha/**'
|
||||||
- 'beta/**'
|
- 'beta/**'
|
||||||
@@ -57,9 +46,6 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
|
||||||
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
|
||||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -69,49 +55,45 @@ jobs:
|
|||||||
name: Update updates.xml
|
name: Update updates.xml
|
||||||
runs-on: release
|
runs-on: release
|
||||||
if: >-
|
if: >-
|
||||||
github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' || github.event_name == 'push'
|
github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GA_TOKEN }}
|
token: ${{ secrets.GA_TOKEN || github.token }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup MokoStandards tools
|
- name: Setup MokoStandards tools
|
||||||
env:
|
env:
|
||||||
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
|
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
|
||||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN }}"}}'
|
|
||||||
run: |
|
run: |
|
||||||
if ! command -v composer &> /dev/null; then
|
git clone --depth 1 --branch version/04 --quiet \
|
||||||
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
|
"https://x-access-token:${GH_TOKEN}@git.mokoconsulting.tech/MokoConsulting/MokoStandards.git" \
|
||||||
fi
|
/tmp/mokostandards 2>/dev/null || true
|
||||||
git clone --depth 1 --branch main --quiet \
|
if [ -d "/tmp/mokostandards" ] && [ -f "/tmp/mokostandards/composer.json" ]; then
|
||||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
|
cd /tmp/mokostandards && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
||||||
/tmp/mokostandards-api 2>/dev/null || true
|
|
||||||
if [ -d "/tmp/mokostandards-api" ] && [ -f "/tmp/mokostandards-api/composer.json" ]; then
|
|
||||||
cd /tmp/mokostandards-api && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Generate updates.xml entry
|
- name: Generate updates.xml entry
|
||||||
id: update
|
|
||||||
run: |
|
run: |
|
||||||
BRANCH="${{ github.ref_name }}"
|
BRANCH="${{ github.ref_name }}"
|
||||||
REPO="${{ github.repository }}"
|
REPO="${{ github.repository }}"
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
VERSION=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null || echo "0.0.0")
|
||||||
VERSION=$(php /tmp/mokostandards-api/cli/version_read.php --path . 2>/dev/null || echo "0.0.0")
|
|
||||||
|
|
||||||
# Auto-bump patch on all branches (dev, alpha, beta, rc)
|
# Auto-bump patch on alpha/beta/rc branches (not dev — dev bumps manually)
|
||||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
if [[ "$BRANCH" != dev/* ]]; then
|
||||||
git config --local user.name "gitea-actions[bot]"
|
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||||
BUMPED=$(php /tmp/mokostandards-api/cli/version_bump.php --path . 2>/dev/null || true)
|
git config --local user.name "gitea-actions[bot]"
|
||||||
if [ -n "$BUMPED" ]; then
|
BUMPED=$(php /tmp/mokostandards/api/cli/version_bump.php --path . 2>/dev/null || true)
|
||||||
VERSION=$(php /tmp/mokostandards-api/cli/version_read.php --path . 2>/dev/null || echo "$VERSION")
|
if [ -n "$BUMPED" ]; then
|
||||||
git add -A
|
VERSION=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null || echo "$VERSION")
|
||||||
git commit -m "chore(version): auto-bump patch ${VERSION} [skip ci]" \
|
git add -A
|
||||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>" 2>/dev/null || true
|
git commit -m "chore(version): auto-bump patch ${VERSION} [skip ci]" \
|
||||||
git push 2>/dev/null || true
|
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>" 2>/dev/null || true
|
||||||
|
git push 2>/dev/null || true
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Determine stability from branch or input
|
# Determine stability from branch or input
|
||||||
@@ -123,14 +105,12 @@ jobs:
|
|||||||
STABILITY="beta"
|
STABILITY="beta"
|
||||||
elif [[ "$BRANCH" == alpha/* ]]; then
|
elif [[ "$BRANCH" == alpha/* ]]; then
|
||||||
STABILITY="alpha"
|
STABILITY="alpha"
|
||||||
elif [[ "$BRANCH" == dev/* ]] || [[ "$BRANCH" == "dev" ]]; then
|
elif [[ "$BRANCH" == dev/* ]]; then
|
||||||
STABILITY="development"
|
STABILITY="development"
|
||||||
else
|
else
|
||||||
STABILITY="stable"
|
STABILITY="stable"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
# Parse manifest (portable — no grep -P)
|
# Parse manifest (portable — no grep -P)
|
||||||
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
||||||
if [ -z "$MANIFEST" ]; then
|
if [ -z "$MANIFEST" ]; then
|
||||||
@@ -152,18 +132,15 @@ jobs:
|
|||||||
[ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}"
|
[ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}"
|
||||||
[ -z "$EXT_TYPE" ] && EXT_TYPE="component"
|
[ -z "$EXT_TYPE" ] && EXT_TYPE="component"
|
||||||
|
|
||||||
# Derive element if not in manifest: try XML filename, then repo name
|
# Templates and modules don't have <element> — derive from <name>
|
||||||
if [ -z "$EXT_ELEMENT" ]; then
|
if [ -z "$EXT_ELEMENT" ]; then
|
||||||
EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
|
EXT_ELEMENT=$(echo "$EXT_NAME" | tr '[:upper:]' '[:lower:]' | tr -d ' ')
|
||||||
case "$EXT_ELEMENT" in
|
|
||||||
templatedetails|manifest|*.xml) EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;;
|
|
||||||
esac
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Use manifest version if README version is empty
|
# Use manifest version if README version is empty
|
||||||
[ "$VERSION" = "0.0.0" ] && [ -n "$EXT_VERSION" ] && VERSION="$EXT_VERSION"
|
[ "$VERSION" = "0.0.0" ] && [ -n "$EXT_VERSION" ] && VERSION="$EXT_VERSION"
|
||||||
|
|
||||||
[ -z "$TARGET_PLATFORM" ] && TARGET_PLATFORM=$(printf '<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" %s>' "/")
|
[ -z "$TARGET_PLATFORM" ] && TARGET_PLATFORM=$(printf '<targetplatform name="joomla" version="5.*" %s>' "/")
|
||||||
|
|
||||||
CLIENT_TAG=""
|
CLIENT_TAG=""
|
||||||
[ -n "$EXT_CLIENT" ] && CLIENT_TAG="<client>${EXT_CLIENT}</client>"
|
[ -n "$EXT_CLIENT" ] && CLIENT_TAG="<client>${EXT_CLIENT}</client>"
|
||||||
@@ -196,10 +173,10 @@ jobs:
|
|||||||
esac
|
esac
|
||||||
|
|
||||||
PACKAGE_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.zip"
|
PACKAGE_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.zip"
|
||||||
DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}"
|
DOWNLOAD_URL="https://git.mokoconsulting.tech/${{ github.repository }}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}"
|
||||||
INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}"
|
INFO_URL="https://github.com/${REPO}"
|
||||||
|
|
||||||
# -- Build install packages (ZIP + tar.gz) --------------------
|
# ── Build install packages (ZIP + tar.gz) ───────────────────
|
||||||
SOURCE_DIR="src"
|
SOURCE_DIR="src"
|
||||||
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
||||||
if [ -d "$SOURCE_DIR" ]; then
|
if [ -d "$SOURCE_DIR" ]; then
|
||||||
@@ -215,62 +192,20 @@ jobs:
|
|||||||
|
|
||||||
SHA256=$(sha256sum "/tmp/${PACKAGE_NAME}" | cut -d' ' -f1)
|
SHA256=$(sha256sum "/tmp/${PACKAGE_NAME}" | cut -d' ' -f1)
|
||||||
|
|
||||||
# Ensure release exists on Gitea
|
# Ensure release exists
|
||||||
RELEASE_JSON=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
gh release view "$RELEASE_TAG" --json tagName > /dev/null 2>&1 || \
|
||||||
"${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null || true)
|
gh release create "$RELEASE_TAG" --title "${RELEASE_TAG} (${DISPLAY_VERSION})" --notes "${STABILITY} release" --prerelease --target main 2>/dev/null || true
|
||||||
RELEASE_ID=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
|
||||||
|
|
||||||
if [ -z "$RELEASE_ID" ]; then
|
# Upload both formats
|
||||||
# Create release
|
gh release upload "$RELEASE_TAG" "/tmp/${PACKAGE_NAME}" --clobber 2>/dev/null || true
|
||||||
RELEASE_JSON=$(curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
gh release upload "$RELEASE_TAG" "/tmp/${TAR_NAME}" --clobber 2>/dev/null || true
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"${API_BASE}/releases" \
|
|
||||||
-d "$(python3 -c "import json; print(json.dumps({
|
|
||||||
'tag_name': '${RELEASE_TAG}',
|
|
||||||
'name': '${RELEASE_TAG} (${DISPLAY_VERSION})',
|
|
||||||
'body': '${STABILITY} release',
|
|
||||||
'prerelease': True,
|
|
||||||
'target_commitish': 'main'
|
|
||||||
}))")" 2>/dev/null || true)
|
|
||||||
RELEASE_ID=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$RELEASE_ID" ]; then
|
|
||||||
# Delete existing assets with same name before uploading
|
|
||||||
ASSETS=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
|
||||||
"${API_BASE}/releases/${RELEASE_ID}/assets" 2>/dev/null || echo "[]")
|
|
||||||
for ASSET_FILE in "$PACKAGE_NAME" "$TAR_NAME"; do
|
|
||||||
ASSET_ID=$(echo "$ASSETS" | python3 -c "
|
|
||||||
import sys,json
|
|
||||||
assets = json.load(sys.stdin)
|
|
||||||
for a in assets:
|
|
||||||
if a['name'] == '${ASSET_FILE}':
|
|
||||||
print(a['id']); break
|
|
||||||
" 2>/dev/null || true)
|
|
||||||
if [ -n "$ASSET_ID" ]; then
|
|
||||||
curl -sf -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
|
||||||
"${API_BASE}/releases/${RELEASE_ID}/assets/${ASSET_ID}" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Upload both formats
|
|
||||||
curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
|
||||||
-H "Content-Type: application/octet-stream" \
|
|
||||||
--data-binary @"/tmp/${PACKAGE_NAME}" \
|
|
||||||
"${API_BASE}/releases/${RELEASE_ID}/assets?name=${PACKAGE_NAME}" > /dev/null 2>&1 || true
|
|
||||||
|
|
||||||
curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
|
||||||
-H "Content-Type: application/octet-stream" \
|
|
||||||
--data-binary @"/tmp/${TAR_NAME}" \
|
|
||||||
"${API_BASE}/releases/${RELEASE_ID}/assets?name=${TAR_NAME}" > /dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Packages: ${PACKAGE_NAME} + ${TAR_NAME} (SHA: ${SHA256})" >> $GITHUB_STEP_SUMMARY
|
echo "Packages: ${PACKAGE_NAME} + ${TAR_NAME} (SHA: ${SHA256})" >> $GITHUB_STEP_SUMMARY
|
||||||
else
|
else
|
||||||
SHA256=""
|
SHA256=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# -- Build the new entry -----------------------------------------
|
# ── Build the new entry ───────────────────────────────────────
|
||||||
NEW_ENTRY=""
|
NEW_ENTRY=""
|
||||||
NEW_ENTRY="${NEW_ENTRY} <update>\n"
|
NEW_ENTRY="${NEW_ENTRY} <update>\n"
|
||||||
NEW_ENTRY="${NEW_ENTRY} <name>${EXT_NAME}</name>\n"
|
NEW_ENTRY="${NEW_ENTRY} <name>${EXT_NAME}</name>\n"
|
||||||
@@ -285,76 +220,40 @@ jobs:
|
|||||||
NEW_ENTRY="${NEW_ENTRY} </tags>\n"
|
NEW_ENTRY="${NEW_ENTRY} </tags>\n"
|
||||||
NEW_ENTRY="${NEW_ENTRY} <infourl title=\"${EXT_NAME}\">${INFO_URL}</infourl>\n"
|
NEW_ENTRY="${NEW_ENTRY} <infourl title=\"${EXT_NAME}\">${INFO_URL}</infourl>\n"
|
||||||
NEW_ENTRY="${NEW_ENTRY} <downloads>\n"
|
NEW_ENTRY="${NEW_ENTRY} <downloads>\n"
|
||||||
|
TAR_URL="https://git.mokoconsulting.tech/${{ github.repository }}/releases/download/${RELEASE_TAG}/${EXT_ELEMENT}-${DISPLAY_VERSION}.tar.gz"
|
||||||
NEW_ENTRY="${NEW_ENTRY} <downloadurl type=\"full\" format=\"zip\">${DOWNLOAD_URL}</downloadurl>\n"
|
NEW_ENTRY="${NEW_ENTRY} <downloadurl type=\"full\" format=\"zip\">${DOWNLOAD_URL}</downloadurl>\n"
|
||||||
|
NEW_ENTRY="${NEW_ENTRY} <downloadurl type=\"full\" format=\"tar.gz\">${TAR_URL}</downloadurl>\n"
|
||||||
NEW_ENTRY="${NEW_ENTRY} </downloads>\n"
|
NEW_ENTRY="${NEW_ENTRY} </downloads>\n"
|
||||||
[ -n "$SHA256" ] && NEW_ENTRY="${NEW_ENTRY} <sha256>${SHA256}</sha256>\n"
|
[ -n "$SHA256" ] && NEW_ENTRY="${NEW_ENTRY} <sha256>sha256:${SHA256}</sha256>\n"
|
||||||
NEW_ENTRY="${NEW_ENTRY} ${TARGET_PLATFORM}\n"
|
NEW_ENTRY="${NEW_ENTRY} ${TARGET_PLATFORM}\n"
|
||||||
[ -n "$PHP_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${PHP_TAG}\n"
|
[ -n "$PHP_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${PHP_TAG}\n"
|
||||||
NEW_ENTRY="${NEW_ENTRY} <maintainer>Moko Consulting</maintainer>\n"
|
NEW_ENTRY="${NEW_ENTRY} <maintainer>Moko Consulting</maintainer>\n"
|
||||||
NEW_ENTRY="${NEW_ENTRY} <maintainerurl>https://mokoconsulting.tech</maintainerurl>\n"
|
NEW_ENTRY="${NEW_ENTRY} <maintainerurl>https://mokoconsulting.tech</maintainerurl>\n"
|
||||||
NEW_ENTRY="${NEW_ENTRY} </update>"
|
NEW_ENTRY="${NEW_ENTRY} </update>"
|
||||||
|
|
||||||
# -- Write new entry to temp file --------------------------------
|
# ── Write new entry to temp file ───────────────────────────────
|
||||||
printf '%b' "$NEW_ENTRY" > /tmp/new_entry.xml
|
printf '%b' "$NEW_ENTRY" > /tmp/new_entry.xml
|
||||||
|
|
||||||
# -- Merge into updates.xml (only update this stability channel) -
|
# ── Merge into updates.xml ─────────────────────────────────────
|
||||||
# Cascading update: each stability level updates itself and all lower levels
|
|
||||||
# stable → all | rc → rc,beta,alpha,dev | beta → beta,alpha,dev | alpha → alpha,dev | dev → dev
|
|
||||||
CASCADE_MAP="stable:development,alpha,beta,rc,stable rc:development,alpha,beta,rc beta:development,alpha,beta alpha:development,alpha development:development"
|
|
||||||
TARGETS=""
|
|
||||||
for entry in $CASCADE_MAP; do
|
|
||||||
key="${entry%%:*}"
|
|
||||||
vals="${entry#*:}"
|
|
||||||
if [ "$key" = "${STABILITY}" ]; then
|
|
||||||
TARGETS="$vals"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
[ -z "$TARGETS" ] && TARGETS="${STABILITY}"
|
|
||||||
|
|
||||||
if [ ! -f "updates.xml" ]; then
|
if [ ! -f "updates.xml" ]; then
|
||||||
printf '%s\n' "<?xml version='1.0' encoding='UTF-8'?>" > updates.xml
|
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>' > updates.xml
|
||||||
printf '%s\n' "<!-- Copyright (C) $(date +%Y) Moko Consulting <hello@mokoconsulting.tech>" >> updates.xml
|
|
||||||
printf '%s\n' " SPDX-License-Identifier: GPL-3.0-or-later" >> updates.xml
|
|
||||||
printf '%s\n' " VERSION: ${VERSION}" >> updates.xml
|
|
||||||
printf '%s\n' " -->" >> updates.xml
|
|
||||||
printf '%s\n' "" >> updates.xml
|
|
||||||
printf '%s\n' '<updates>' >> updates.xml
|
printf '%s\n' '<updates>' >> updates.xml
|
||||||
cat /tmp/new_entry.xml >> updates.xml
|
cat /tmp/new_entry.xml >> updates.xml
|
||||||
printf '\n%s\n' '</updates>' >> updates.xml
|
printf '\n%s\n' '</updates>' >> updates.xml
|
||||||
else
|
else
|
||||||
# Replace each cascading channel with the new entry (different tag)
|
# Remove existing entry for this stability, insert new one
|
||||||
export PY_TARGETS="$TARGETS"
|
printf 'import re\nstability = "%s"\n' "${STABILITY}" > /tmp/merge_xml.py
|
||||||
python3 << PYEOF
|
printf 'with open("updates.xml") as f: content = f.read()\n' >> /tmp/merge_xml.py
|
||||||
import re, os
|
printf 'with open("/tmp/new_entry.xml") as f: new_entry = f.read()\n' >> /tmp/merge_xml.py
|
||||||
targets = os.environ["PY_TARGETS"].split(",")
|
printf 'pattern = r" <update>.*?<tag>" + re.escape(stability) + r"</tag>.*?</update>\\n?"\n' >> /tmp/merge_xml.py
|
||||||
stability = "${STABILITY}"
|
printf 'content = re.sub(pattern, "", content, flags=re.DOTALL)\n' >> /tmp/merge_xml.py
|
||||||
with open("updates.xml") as f:
|
printf 'content = content.replace("</updates>", new_entry + "\\n</updates>")\n' >> /tmp/merge_xml.py
|
||||||
content = f.read()
|
printf 'content = re.sub(r"\\n{3,}", "\\n\\n", content)\n' >> /tmp/merge_xml.py
|
||||||
with open("/tmp/new_entry.xml") as f:
|
printf 'with open("updates.xml", "w") as f: f.write(content)\n' >> /tmp/merge_xml.py
|
||||||
new_entry_template = f.read()
|
python3 /tmp/merge_xml.py 2>/dev/null || {
|
||||||
for tag in targets:
|
|
||||||
tag = tag.strip()
|
|
||||||
# Build entry with this tag
|
|
||||||
new_entry = re.sub(r"<tag>[^<]*</tag>", f"<tag>{tag}</tag>", new_entry_template)
|
|
||||||
# Remove existing entry for this tag
|
|
||||||
pattern = r" <update>.*?<tag>" + re.escape(tag) + r"</tag>.*?</update>\n?"
|
|
||||||
content = re.sub(pattern, "", content, flags=re.DOTALL)
|
|
||||||
# Insert before </updates>
|
|
||||||
content = content.replace("</updates>", new_entry + "\n</updates>")
|
|
||||||
content = re.sub(r"\n{3,}", "\n\n", content)
|
|
||||||
with open("updates.xml", "w") as f:
|
|
||||||
f.write(content)
|
|
||||||
PYEOF
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
# Fallback: rebuild keeping other stability entries
|
# Fallback: rebuild keeping other stability entries
|
||||||
{
|
{
|
||||||
printf '%s\n' "<?xml version='1.0' encoding='UTF-8'?>"
|
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>'
|
||||||
printf '%s\n' "<!-- Copyright (C) $(date +%Y) Moko Consulting <hello@mokoconsulting.tech>"
|
|
||||||
printf '%s\n' " SPDX-License-Identifier: GPL-3.0-or-later"
|
|
||||||
printf '%s\n' " VERSION: ${VERSION}"
|
|
||||||
printf '%s\n' " -->"
|
|
||||||
printf '%s\n' ""
|
|
||||||
printf '%s\n' '<updates>'
|
printf '%s\n' '<updates>'
|
||||||
for TAG in stable rc development; do
|
for TAG in stable rc development; do
|
||||||
[ "$TAG" = "${STABILITY}" ] && continue
|
[ "$TAG" = "${STABILITY}" ] && continue
|
||||||
@@ -366,7 +265,7 @@ jobs:
|
|||||||
printf '\n%s\n' '</updates>'
|
printf '\n%s\n' '</updates>'
|
||||||
} > /tmp/updates_new.xml
|
} > /tmp/updates_new.xml
|
||||||
mv /tmp/updates_new.xml updates.xml
|
mv /tmp/updates_new.xml updates.xml
|
||||||
fi
|
}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Commit
|
# Commit
|
||||||
@@ -379,55 +278,8 @@ jobs:
|
|||||||
git push
|
git push
|
||||||
}
|
}
|
||||||
|
|
||||||
# -- Sync updates.xml to main (for non-main branches) ----------------------
|
|
||||||
- name: Sync updates.xml to main
|
|
||||||
if: github.ref_name != 'main'
|
|
||||||
run: |
|
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
|
||||||
GA_TOKEN="${{ secrets.GA_TOKEN }}"
|
|
||||||
|
|
||||||
FILE_SHA=$(curl -sf -H "Authorization: token ${GA_TOKEN}" \
|
|
||||||
"${API_BASE}/contents/updates.xml?ref=main" | python3 -c "import sys,json; print(json.load(sys.stdin).get('sha',''))" 2>/dev/null || true)
|
|
||||||
|
|
||||||
if [ -n "$FILE_SHA" ] && [ -f "updates.xml" ]; then
|
|
||||||
CONTENT=$(base64 -w0 updates.xml)
|
|
||||||
curl -sf -X PUT -H "Authorization: token ${GA_TOKEN}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"${API_BASE}/contents/updates.xml" \
|
|
||||||
-d "$(python3 -c "import json; print(json.dumps({
|
|
||||||
'content': '${CONTENT}',
|
|
||||||
'sha': '${FILE_SHA}',
|
|
||||||
'message': 'chore: sync updates.xml from ${STABILITY} [skip ci]',
|
|
||||||
'branch': 'main'
|
|
||||||
}))")" > /dev/null 2>&1 \
|
|
||||||
&& echo "updates.xml synced to main (${STABILITY})" >> $GITHUB_STEP_SUMMARY \
|
|
||||||
|| echo "WARNING: failed to sync updates.xml to main" >> $GITHUB_STEP_SUMMARY
|
|
||||||
else
|
|
||||||
echo "WARNING: could not get updates.xml SHA from main" >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
|
||||||
|
|
||||||
# -- Mirror to GitHub (stable and rc only) --------------------------------
|
|
||||||
- name: Mirror release to GitHub
|
|
||||||
if: >-
|
|
||||||
(steps.update.outputs.stability == 'stable' || steps.update.outputs.stability == 'rc') &&
|
|
||||||
secrets.GH_TOKEN != ''
|
|
||||||
continue-on-error: true
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
|
||||||
run: |
|
|
||||||
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
|
||||||
STABILITY="${{ steps.update.outputs.stability }}"
|
|
||||||
echo "GitHub mirror sync for ${STABILITY} — ${GH_REPO}" >> $GITHUB_STEP_SUMMARY
|
|
||||||
# Mirror packages if they exist
|
|
||||||
for PKG in /tmp/*.zip /tmp/*.tar.gz; do
|
|
||||||
if [ -f "$PKG" ]; then
|
|
||||||
_RELID=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/tags/${RELEASE_TAG}" 2>/dev/null | jq -r ".id // empty")
|
|
||||||
[ -n "$_RELID" ] && curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" -H "Content-Type: application/octet-stream" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/${_RELID}/assets?name=$(basename $PKG)" --data-binary "@$PKG" > /dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: SFTP deploy to dev server
|
- name: SFTP deploy to dev server
|
||||||
if: contains(github.ref, 'dev/') || github.ref == 'refs/heads/dev'
|
if: contains(github.ref, 'dev/')
|
||||||
env:
|
env:
|
||||||
DEV_HOST: ${{ vars.DEV_FTP_HOST }}
|
DEV_HOST: ${{ vars.DEV_FTP_HOST }}
|
||||||
DEV_PATH: ${{ vars.DEV_FTP_PATH }}
|
DEV_PATH: ${{ vars.DEV_FTP_PATH }}
|
||||||
@@ -436,15 +288,15 @@ jobs:
|
|||||||
DEV_PORT: ${{ vars.DEV_FTP_PORT }}
|
DEV_PORT: ${{ vars.DEV_FTP_PORT }}
|
||||||
DEV_KEY: ${{ secrets.DEV_FTP_KEY }}
|
DEV_KEY: ${{ secrets.DEV_FTP_KEY }}
|
||||||
DEV_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
|
DEV_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
|
||||||
|
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||||
run: |
|
run: |
|
||||||
# -- Permission check: admin or maintain role required --------
|
# ── Permission check: admin or maintain role required ──────
|
||||||
ACTOR="${{ github.actor }}"
|
ACTOR="${{ github.actor }}"
|
||||||
REPO="${{ github.repository }}"
|
REPO="${{ github.repository }}"
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/collaborators/${ACTOR}/permission" 2>/dev/null \
|
||||||
|
2>/dev/null | jq -r '.permission' || \
|
||||||
PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/collaborators/${ACTOR}" 2>/dev/null \
|
||||||
"${API_BASE}/collaborators/${ACTOR}/permission" 2>/dev/null | \
|
2>/dev/null | jq -r '.role' || echo "read")
|
||||||
python3 -c "import sys,json; print(json.load(sys.stdin).get('permission','read'))" 2>/dev/null || echo "read")
|
|
||||||
case "$PERMISSION" in
|
case "$PERMISSION" in
|
||||||
admin|maintain|write) ;;
|
admin|maintain|write) ;;
|
||||||
*)
|
*)
|
||||||
@@ -472,11 +324,11 @@ jobs:
|
|||||||
printf ',"password":"%s"}' "$DEV_PASS" >> /tmp/sftp-config.json
|
printf ',"password":"%s"}' "$DEV_PASS" >> /tmp/sftp-config.json
|
||||||
fi
|
fi
|
||||||
|
|
||||||
PLATFORM=$(php /tmp/mokostandards-api/cli/platform_detect.php --path . 2>/dev/null || true)
|
PLATFORM=$(php /tmp/mokostandards/api/cli/platform_detect.php --path . 2>/dev/null || true)
|
||||||
if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/mokostandards-api/deploy/deploy-joomla.php" ]; then
|
if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/mokostandards/api/deploy/deploy-joomla.php" ]; then
|
||||||
php /tmp/mokostandards-api/deploy/deploy-joomla.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
php /tmp/mokostandards/api/deploy/deploy-joomla.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
||||||
elif [ -f "/tmp/mokostandards-api/deploy/deploy-sftp.php" ]; then
|
elif [ -f "/tmp/mokostandards/api/deploy/deploy-sftp.php" ]; then
|
||||||
php /tmp/mokostandards-api/deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
php /tmp/mokostandards/api/deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
||||||
fi
|
fi
|
||||||
rm -f /tmp/deploy_key /tmp/sftp-config.json
|
rm -f /tmp/deploy_key /tmp/sftp-config.json
|
||||||
echo "SFTP deploy to dev complete" >> $GITHUB_STEP_SUMMARY
|
echo "SFTP deploy to dev complete" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|||||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -119,14 +119,6 @@ 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,7 +9,7 @@
|
|||||||
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.19
|
VERSION: 01.00.17
|
||||||
BRIEF: Documentation for MokoOnyx template
|
BRIEF: Documentation for MokoOnyx template
|
||||||
-->
|
-->
|
||||||
|
|
||||||
@@ -24,31 +24,489 @@
|
|||||||
[](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
|
||||||
- **Template Overrides**: Includes overrides for all core Joomla modules, Community Builder, and DPCalendar
|
- **Template Overrides**: Includes overrides for all core Joomla modules, Community Builder, and DPCalendar with consistent title rendering and Bootstrap 5 styling
|
||||||
|
- **Upgrade-Friendly**: Minimal core 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
|
||||||
- **Google Tag Manager / GA4**: Optional analytics integrations
|
- **Color Palettes**: Standard, Alternative, and Custom color schemes
|
||||||
|
- **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
|
||||||
## Requirements
|
- Automatic heading extraction and navigation
|
||||||
|
- Responsive sidebar positioning
|
||||||
- **Joomla**: 5.x or 6.x
|
|
||||||
- **PHP**: 8.1 or higher
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Download the latest `mokoonyx-{version}.zip` from [Releases](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases) and install via Joomla's Extension Manager.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
This project is licensed under the **GNU General Public License v3.0** - see the [LICENSE](./LICENSE) file for details.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Made with love by [Moko Consulting](https://mokoconsulting.tech)**
|
## 📋 Requirements
|
||||||
|
|
||||||
|
- **Joomla**: 5.x or 6.x
|
||||||
|
- **PHP**: 8.1 or higher
|
||||||
|
- **Database**: MySQL 5.7+ / MariaDB 10.2+ / PostgreSQL 11+
|
||||||
|
- **Browser Support**: Modern browsers (Chrome, Firefox, Safari, Edge)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Installation
|
||||||
|
|
||||||
|
**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
|
||||||
|
|
||||||
|
See [Gitea Releases](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases) for all versions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💬 Support
|
||||||
|
|
||||||
|
### Getting Help
|
||||||
|
|
||||||
|
- **Documentation**: Check this README and [docs folder](./docs/)
|
||||||
|
- **Issues**: Report bugs via [Gitea Issues](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/issues)
|
||||||
|
- **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 MokoOnyx 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 5.x and 6.x
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
- **Repository**: [Gitea](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx)
|
||||||
|
- **Issue Tracker**: [Gitea Issues](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/issues)
|
||||||
|
- **GitHub Mirror**: [GitHub](https://github.com/mokoconsulting-tech/MokoOnyx)
|
||||||
|
- **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-04-22 | 01.00.15 | Updated README: dynamic version badge, corrected requirements, fixed links | Claude Code |
|
||||||
|
| 2026-04-19 | 01.00.00 | Initial MokoOnyx release — renamed from MokoCassiopeia with auto-migration | Moko Consulting |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Made with ❤️ by [Moko Consulting](https://mokoconsulting.tech)**
|
||||||
|
|||||||
@@ -10,9 +10,8 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"ext-zip": "*",
|
"php": ">=8.1",
|
||||||
"mokoconsulting-tech/enterprise": "dev-version/04",
|
"ext-zip": "*"
|
||||||
"php": ">=8.1"
|
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"mokoconsulting-tech/enterprise": "^4.0"
|
"mokoconsulting-tech/enterprise": "^4.0"
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
#!/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();
|
|
||||||
@@ -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-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_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_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."
|
||||||
|
|||||||
@@ -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-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_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_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."
|
||||||
|
|||||||
1
src/media/css/editor.min.css
vendored
Normal file
1
src/media/css/editor.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
@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)}
|
||||||
@@ -13927,11 +13927,6 @@ meter {
|
|||||||
|
|
||||||
/* ── HERO CARD BASE ── */
|
/* ── HERO CARD BASE ── */
|
||||||
.hero {
|
.hero {
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
min-height: var(--banner-min-height, 60vh);
|
|
||||||
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));
|
||||||
@@ -13942,12 +13937,6 @@ meter {
|
|||||||
border-radius: var(--hero-card-border-radius, .5rem);
|
border-radius: var(--hero-card-border-radius, .5rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero .card {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── PRIMARY VARIANT (uses default card vars) ── */
|
/* ── PRIMARY VARIANT (uses default card vars) ── */
|
||||||
.hero#primary {
|
.hero#primary {
|
||||||
background-color: var(--hero-card-bg, #0d1e3a);
|
background-color: var(--hero-card-bg, #0d1e3a);
|
||||||
@@ -14204,11 +14193,10 @@ fieldset>* {
|
|||||||
|
|
||||||
.container-header {
|
.container-header {
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
background-color: var(--header-background-color, #adadad);
|
background: var(--header-background-image, url('../../../../../../media/templates/site/mokoonyx/images/bg.svg'));
|
||||||
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);
|
||||||
background-repeat: var(--header-background-repeat, repeat);
|
|
||||||
box-shadow: 0 5px 5px hsla(0, 0%, 0%, 0.03) inset;
|
box-shadow: 0 5px 5px hsla(0, 0%, 0%, 0.03) inset;
|
||||||
|
background-repeat: var(--header-background-repeat, repeat);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sticky header: override z-index to stay above all content */
|
/* Sticky header: override z-index to stay above all content */
|
||||||
|
|||||||
1
src/media/css/template.min.css
vendored
Normal file
1
src/media/css/template.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -210,7 +210,6 @@ 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
Normal file
1
src/media/css/theme/dark.standard.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -209,7 +209,6 @@ 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
Normal file
1
src/media/css/theme/light.standard.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/media/js/gtm.min.js
vendored
Normal file
1
src/media/js/gtm.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
(()=>{"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
Normal file
1
src/media/js/template.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
!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);
|
||||||
@@ -93,7 +93,6 @@ class Tpl_MokoonyxInstallerScript implements InstallerScriptInterface
|
|||||||
$this->migrateFromCassiopeia();
|
$this->migrateFromCassiopeia();
|
||||||
$this->replaceCassiopeiaReferences();
|
$this->replaceCassiopeiaReferences();
|
||||||
$this->clearFaviconStamp();
|
$this->clearFaviconStamp();
|
||||||
$this->lockExtension();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -379,29 +378,6 @@ class Tpl_MokoonyxInstallerScript implements InstallerScriptInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Lock the extension to prevent uninstallation via Extension Manager.
|
|
||||||
*/
|
|
||||||
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,7 +39,7 @@
|
|||||||
</server>
|
</server>
|
||||||
</updateservers>
|
</updateservers>
|
||||||
<name>MokoOnyx</name>
|
<name>MokoOnyx</name>
|
||||||
<version>01.00.18</version>
|
<version>01.00.17</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>
|
||||||
@@ -373,6 +373,12 @@
|
|||||||
<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_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" />
|
<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>
|
</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>
|
||||||
</fields>
|
</fields>
|
||||||
</config>
|
</config>
|
||||||
</extension>
|
</extension>
|
||||||
|
|||||||
@@ -210,7 +210,6 @@ 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,7 +209,6 @@ 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;
|
||||||
|
|||||||
836
src/templates/theme-test.html
Normal file
836
src/templates/theme-test.html
Normal file
@@ -0,0 +1,836 @@
|
|||||||
|
<!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>
|
||||||
18
updates.xml
18
updates.xml
@@ -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.24</version>
|
<version>01.00.16</version>
|
||||||
<creationDate>2026-04-23</creationDate>
|
<creationDate>2026-04-22</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.24-dev.zip</downloadurl>
|
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/development/mokoonyx-01.00.16-dev.zip</downloadurl>
|
||||||
</downloads>
|
</downloads>
|
||||||
<sha256>a2d215c19b3487eeeae62735a618e183c2e665ff75f9031bdb5df4b3c8f299a6</sha256>
|
<sha256>6d0e690995eacd1b2e9193d847abd21369d2eb574406b27515d23f8d5a428c51</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>
|
||||||
@@ -81,6 +81,7 @@
|
|||||||
<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.07-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.07-rc.zip</downloadurl>
|
||||||
</downloads>
|
</downloads>
|
||||||
<sha256></sha256>
|
<sha256></sha256>
|
||||||
<tags><tag>rc</tag></tags>
|
<tags><tag>rc</tag></tags>
|
||||||
@@ -97,13 +98,14 @@
|
|||||||
<element>mokoonyx</element>
|
<element>mokoonyx</element>
|
||||||
<type>template</type>
|
<type>template</type>
|
||||||
<client>site</client>
|
<client>site</client>
|
||||||
<version>01.00.25</version>
|
<version>01.00.17</version>
|
||||||
<creationDate>2026-04-23</creationDate>
|
<creationDate>2026-04-22</creationDate>
|
||||||
<infourl title='MokoOnyx'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/stable</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/stable/mokoonyx-01.00.25.zip</downloadurl>
|
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/stable/mokoonyx-01.00.17.zip</downloadurl>
|
||||||
|
<downloadurl type='full' format='zip'>https://github.com/mokoconsulting-tech/MokoOnyx/releases/download/stable/mokoonyx-01.00.17.zip</downloadurl>
|
||||||
</downloads>
|
</downloads>
|
||||||
<sha256>d111a027bdd5f1eae1fe973933fa4043be2740d62fea3527fb856275f8fb3be4</sha256>
|
<sha256>f4811005774b99e54513cd2cae052b815ef085e2a04f3a91cd5c0bc9cff7ffc2</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