Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f2494de8b9 | |||
| 8d9653e0c9 | |||
| a3288c0042 | |||
| 203324a40b | |||
| f32fda306a | |||
| c38f5752b6 | |||
| 1b4984ed18 | |||
| ee8fc55d03 | |||
| f93d9c3e73 | |||
| 71cc1b6465 | |||
| b51ebf5534 | |||
| e9a0be9471 | |||
| 1cf063e08e | |||
| e3349bc043 | |||
| fb9e9ddc48 | |||
| d6e0036a0b | |||
| 6350b9cae7 | |||
| 009bdf3bcf | |||
| 08aebca9b2 | |||
| 7b74e09e2b | |||
| 1771a719c8 | |||
| adc1172829 | |||
| be63ddd8ec | |||
| 0055b7eaaf | |||
| faedeea34a | |||
| e070cb99f5 | |||
| 41bc58a8d0 | |||
| 8d53b2c80f | |||
| f781e0d9ae | |||
| ffa262f22b | |||
| b16b31c1de | |||
| 12a34619f2 | |||
| d69def86df | |||
| b4d843981b | |||
|
|
3de10bd2a2 | ||
| e9536a6998 |
@@ -191,6 +191,16 @@ jobs:
|
|||||||
composer install --no-dev --optimize-autoloader --no-interaction
|
composer install --no-dev --optimize-autoloader --no-interaction
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- name: Minify CSS and JS
|
||||||
|
run: |
|
||||||
|
npm ci --ignore-scripts
|
||||||
|
node scripts/minify.js
|
||||||
|
|
||||||
- name: Create package
|
- name: Create package
|
||||||
run: |
|
run: |
|
||||||
mkdir -p build/package
|
mkdir -p build/package
|
||||||
@@ -479,39 +489,27 @@ jobs:
|
|||||||
# Push to current branch
|
# Push to current branch
|
||||||
git push || true
|
git push || true
|
||||||
|
|
||||||
# Also update updates.xml on main via Gitea API (git push blocked by branch protection)
|
# Sync updates.xml to main via direct API
|
||||||
if [ "$CURRENT_BRANCH" != "main" ]; then
|
|
||||||
GA_TOKEN="${{ secrets.GA_TOKEN }}"
|
GA_TOKEN="${{ secrets.GA_TOKEN }}"
|
||||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||||
|
|
||||||
# Get current file SHA on main (required for update)
|
|
||||||
FILE_SHA=$(curl -sf -H "Authorization: token ${GA_TOKEN}" \
|
FILE_SHA=$(curl -sf -H "Authorization: token ${GA_TOKEN}" \
|
||||||
"${API}/contents/updates.xml?ref=main" | jq -r '.sha // empty')
|
"${API}/contents/updates.xml?ref=main" | jq -r '.sha // empty')
|
||||||
|
|
||||||
if [ -n "$FILE_SHA" ]; then
|
if [ -n "$FILE_SHA" ]; then
|
||||||
# Base64-encode the updates.xml content from working tree (has updated SHA)
|
|
||||||
CONTENT=$(base64 -w0 updates.xml)
|
CONTENT=$(base64 -w0 updates.xml)
|
||||||
|
curl -sf -X PUT -H "Authorization: token ${GA_TOKEN}" \
|
||||||
RESPONSE=$(curl -s -w "\n%{http_code}" -X PUT -H "Authorization: token ${GA_TOKEN}" \
|
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
"${API}/contents/updates.xml" \
|
"${API}/contents/updates.xml" \
|
||||||
-d "$(jq -n \
|
-d "$(jq -n \
|
||||||
--arg content "$CONTENT" \
|
--arg content "$CONTENT" \
|
||||||
--arg sha "$FILE_SHA" \
|
--arg sha "$FILE_SHA" \
|
||||||
--arg msg "chore: update ${STABILITY} channel to ${VERSION} on main [skip ci]" \
|
--arg msg "chore: sync updates.xml ${STABILITY} ${VERSION} [skip ci]" \
|
||||||
--arg branch "main" \
|
--arg branch "main" \
|
||||||
'{content: $content, sha: $sha, message: $msg, branch: $branch}'
|
'{content: $content, sha: $sha, message: $msg, branch: $branch}'
|
||||||
)")
|
)" > /dev/null 2>&1 \
|
||||||
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
|
&& echo "updates.xml synced to main" \
|
||||||
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ]; then
|
|| echo "WARNING: failed to sync updates.xml to main"
|
||||||
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
|
||||||
|
|||||||
335
.github/workflows/auto-release.yml
vendored
335
.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/MokoStandards
|
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
|
||||||
# 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,13 +22,15 @@
|
|||||||
# | 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 GitHub Release for this minor |
|
# | 7a. Patch: update existing Gitea 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 |
|
||||||
# | Patch 00 = development (no release). First release = patch 01. |
|
# | All patches release (including 00). Patch 00/01 = full pipeline. |
|
||||||
# | First release only (patch == 01): |
|
# | First release only (patch == 01): |
|
||||||
# | 7b. Create new GitHub Release |
|
# | 7b. Create new Gitea Release |
|
||||||
|
# | |
|
||||||
|
# | GitHub mirror: stable/rc releases only (continue-on-error) |
|
||||||
# | |
|
# | |
|
||||||
# +========================================================================+
|
# +========================================================================+
|
||||||
|
|
||||||
@@ -46,6 +48,9 @@ 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
|
||||||
@@ -61,19 +66,21 @@ jobs:
|
|||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GA_TOKEN || github.token }}
|
token: ${{ secrets.GA_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:
|
||||||
GA_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
|
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||||
|
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN }}"}}'
|
||||||
run: |
|
run: |
|
||||||
git clone --depth 1 --branch version/04 --quiet \
|
# Ensure PHP + Composer are available
|
||||||
"https://x-access-token:${GH_TOKEN}@git.mokoconsulting.tech/MokoConsulting/MokoStandards-API.git" \
|
if ! command -v composer &> /dev/null; then
|
||||||
|
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
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
|
||||||
@@ -99,21 +106,16 @@ 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=v${MAJOR}" >> "$GITHUB_OUTPUT"
|
echo "release_tag=stable" >> "$GITHUB_OUTPUT"
|
||||||
if [ "$PATCH" = "00" ]; then
|
echo "stability=stable" >> "$GITHUB_OUTPUT"
|
||||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "is_minor=false" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Version: $VERSION (patch 00 = development — skipping release)"
|
|
||||||
else
|
|
||||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||||
if [ "$PATCH" = "01" ]; then
|
if [ "$PATCH" = "00" ] || [ "$PATCH" = "01" ]; then
|
||||||
echo "is_minor=true" >> "$GITHUB_OUTPUT"
|
echo "is_minor=true" >> "$GITHUB_OUTPUT"
|
||||||
echo "Version: $VERSION (first release — full pipeline)"
|
echo "Version: $VERSION (first release for this minor — full pipeline)"
|
||||||
else
|
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 — platform version + badges only)"
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Check if already released
|
- name: Check if already released
|
||||||
if: steps.version.outputs.skip != 'true'
|
if: steps.version.outputs.skip != 'true'
|
||||||
@@ -131,11 +133,8 @@ 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"
|
||||||
|
|
||||||
if [ "$TAG_EXISTS" = "true" ] && [ "$BRANCH_EXISTS" = "true" ]; then
|
# Tag and branch may persist across patch releases — never skip
|
||||||
echo "already_released=true" >> "$GITHUB_OUTPUT"
|
|
||||||
else
|
|
||||||
echo "already_released=false" >> "$GITHUB_OUTPUT"
|
echo "already_released=false" >> "$GITHUB_OUTPUT"
|
||||||
fi
|
|
||||||
|
|
||||||
# -- SANITY CHECKS -------------------------------------------------------
|
# -- SANITY CHECKS -------------------------------------------------------
|
||||||
- name: "Sanity: Pre-release validation"
|
- name: "Sanity: Pre-release validation"
|
||||||
@@ -291,9 +290,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"
|
||||||
|
|
||||||
# Templates/modules don't have <element> — derive from <name> (lowercased)
|
# Derive element if not in manifest:
|
||||||
|
# 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=$(echo "$EXT_NAME" | tr '[:upper:]' '[:lower:]' | tr -d ' ')
|
EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
|
||||||
|
# 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>
|
||||||
@@ -312,7 +317,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.*" %s>' "/")
|
TARGET_PLATFORM=$(printf '<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" %s>' "/")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Build php_minimum tag
|
# Build php_minimum tag
|
||||||
@@ -321,11 +326,12 @@ jobs:
|
|||||||
PHP_TAG="<php_minimum>${PHP_MINIMUM}</php_minimum>"
|
PHP_TAG="<php_minimum>${PHP_MINIMUM}</php_minimum>"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
DOWNLOAD_URL="https://git.mokoconsulting.tech/${{ github.repository }}/releases/download/v${VERSION}/${EXT_ELEMENT}-${VERSION}.zip"
|
DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/stable/${EXT_ELEMENT}-${VERSION}.zip"
|
||||||
INFO_URL="https://git.mokoconsulting.tech/${{ github.repository }}/releases/tag/v${VERSION}"
|
INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/stable"
|
||||||
|
|
||||||
# -- Build stable entry to temp file ─────────────────────────
|
# -- Build update entry for a given stability tag
|
||||||
{
|
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>"
|
||||||
@@ -334,9 +340,7 @@ 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>'
|
printf '%s\n' " <tags><tag>${TAG_NAME}</tag></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>"
|
||||||
@@ -346,35 +350,27 @@ 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>'
|
||||||
[ -n "$DEV_ENTRY" ] && echo "$DEV_ENTRY"
|
build_entry "development"
|
||||||
[ -n "$ALPHA_ENTRY" ] && echo "$ALPHA_ENTRY"
|
build_entry "alpha"
|
||||||
[ -n "$BETA_ENTRY" ] && echo "$BETA_ENTRY"
|
build_entry "beta"
|
||||||
[ -n "$RC_ENTRY" ] && echo "$RC_ENTRY"
|
build_entry "rc"
|
||||||
cat /tmp/stable_entry.xml
|
build_entry "stable"
|
||||||
printf '%s\n' '</updates>'
|
printf '%s\n' '</updates>'
|
||||||
} > updates.xml
|
} > updates.xml
|
||||||
|
|
||||||
echo "updates.xml: ${VERSION} (stable + rc/dev preserved)" >> $GITHUB_STEP_SUMMARY
|
echo "updates.xml: ${VERSION} (all channels updated to stable)" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# -- Commit all changes ---------------------------------------------------
|
# -- Commit all changes ---------------------------------------------------
|
||||||
- name: Commit release changes
|
- name: Commit release changes
|
||||||
@@ -389,10 +385,12 @@ 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
|
git push -u origin HEAD
|
||||||
|
|
||||||
# -- STEP 6: Create tag ---------------------------------------------------
|
# -- STEP 6: Create tag ---------------------------------------------------
|
||||||
- name: "Step 6: Create git tag"
|
- name: "Step 6: Create git tag"
|
||||||
@@ -412,69 +410,75 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "Tag: ${TAG}" >> $GITHUB_STEP_SUMMARY
|
echo "Tag: ${TAG}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# -- STEP 7: Create or update GitHub Release ------------------------------
|
# -- STEP 7: Create or update Gitea Release --------------------------------
|
||||||
- name: "Step 7: GitHub Release"
|
- name: "Step 7: Gitea 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=$(gh release view "$RELEASE_TAG" --json tagName -q .tagName 2>/dev/null || true)
|
EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
||||||
|
"${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" ]; then
|
if [ -z "$EXISTING_ID" ]; then
|
||||||
# First release for this major
|
# First release for this major
|
||||||
gh release create "$RELEASE_TAG" \
|
curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
||||||
--title "v${MAJOR} (latest: ${VERSION})" \
|
-H "Content-Type: application/json" \
|
||||||
--notes-file /tmp/release_notes.md \
|
"${API_BASE}/releases" \
|
||||||
--target "$BRANCH"
|
-d "$(python3 -c "import json; print(json.dumps({
|
||||||
|
'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_NOTES=$(gh release view "$RELEASE_TAG" --json body -q .body 2>/dev/null || true)
|
CURRENT_BODY=$(echo "$EXISTING" | python3 -c "import sys,json; print(json.load(sys.stdin).get('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" \
|
---
|
||||||
--title "v${MAJOR} (latest: ${VERSION})" \
|
### ${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)
|
||||||
gh release view "$RELEASE_TAG" --json tagName > /dev/null 2>&1 || {
|
RELEASE_JSON=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
||||||
|
"${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)
|
||||||
@@ -508,27 +512,109 @@ 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 ----------------------------------
|
||||||
gh release upload "$RELEASE_TAG" "/tmp/${ZIP_NAME}" --clobber 2>/dev/null || true
|
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/octet-stream" \
|
||||||
|
--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="https://git.mokoconsulting.tech/${{ github.repository }}/releases/download/${RELEASE_TAG}/${ZIP_NAME}"
|
ZIP_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${ZIP_NAME}"
|
||||||
TAR_URL="https://git.mokoconsulting.tech/${{ github.repository }}/releases/download/${RELEASE_TAG}/${TAR_NAME}"
|
TAR_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${TAR_NAME}"
|
||||||
|
|
||||||
# Replace downloads block with both formats + SHA
|
# Use Python to update only the stable entry's downloads + sha256
|
||||||
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
|
export PY_ZIP_URL="$ZIP_URL" PY_TAR_URL="$TAR_URL" PY_SHA="$SHA256_ZIP"
|
||||||
if grep -q '<sha256>' updates.xml; then
|
python3 << 'PYEOF'
|
||||||
sed -i "s|<sha256>.*</sha256>|<sha256>${SHA256_ZIP}</sha256>|" updates.xml
|
import re, os
|
||||||
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
|
||||||
@@ -538,7 +624,50 @@ 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 | [${PACKAGE_NAME}](https://git.mokoconsulting.tech/${{ github.repository }}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}) |" >> $GITHUB_STEP_SUMMARY
|
echo "| Download | [${ZIP_NAME}](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${ZIP_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
|
||||||
@@ -559,5 +688,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](https://github.com/${{ github.repository }}/releases/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
|
||||||
fi
|
fi
|
||||||
|
|||||||
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@@ -103,6 +103,16 @@ jobs:
|
|||||||
composer install --no-dev --optimize-autoloader --no-interaction
|
composer install --no-dev --optimize-autoloader --no-interaction
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- name: Minify CSS and JS
|
||||||
|
run: |
|
||||||
|
npm ci --ignore-scripts
|
||||||
|
node scripts/minify.js
|
||||||
|
|
||||||
- name: Create package
|
- name: Create package
|
||||||
run: |
|
run: |
|
||||||
mkdir -p build/package
|
mkdir -p build/package
|
||||||
|
|||||||
268
.github/workflows/update-server.yml
vendored
268
.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/MokoStandards
|
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
|
||||||
# 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,16 +13,27 @@
|
|||||||
# 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/**
|
# - <tag>development</tag> on push to dev or 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/**'
|
||||||
@@ -46,6 +57,9 @@ 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
|
||||||
@@ -55,46 +69,50 @@ 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.pull_request.merged == true || github.event_name == 'workflow_dispatch' || github.event_name == 'push'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GA_TOKEN || github.token }}
|
token: ${{ secrets.GA_TOKEN }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup MokoStandards tools
|
- name: Setup MokoStandards tools
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
|
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||||
|
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN }}"}}'
|
||||||
run: |
|
run: |
|
||||||
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.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
|
||||||
/tmp/mokostandards 2>/dev/null || true
|
fi
|
||||||
if [ -d "/tmp/mokostandards" ] && [ -f "/tmp/mokostandards/composer.json" ]; then
|
git clone --depth 1 --branch main --quiet \
|
||||||
cd /tmp/mokostandards && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
|
||||||
|
/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 }}"
|
||||||
VERSION=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null || echo "0.0.0")
|
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")
|
||||||
|
|
||||||
# Auto-bump patch on alpha/beta/rc branches (not dev — dev bumps manually)
|
# Auto-bump patch on all branches (dev, alpha, beta, rc)
|
||||||
if [[ "$BRANCH" != dev/* ]]; then
|
|
||||||
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]"
|
||||||
BUMPED=$(php /tmp/mokostandards/api/cli/version_bump.php --path . 2>/dev/null || true)
|
BUMPED=$(php /tmp/mokostandards-api/cli/version_bump.php --path . 2>/dev/null || true)
|
||||||
if [ -n "$BUMPED" ]; then
|
if [ -n "$BUMPED" ]; then
|
||||||
VERSION=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null || echo "$VERSION")
|
VERSION=$(php /tmp/mokostandards-api/cli/version_read.php --path . 2>/dev/null || echo "$VERSION")
|
||||||
git add -A
|
git add -A
|
||||||
git commit -m "chore(version): auto-bump patch ${VERSION} [skip ci]" \
|
git commit -m "chore(version): auto-bump patch ${VERSION} [skip ci]" \
|
||||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>" 2>/dev/null || true
|
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>" 2>/dev/null || true
|
||||||
git push 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
|
||||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||||
@@ -105,12 +123,14 @@ jobs:
|
|||||||
STABILITY="beta"
|
STABILITY="beta"
|
||||||
elif [[ "$BRANCH" == alpha/* ]]; then
|
elif [[ "$BRANCH" == alpha/* ]]; then
|
||||||
STABILITY="alpha"
|
STABILITY="alpha"
|
||||||
elif [[ "$BRANCH" == dev/* ]]; then
|
elif [[ "$BRANCH" == dev/* ]] || [[ "$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
|
||||||
@@ -132,15 +152,18 @@ 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"
|
||||||
|
|
||||||
# Templates and modules don't have <element> — derive from <name>
|
# Derive element if not in manifest: try XML filename, then repo name
|
||||||
if [ -z "$EXT_ELEMENT" ]; then
|
if [ -z "$EXT_ELEMENT" ]; then
|
||||||
EXT_ELEMENT=$(echo "$EXT_NAME" | tr '[:upper:]' '[:lower:]' | tr -d ' ')
|
EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
|
||||||
|
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.*" %s>' "/")
|
[ -z "$TARGET_PLATFORM" ] && TARGET_PLATFORM=$(printf '<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" %s>' "/")
|
||||||
|
|
||||||
CLIENT_TAG=""
|
CLIENT_TAG=""
|
||||||
[ -n "$EXT_CLIENT" ] && CLIENT_TAG="<client>${EXT_CLIENT}</client>"
|
[ -n "$EXT_CLIENT" ] && CLIENT_TAG="<client>${EXT_CLIENT}</client>"
|
||||||
@@ -173,10 +196,10 @@ jobs:
|
|||||||
esac
|
esac
|
||||||
|
|
||||||
PACKAGE_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.zip"
|
PACKAGE_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.zip"
|
||||||
DOWNLOAD_URL="https://git.mokoconsulting.tech/${{ github.repository }}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}"
|
DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}"
|
||||||
INFO_URL="https://github.com/${REPO}"
|
INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_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
|
||||||
@@ -192,20 +215,62 @@ jobs:
|
|||||||
|
|
||||||
SHA256=$(sha256sum "/tmp/${PACKAGE_NAME}" | cut -d' ' -f1)
|
SHA256=$(sha256sum "/tmp/${PACKAGE_NAME}" | cut -d' ' -f1)
|
||||||
|
|
||||||
# Ensure release exists
|
# Ensure release exists on Gitea
|
||||||
gh release view "$RELEASE_TAG" --json tagName > /dev/null 2>&1 || \
|
RELEASE_JSON=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
||||||
gh release create "$RELEASE_TAG" --title "${RELEASE_TAG} (${DISPLAY_VERSION})" --notes "${STABILITY} release" --prerelease --target main 2>/dev/null || true
|
"${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
|
||||||
|
# Create release
|
||||||
|
RELEASE_JSON=$(curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
||||||
|
-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
|
# Upload both formats
|
||||||
gh release upload "$RELEASE_TAG" "/tmp/${PACKAGE_NAME}" --clobber 2>/dev/null || true
|
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/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"
|
||||||
@@ -220,40 +285,76 @@ 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}</sha256>\n"
|
[ -n "$SHA256" ] && NEW_ENTRY="${NEW_ENTRY} <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 ─────────────────────────────────────
|
# -- Merge into updates.xml (only update this stability channel) -
|
||||||
|
# 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
|
||||||
# Remove existing entry for this stability, insert new one
|
# Replace each cascading channel with the new entry (different tag)
|
||||||
printf 'import re\nstability = "%s"\n' "${STABILITY}" > /tmp/merge_xml.py
|
export PY_TARGETS="$TARGETS"
|
||||||
printf 'with open("updates.xml") as f: content = f.read()\n' >> /tmp/merge_xml.py
|
python3 << PYEOF
|
||||||
printf 'with open("/tmp/new_entry.xml") as f: new_entry = f.read()\n' >> /tmp/merge_xml.py
|
import re, os
|
||||||
printf 'pattern = r" <update>.*?<tag>" + re.escape(stability) + r"</tag>.*?</update>\\n?"\n' >> /tmp/merge_xml.py
|
targets = os.environ["PY_TARGETS"].split(",")
|
||||||
printf 'content = re.sub(pattern, "", content, flags=re.DOTALL)\n' >> /tmp/merge_xml.py
|
stability = "${STABILITY}"
|
||||||
printf 'content = content.replace("</updates>", new_entry + "\\n</updates>")\n' >> /tmp/merge_xml.py
|
with open("updates.xml") as f:
|
||||||
printf 'content = re.sub(r"\\n{3,}", "\\n\\n", content)\n' >> /tmp/merge_xml.py
|
content = f.read()
|
||||||
printf 'with open("updates.xml", "w") as f: f.write(content)\n' >> /tmp/merge_xml.py
|
with open("/tmp/new_entry.xml") as f:
|
||||||
python3 /tmp/merge_xml.py 2>/dev/null || {
|
new_entry_template = f.read()
|
||||||
|
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
|
||||||
@@ -265,7 +366,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
|
||||||
@@ -278,8 +379,55 @@ 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/')
|
if: contains(github.ref, 'dev/') || github.ref == 'refs/heads/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 }}
|
||||||
@@ -288,15 +436,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 }}"
|
||||||
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 \
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
2>/dev/null | jq -r '.permission' || \
|
|
||||||
curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/collaborators/${ACTOR}" 2>/dev/null \
|
PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
||||||
2>/dev/null | jq -r '.role' || echo "read")
|
"${API_BASE}/collaborators/${ACTOR}/permission" 2>/dev/null | \
|
||||||
|
python3 -c "import sys,json; print(json.load(sys.stdin).get('permission','read'))" 2>/dev/null || echo "read")
|
||||||
case "$PERMISSION" in
|
case "$PERMISSION" in
|
||||||
admin|maintain|write) ;;
|
admin|maintain|write) ;;
|
||||||
*)
|
*)
|
||||||
@@ -324,11 +472,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,6 +119,14 @@ site/
|
|||||||
*.js.map
|
*.js.map
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Minified assets (generated at release time by scripts/minify.js)
|
||||||
|
# Vendor pre-minified files are excluded from this rule.
|
||||||
|
# ============================================================
|
||||||
|
src/media/css/*.min.css
|
||||||
|
src/media/css/theme/*.min.css
|
||||||
|
src/media/js/*.min.js
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# CI / test artifacts
|
# CI / test artifacts
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|||||||
478
README.md
478
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.17
|
VERSION: 01.00.19
|
||||||
BRIEF: Documentation for MokoOnyx template
|
BRIEF: Documentation for MokoOnyx template
|
||||||
-->
|
-->
|
||||||
|
|
||||||
@@ -24,489 +24,31 @@
|
|||||||
[](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 with consistent title rendering and Bootstrap 5 styling
|
- **Template Overrides**: Includes overrides for all core Joomla modules, Community Builder, and DPCalendar
|
||||||
- **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
|
||||||
- **Color Palettes**: Standard, Alternative, and Custom color schemes
|
- **Google Tag Manager / GA4**: Optional analytics integrations
|
||||||
- **Theme Persistence**: User preferences saved via localStorage
|
|
||||||
- **Theme Control Options**: Switch, radio buttons, or hidden controls
|
|
||||||
- **Auto Dark Mode**: Optional automatic dark mode based on time/system settings
|
|
||||||
- **Meta Tags**: Automatic color-scheme and theme-color meta tags
|
|
||||||
|
|
||||||
### Developer Features
|
|
||||||
|
|
||||||
- **Custom Code Injection**: Add custom HTML to `<head>` start/end
|
|
||||||
- **Drawer Sidebars**: Configurable left/right drawer positions with custom icons
|
|
||||||
- **Font Options**: Local and web fonts (Roboto, Fira Sans, Noto Sans)
|
|
||||||
- **Sticky Header**: Optional sticky navigation
|
|
||||||
- **Back to Top**: Floating back-to-top button
|
|
||||||
|
|
||||||
### Analytics & Tracking
|
|
||||||
|
|
||||||
- **Google Tag Manager**: Optional GTM integration with container ID configuration
|
|
||||||
- **Google Analytics**: Optional GA4 integration with measurement ID
|
|
||||||
- **Privacy-Friendly**: All tracking features are optional and easily disabled
|
|
||||||
|
|
||||||
### Content Features
|
|
||||||
|
|
||||||
- **Table of Contents**: Automatic TOC generation for long articles
|
- **Table of Contents**: Automatic TOC generation for long articles
|
||||||
- Placement options: `toc-left` or `toc-right` layouts
|
|
||||||
- Automatic heading extraction and navigation
|
|
||||||
- Responsive sidebar positioning
|
|
||||||
|
|
||||||
---
|
## Requirements
|
||||||
|
|
||||||
## 📋 Requirements
|
|
||||||
|
|
||||||
- **Joomla**: 5.x or 6.x
|
- **Joomla**: 5.x or 6.x
|
||||||
- **PHP**: 8.1 or higher
|
- **PHP**: 8.1 or higher
|
||||||
- **Database**: MySQL 5.7+ / MariaDB 10.2+ / PostgreSQL 11+
|
|
||||||
- **Browser Support**: Modern browsers (Chrome, Firefox, Safari, Edge)
|
|
||||||
|
|
||||||
---
|
## Installation
|
||||||
|
|
||||||
## 📦 Installation
|
Download the latest `mokoonyx-{version}.zip` from [Releases](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases) and install via Joomla's Extension Manager.
|
||||||
|
|
||||||
**Note**: MokoOnyx is a **standalone Joomla template extension** (not bundled as a package). Install it directly via Joomla's Extension Manager.
|
## License
|
||||||
|
|
||||||
### 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.
|
This project is licensed under the **GNU General Public License v3.0** - see the [LICENSE](./LICENSE) file for details.
|
||||||
|
|
||||||
### Third-Party Licenses
|
|
||||||
|
|
||||||
- **Joomla! CMS**: GPL-2.0-or-later
|
|
||||||
- **Cassiopeia Template**: GPL-2.0-or-later (Joomla Project)
|
|
||||||
- **Font Awesome 7 Free**: Font Awesome Free License
|
|
||||||
- **Bootstrap 5**: MIT License
|
|
||||||
- **Bootstrap TOC**: MIT License (A. Feld)
|
|
||||||
|
|
||||||
All third-party libraries and assets remain the property of their respective authors and are credited in source files.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔗 Links
|
**Made with love by [Moko Consulting](https://mokoconsulting.tech)**
|
||||||
|
|
||||||
- **Repository**: [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,8 +10,9 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=8.1",
|
"ext-zip": "*",
|
||||||
"ext-zip": "*"
|
"mokoconsulting-tech/enterprise": "dev-version/04",
|
||||||
|
"php": ">=8.1"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"mokoconsulting-tech/enterprise": "^4.0"
|
"mokoconsulting-tech/enterprise": "^4.0"
|
||||||
|
|||||||
87
scripts/minify.js
Normal file
87
scripts/minify.js
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
*
|
||||||
|
* This file is part of a Moko Consulting project.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* FILE INFORMATION
|
||||||
|
* DEFGROUP: MokoOnyx.Build
|
||||||
|
* INGROUP: MokoOnyx
|
||||||
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||||
|
* PATH: /scripts/minify.js
|
||||||
|
* VERSION: 01.00.00
|
||||||
|
* BRIEF: Minify project CSS and JS assets (excludes vendor/)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const CleanCSS = require('clean-css');
|
||||||
|
const { minify: terserMinify } = require('terser');
|
||||||
|
|
||||||
|
const SRC = path.resolve(__dirname, '..', 'src', 'media');
|
||||||
|
|
||||||
|
// Project-owned files only — vendor assets ship pre-minified
|
||||||
|
const CSS_FILES = [
|
||||||
|
'css/editor.css',
|
||||||
|
'css/template.css',
|
||||||
|
'css/theme/dark.standard.css',
|
||||||
|
'css/theme/light.standard.css',
|
||||||
|
];
|
||||||
|
|
||||||
|
const JS_FILES = [
|
||||||
|
'js/gtm.js',
|
||||||
|
'js/template.js',
|
||||||
|
];
|
||||||
|
|
||||||
|
async function minifyCSS(relPath) {
|
||||||
|
const src = path.join(SRC, relPath);
|
||||||
|
const dest = src.replace(/\.css$/, '.min.css');
|
||||||
|
const input = fs.readFileSync(src, 'utf8');
|
||||||
|
const result = new CleanCSS({ level: 1 }).minify(input);
|
||||||
|
|
||||||
|
if (result.errors.length) {
|
||||||
|
console.error(` FAIL ${relPath}:`, result.errors);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(dest, result.styles);
|
||||||
|
const ratio = ((1 - result.styles.length / input.length) * 100).toFixed(0);
|
||||||
|
console.log(` ${relPath} → .min.css (${ratio}% smaller)`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function minifyJS(relPath) {
|
||||||
|
const src = path.join(SRC, relPath);
|
||||||
|
const dest = src.replace(/\.js$/, '.min.js');
|
||||||
|
const input = fs.readFileSync(src, 'utf8');
|
||||||
|
const result = await terserMinify(input, { compress: true, mangle: true });
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
console.error(` FAIL ${relPath}:`, result.error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(dest, result.code);
|
||||||
|
const ratio = ((1 - result.code.length / input.length) * 100).toFixed(0);
|
||||||
|
console.log(` ${relPath} → .min.js (${ratio}% smaller)`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('Minifying project assets...\n');
|
||||||
|
|
||||||
|
let ok = true;
|
||||||
|
|
||||||
|
for (const f of CSS_FILES) {
|
||||||
|
if (!await minifyCSS(f)) ok = false;
|
||||||
|
}
|
||||||
|
for (const f of JS_FILES) {
|
||||||
|
if (!await minifyJS(f)) ok = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(ok ? '\nDone.' : '\nCompleted with errors.');
|
||||||
|
process.exit(ok ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -176,7 +176,7 @@ TPL_MOKOONYX_CSS_VARS_BLOCK_COLORS_LABEL="Block Colour System (top-a / top-b / b
|
|||||||
TPL_MOKOONYX_CSS_VARS_BLOCK_COLORS_DESC="Automatic brand colour palette for modules in <code>top-a</code>, <code>top-b</code>, <code>bottom-a</code>, and <code>bottom-b</code> positions. Colours assigned by <code>:nth-child()</code> order — no classes needed.<br><br><strong>Slot palette</strong><br><code>--block-color-1</code> / <code>--block-text-1</code> — 1st module<br><code>--block-color-2</code> / <code>--block-text-2</code> — 2nd module<br><code>--block-color-3</code> / <code>--block-text-3</code> — 3rd module<br><code>--block-color-4</code> / <code>--block-text-4</code> — 4th module<br><br><strong>Named overrides</strong> (add an ID to the module HTML to bypass slot colour)<br><code>--block-highlight-bg</code> / <code>--block-highlight-text</code> — for <code>#block-highlight</code><br><code>--block-cta-bg</code> / <code>--block-cta-text</code> — for <code>#block-cta</code><br><code>--block-alert-bg</code> / <code>--block-alert-text</code> — for <code>#block-alert</code><br><br><strong>Priority:</strong> Named ID > Slot colour. No <code>!important</code> needed — specificity handles it."
|
TPL_MOKOONYX_CSS_VARS_BLOCK_COLORS_DESC="Automatic brand colour palette for modules in <code>top-a</code>, <code>top-b</code>, <code>bottom-a</code>, and <code>bottom-b</code> positions. Colours assigned by <code>:nth-child()</code> order — no classes needed.<br><br><strong>Slot palette</strong><br><code>--block-color-1</code> / <code>--block-text-1</code> — 1st module<br><code>--block-color-2</code> / <code>--block-text-2</code> — 2nd module<br><code>--block-color-3</code> / <code>--block-text-3</code> — 3rd module<br><code>--block-color-4</code> / <code>--block-text-4</code> — 4th module<br><br><strong>Named overrides</strong> (add an ID to the module HTML to bypass slot colour)<br><code>--block-highlight-bg</code> / <code>--block-highlight-text</code> — for <code>#block-highlight</code><br><code>--block-cta-bg</code> / <code>--block-cta-text</code> — for <code>#block-cta</code><br><code>--block-alert-bg</code> / <code>--block-alert-text</code> — for <code>#block-alert</code><br><br><strong>Priority:</strong> Named ID > Slot colour. No <code>!important</code> needed — specificity handles it."
|
||||||
|
|
||||||
TPL_MOKOONYX_CSS_VARS_HEADER_LABEL="Header Background"
|
TPL_MOKOONYX_CSS_VARS_HEADER_LABEL="Header Background"
|
||||||
TPL_MOKOONYX_CSS_VARS_HEADER_DESC="Controls the background of the topbar/header area.<br><code>--header-background-image</code> — CSS <code>background-image</code> value (default: built-in SVG pattern)<br><code>--header-background-attachment</code> — <code>fixed</code> or <code>scroll</code><br><code>--header-background-repeat</code> — e.g. <code>repeat</code>, <code>no-repeat</code><br><code>--header-background-size</code> — e.g. <code>auto</code>, <code>cover</code>, <code>contain</code>"
|
TPL_MOKOONYX_CSS_VARS_HEADER_DESC="Controls the background of the topbar/header area.<br><code>--header-background-color</code> — fallback colour when no image is set (default: <code>#adadad</code> light / <code>#1a1f2b</code> dark). Set <code>--header-background-image: none;</code> to use a solid colour.<br><code>--header-background-image</code> — CSS <code>background-image</code> value (default: built-in SVG pattern)<br><code>--header-background-attachment</code> — <code>fixed</code> or <code>scroll</code><br><code>--header-background-repeat</code> — e.g. <code>repeat</code>, <code>no-repeat</code><br><code>--header-background-size</code> — e.g. <code>auto</code>, <code>cover</code>, <code>contain</code>"
|
||||||
|
|
||||||
TPL_MOKOONYX_CSS_VARS_CONTAINERS_LABEL="Container Backgrounds"
|
TPL_MOKOONYX_CSS_VARS_CONTAINERS_LABEL="Container Backgrounds"
|
||||||
TPL_MOKOONYX_CSS_VARS_CONTAINERS_DESC="Each layout container has its own background variables. Replace <code>{pos}</code> with: <code>below-topbar</code>, <code>top-a</code>, <code>top-b</code>, <code>sidebar</code>, <code>bottom-a</code>, or <code>bottom-b</code>.<br><br><code>--container-{pos}-bg-image</code> — Background image (default: <code>none</code>)<br><code>--container-{pos}-bg-color</code> — Background colour (default: <code>transparent</code>)<br><code>--container-{pos}-bg-position</code> — Background position<br><code>--container-{pos}-bg-attachment</code> — <code>fixed</code> or <code>scroll</code><br><code>--container-{pos}-bg-repeat</code> — Repeat behaviour<br><code>--container-{pos}-bg-size</code> — e.g. <code>cover</code>, <code>auto</code><br><code>--container-{pos}-border</code> — Border shorthand<br><code>--container-{pos}-border-radius</code> — Border radius<br><br>Also: <code>--container-toc-bg</code> / <code>--container-toc-color</code> for the TOC sidebar."
|
TPL_MOKOONYX_CSS_VARS_CONTAINERS_DESC="Each layout container has its own background variables. Replace <code>{pos}</code> with: <code>below-topbar</code>, <code>top-a</code>, <code>top-b</code>, <code>sidebar</code>, <code>bottom-a</code>, or <code>bottom-b</code>.<br><br><code>--container-{pos}-bg-image</code> — Background image (default: <code>none</code>)<br><code>--container-{pos}-bg-color</code> — Background colour (default: <code>transparent</code>)<br><code>--container-{pos}-bg-position</code> — Background position<br><code>--container-{pos}-bg-attachment</code> — <code>fixed</code> or <code>scroll</code><br><code>--container-{pos}-bg-repeat</code> — Repeat behaviour<br><code>--container-{pos}-bg-size</code> — e.g. <code>cover</code>, <code>auto</code><br><code>--container-{pos}-border</code> — Border shorthand<br><code>--container-{pos}-border-radius</code> — Border radius<br><br>Also: <code>--container-toc-bg</code> / <code>--container-toc-color</code> for the TOC sidebar."
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ TPL_MOKOONYX_CSS_VARS_BLOCK_COLORS_LABEL="Block Color System (top-a / top-b / bo
|
|||||||
TPL_MOKOONYX_CSS_VARS_BLOCK_COLORS_DESC="Automatic brand color palette for modules in <code>top-a</code>, <code>top-b</code>, <code>bottom-a</code>, and <code>bottom-b</code> positions. Colors assigned by <code>:nth-child()</code> order — no classes needed.<br><br><strong>Slot palette</strong><br><code>--block-color-1</code> / <code>--block-text-1</code> — 1st module<br><code>--block-color-2</code> / <code>--block-text-2</code> — 2nd module<br><code>--block-color-3</code> / <code>--block-text-3</code> — 3rd module<br><code>--block-color-4</code> / <code>--block-text-4</code> — 4th module<br><br><strong>Named overrides</strong> (add an ID to the module HTML to bypass slot color)<br><code>--block-highlight-bg</code> / <code>--block-highlight-text</code> — for <code>#block-highlight</code><br><code>--block-cta-bg</code> / <code>--block-cta-text</code> — for <code>#block-cta</code><br><code>--block-alert-bg</code> / <code>--block-alert-text</code> — for <code>#block-alert</code><br><br><strong>Priority:</strong> Named ID > Slot color. No <code>!important</code> needed — specificity handles it."
|
TPL_MOKOONYX_CSS_VARS_BLOCK_COLORS_DESC="Automatic brand color palette for modules in <code>top-a</code>, <code>top-b</code>, <code>bottom-a</code>, and <code>bottom-b</code> positions. Colors assigned by <code>:nth-child()</code> order — no classes needed.<br><br><strong>Slot palette</strong><br><code>--block-color-1</code> / <code>--block-text-1</code> — 1st module<br><code>--block-color-2</code> / <code>--block-text-2</code> — 2nd module<br><code>--block-color-3</code> / <code>--block-text-3</code> — 3rd module<br><code>--block-color-4</code> / <code>--block-text-4</code> — 4th module<br><br><strong>Named overrides</strong> (add an ID to the module HTML to bypass slot color)<br><code>--block-highlight-bg</code> / <code>--block-highlight-text</code> — for <code>#block-highlight</code><br><code>--block-cta-bg</code> / <code>--block-cta-text</code> — for <code>#block-cta</code><br><code>--block-alert-bg</code> / <code>--block-alert-text</code> — for <code>#block-alert</code><br><br><strong>Priority:</strong> Named ID > Slot color. No <code>!important</code> needed — specificity handles it."
|
||||||
|
|
||||||
TPL_MOKOONYX_CSS_VARS_HEADER_LABEL="Header Background"
|
TPL_MOKOONYX_CSS_VARS_HEADER_LABEL="Header Background"
|
||||||
TPL_MOKOONYX_CSS_VARS_HEADER_DESC="Controls the background of the topbar/header area.<br><code>--header-background-image</code> — CSS <code>background-image</code> value (default: built-in SVG pattern)<br><code>--header-background-attachment</code> — <code>fixed</code> or <code>scroll</code><br><code>--header-background-repeat</code> — e.g. <code>repeat</code>, <code>no-repeat</code><br><code>--header-background-size</code> — e.g. <code>auto</code>, <code>cover</code>, <code>contain</code>"
|
TPL_MOKOONYX_CSS_VARS_HEADER_DESC="Controls the background of the topbar/header area.<br><code>--header-background-color</code> — fallback color when no image is set (default: <code>#adadad</code> light / <code>#1a1f2b</code> dark). Set <code>--header-background-image: none;</code> to use a solid color.<br><code>--header-background-image</code> — CSS <code>background-image</code> value (default: built-in SVG pattern)<br><code>--header-background-attachment</code> — <code>fixed</code> or <code>scroll</code><br><code>--header-background-repeat</code> — e.g. <code>repeat</code>, <code>no-repeat</code><br><code>--header-background-size</code> — e.g. <code>auto</code>, <code>cover</code>, <code>contain</code>"
|
||||||
|
|
||||||
TPL_MOKOONYX_CSS_VARS_CONTAINERS_LABEL="Container Backgrounds"
|
TPL_MOKOONYX_CSS_VARS_CONTAINERS_LABEL="Container Backgrounds"
|
||||||
TPL_MOKOONYX_CSS_VARS_CONTAINERS_DESC="Each layout container has its own background variables. Replace <code>{pos}</code> with: <code>below-topbar</code>, <code>top-a</code>, <code>top-b</code>, <code>sidebar</code>, <code>bottom-a</code>, or <code>bottom-b</code>.<br><br><code>--container-{pos}-bg-image</code> — Background image (default: <code>none</code>)<br><code>--container-{pos}-bg-color</code> — Background color (default: <code>transparent</code>)<br><code>--container-{pos}-bg-position</code> — Background position<br><code>--container-{pos}-bg-attachment</code> — <code>fixed</code> or <code>scroll</code><br><code>--container-{pos}-bg-repeat</code> — Repeat behavior<br><code>--container-{pos}-bg-size</code> — e.g. <code>cover</code>, <code>auto</code><br><code>--container-{pos}-border</code> — Border shorthand<br><code>--container-{pos}-border-radius</code> — Border radius<br><br>Also: <code>--container-toc-bg</code> / <code>--container-toc-color</code> for the TOC sidebar."
|
TPL_MOKOONYX_CSS_VARS_CONTAINERS_DESC="Each layout container has its own background variables. Replace <code>{pos}</code> with: <code>below-topbar</code>, <code>top-a</code>, <code>top-b</code>, <code>sidebar</code>, <code>bottom-a</code>, or <code>bottom-b</code>.<br><br><code>--container-{pos}-bg-image</code> — Background image (default: <code>none</code>)<br><code>--container-{pos}-bg-color</code> — Background color (default: <code>transparent</code>)<br><code>--container-{pos}-bg-position</code> — Background position<br><code>--container-{pos}-bg-attachment</code> — <code>fixed</code> or <code>scroll</code><br><code>--container-{pos}-bg-repeat</code> — Repeat behavior<br><code>--container-{pos}-bg-size</code> — e.g. <code>cover</code>, <code>auto</code><br><code>--container-{pos}-border</code> — Border shorthand<br><code>--container-{pos}-border-radius</code> — Border radius<br><br>Also: <code>--container-toc-bg</code> / <code>--container-toc-color</code> for the TOC sidebar."
|
||||||
|
|||||||
1
src/media/css/editor.min.css
vendored
1
src/media/css/editor.min.css
vendored
@@ -1 +0,0 @@
|
|||||||
@charset "UTF-8";body{font-size:1rem;font-weight:400;line-height:1.5;color:#22262a;background-color:#fff}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:700;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}h2{font-size:calc(1.325rem + .9vw)}h3{font-size:calc(1.3rem + .6vw)}h4{font-size:calc(1.275rem + .3vw)}h5{font-size:1.25rem}h6{font-size:1rem}a{text-decoration:none}a:link{color:#224faa}a:hover{color:#424077}p{margin-top:0;margin-bottom:1rem}hr#system-readmore{color:red;border:1px dashed red}span[lang]{padding:2px;border:1px dashed #bbb}span[lang]:after{font-size:smaller;color:red;vertical-align:super;content:attr(lang)}
|
|
||||||
@@ -13927,6 +13927,11 @@ 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));
|
||||||
@@ -13937,6 +13942,12 @@ 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);
|
||||||
@@ -14193,10 +14204,11 @@ fieldset>* {
|
|||||||
|
|
||||||
.container-header {
|
.container-header {
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
background: var(--header-background-image, url('../../../../../../media/templates/site/mokoonyx/images/bg.svg'));
|
background-color: var(--header-background-color, #adadad);
|
||||||
|
background-image: var(--header-background-image, url('../../../../../../media/templates/site/mokoonyx/images/bg.svg'));
|
||||||
background-size: var(--header-background-size, auto);
|
background-size: var(--header-background-size, auto);
|
||||||
box-shadow: 0 5px 5px hsla(0, 0%, 0%, 0.03) inset;
|
|
||||||
background-repeat: var(--header-background-repeat, repeat);
|
background-repeat: var(--header-background-repeat, repeat);
|
||||||
|
box-shadow: 0 5px 5px hsla(0, 0%, 0%, 0.03) inset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sticky header: override z-index to stay above all content */
|
/* Sticky header: override z-index to stay above all content */
|
||||||
|
|||||||
1
src/media/css/template.min.css
vendored
1
src/media/css/template.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -210,6 +210,7 @@ color-scheme: dark;
|
|||||||
|
|
||||||
|
|
||||||
/* ===== HEADER BACKGROUND ===== */
|
/* ===== HEADER BACKGROUND ===== */
|
||||||
|
--header-background-color: #1a1f2b;
|
||||||
--header-background-image: url('../../../../../../media/templates/site/mokoonyx/images/bg.svg');
|
--header-background-image: url('../../../../../../media/templates/site/mokoonyx/images/bg.svg');
|
||||||
--header-background-attachment: fixed;
|
--header-background-attachment: fixed;
|
||||||
--header-background-repeat: repeat;
|
--header-background-repeat: repeat;
|
||||||
|
|||||||
1
src/media/css/theme/dark.standard.min.css
vendored
1
src/media/css/theme/dark.standard.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -209,6 +209,7 @@ color-scheme: light;
|
|||||||
|
|
||||||
|
|
||||||
/* ===== HEADER BACKGROUND ===== */
|
/* ===== HEADER BACKGROUND ===== */
|
||||||
|
--header-background-color: #adadad;
|
||||||
--header-background-image: url('../../../../../../media/templates/site/mokoonyx/images/bg.svg');
|
--header-background-image: url('../../../../../../media/templates/site/mokoonyx/images/bg.svg');
|
||||||
--header-background-attachment: fixed;
|
--header-background-attachment: fixed;
|
||||||
--header-background-repeat: repeat;
|
--header-background-repeat: repeat;
|
||||||
|
|||||||
1
src/media/css/theme/light.standard.min.css
vendored
1
src/media/css/theme/light.standard.min.css
vendored
File diff suppressed because one or more lines are too long
1
src/media/js/gtm.min.js
vendored
1
src/media/js/gtm.min.js
vendored
@@ -1 +0,0 @@
|
|||||||
(()=>{"use strict";const e=window,t={},n=e=>{const t=(()=>{const e=document.currentScript;return e||(Array.from(document.getElementsByTagName("script")).reverse().find(e=>(e.getAttribute("src")||"").includes("/gtm.js"))||null)})(),n=document.documentElement,o=document.body,a=document.querySelector(`meta[name="moko:gtm-${e}"]`);return t&&t.dataset&&t.dataset[e]||n&&n.dataset&&n.dataset[e]||o&&o.dataset&&o.dataset[e]||a&&a.getAttribute("content")||null},o=(e,t=!1)=>{if(null==e)return t;const n=String(e).trim().toLowerCase();return!!["1","true","yes","y","on"].includes(n)||!["0","false","no","n","off"].includes(n)&&t},a=(...e)=>{if(r.debug)try{console.info("[moko-gtm]",...e)}catch(e){}},r={id:"",dataLayerName:"dataLayer",debug:!1,ignoreDNT:!1,blockOnDev:!0,envAuth:"",envPreview:"",consentDefault:{analytics_storage:"granted",functionality_storage:"granted",security_storage:"granted",ad_storage:"denied",ad_user_data:"denied",ad_personalization:"denied"},pageVars:()=>({})},d=(e,t={})=>{const n={...e};for(const e in t){if(!Object.prototype.hasOwnProperty.call(t,e))continue;const o=t[e];o&&"object"==typeof o&&!Array.isArray(o)?n[e]={...n[e]||{},...o}:void 0!==o&&(n[e]=o)}return n},i=()=>{const t=e.MOKO_GTM_OPTIONS&&"object"==typeof e.MOKO_GTM_OPTIONS?e.MOKO_GTM_OPTIONS:{},a=n("id")||e.MOKO_GTM_ID||"",r=n("dataLayer")||"",d=n("debug"),i=n("ignoreDnt"),c=n("blockOnDev"),s=n("envAuth")||"",u=n("envPreview")||"";return{id:a||t.id||"",dataLayerName:r||t.dataLayerName||void 0,debug:o(d,!!t.debug),ignoreDNT:o(i,!!t.ignoreDNT),blockOnDev:o(c,t.blockOnDev??!0),envAuth:s||t.envAuth||"",envPreview:u||t.envPreview||"",consentDefault:t.consentDefault||void 0,pageVars:"function"==typeof t.pageVars?t.pageVars:void 0}},c=()=>{const t=r.dataLayerName;return e[t]=e[t]||[],e[t]},s=(...e)=>{c().push(arguments.length>1?e:e[0]),a("gtag push:",e)};t.push=(...e)=>s(...e),t.setConsent=e=>{s("consent","update",e||{})},t.isLoaded=()=>!!document.querySelector('script[src*="googletagmanager.com/gtm.js"]'),t.config=()=>({...r});const u=()=>{if(!r.id)return void a("GTM ID missing; aborting load.");if(t.isLoaded())return void a("GTM already loaded; skipping duplicate injection.");c().push({"gtm.start":(new Date).getTime(),event:"gtm.js"});const e=document.getElementsByTagName("script")[0],n=document.createElement("script");n.async=!0,n.src=`https://www.googletagmanager.com/gtm.js?id=${encodeURIComponent(r.id)}${"dataLayer"!==r.dataLayerName?`&l=${encodeURIComponent(r.dataLayerName)}`:""}${(()=>{const e=[];return r.envAuth&&e.push(`gtm_auth=${encodeURIComponent(r.envAuth)}`),r.envPreview&&e.push(`gtm_preview=${encodeURIComponent(r.envPreview)}`,"gtm_cookies_win=x"),e.length?`&${e.join("&")}`:""})()}`,e&&e.parentNode?e.parentNode.insertBefore(n,e):(document.head||document.documentElement).appendChild(n),a("Injected GTM script:",n.src)},g=()=>!r.ignoreDNT&&(()=>{const e=navigator,t=(e.doNotTrack||e.msDoNotTrack||e.navigator&&e.navigator.doNotTrack||"").toString().toLowerCase();return"1"===t||"yes"===t})()?(a("DNT is enabled; blocking GTM load (set ignoreDNT=true to override)."),!1):!r.blockOnDev||!(()=>{const t=e.location&&e.location.hostname||"";return"localhost"===t||"127.0.0.1"===t||t.endsWith(".local")||t.endsWith(".test")})()||(a("Development host detected; blocking GTM load (set blockOnDev=false to override)."),!1);t.init=(e={})=>{const t=i(),n=d(r,d(t,e));Object.assign(r,n),a("Config:",r),c(),s("consent","default",r.consentDefault),a("Applied default consent:",r.consentDefault),(()=>{const e={event:"moko.page_init",page_title:document.title||"",page_language:document.documentElement&&document.documentElement.lang||"",..."function"==typeof r.pageVars&&r.pageVars()||{}};s(e)})(),g()?u():a("GTM load prevented by configuration or environment.")};const m=()=>{!(!i().id&&!e.MOKO_GTM_ID)?t.init():a("No GTM ID detected; awaiting manual init via window.mokoGTM.init({ id: 'GTM-XXXXXXX' }).")};"complete"===document.readyState||"interactive"===document.readyState?setTimeout(m,0):document.addEventListener("DOMContentLoaded",m,{once:!0}),e.mokoGTM=t;try{const e=i();o(e.debug,!1)&&(r.debug=!0,a("Ready. You can call window.mokoGTM.init({ id: 'GTM-XXXXXXX' })."))}catch(e){}})();
|
|
||||||
1
src/media/js/template.min.js
vendored
1
src/media/js/template.min.js
vendored
@@ -1 +0,0 @@
|
|||||||
!function(e,t){"use strict";var a="theme",n=e.matchMedia("(prefers-color-scheme: dark)"),r=t.documentElement;function o(e){r.setAttribute("data-bs-theme",e),r.setAttribute("data-aria-theme",e);try{localStorage.setItem(a,e)}catch(e){}}function d(){try{localStorage.removeItem(a)}catch(e){}}function i(){return n.matches?"dark":"light"}function c(){try{return localStorage.getItem(a)}catch(e){return null}}function l(){if(!t.getElementById("mokoThemeFab")){var a,l=t.createElement("div");l.id="mokoThemeFab",l.className=(a=(t.body.getAttribute("data-theme-fab-pos")||"br").toLowerCase(),/^(br|bl|tr|tl)$/.test(a)||(a="br"),"pos-"+a);var s=t.createElement("span");s.className="label",s.textContent="Light";var u=t.createElement("button");u.id="mokoThemeSwitch",u.type="button",u.setAttribute("role","switch"),u.setAttribute("aria-label","Toggle dark mode"),u.setAttribute("aria-checked","false");var m=t.createElement("span");m.className="switch";var h=t.createElement("span");h.className="knob",m.appendChild(h),u.appendChild(m);var f=t.createElement("span");f.className="label",f.textContent="Dark";var b=t.createElement("button");b.id="mokoThemeAuto",b.type="button",b.className="btn btn-sm btn-link text-decoration-none px-2",b.setAttribute("aria-label","Follow system theme"),b.textContent="Auto",u.addEventListener("click",function(){var e="dark"===(r.getAttribute("data-bs-theme")||"light").toLowerCase()?"light":"dark";o(e),u.setAttribute("aria-checked","dark"===e?"true":"false");var a=t.querySelector('meta[name="theme-color"]');a&&a.setAttribute("content","dark"===e?"#0f1115":"#ffffff")}),b.addEventListener("click",function(){d();var e=i();o(e),u.setAttribute("aria-checked","dark"===e?"true":"false")});var g=function(){if(!c()){var e=i();o(e),u.setAttribute("aria-checked","dark"===e?"true":"false")}};"function"==typeof n.addEventListener?n.addEventListener("change",g):"function"==typeof n.addListener&&n.addListener(g);var p=c()||i();u.setAttribute("aria-checked","dark"===p?"true":"false"),l.appendChild(s),l.appendChild(u),l.appendChild(f),l.appendChild(b),t.body.appendChild(l),e.mokoThemeFabStatus=function(){var a=t.getElementById("mokoThemeFab");if(!a)return{mounted:!1};var n=a.getBoundingClientRect();return{mounted:!0,rect:{top:n.top,left:n.left,width:n.width,height:n.height},zIndex:e.getComputedStyle(a).zIndex,posClass:a.className}},setTimeout(function(){var e=l.getBoundingClientRect();(e.width<10||e.height<10)&&(l.classList.add("debug-outline"),console.warn("[moko] Theme FAB mounted but appears too small — check CSS collisions."))},50)}}function s(){e.scrollY>50?t.body.classList.add("scrolled"):t.body.classList.remove("scrolled")}function u(){var a=t.getElementById("back-top");a&&a.addEventListener("click",function(t){t.preventDefault(),e.scrollTo({top:0,behavior:"smooth"})})}function m(){!function(){var e=c()||i();o(e);var a=function(){c()||o(i())};"function"==typeof n.addEventListener?n.addEventListener("change",a):"function"==typeof n.addListener&&n.addListener(a);var r=t.getElementById("themeSwitch"),l=t.getElementById("themeAuto");r&&(r.checked="dark"===e,r.addEventListener("change",function(){o(r.checked?"dark":"light")})),l&&l.addEventListener("click",function(){d(),o(i())})}(),"1"===t.body.getAttribute("data-theme-fab-enabled")&&l(),s(),e.addEventListener("scroll",s),t.querySelector(".drawer-toggle-left")||t.querySelector(".drawer-toggle-right"),u()}"loading"===t.readyState?t.addEventListener("DOMContentLoaded",m):m()}(window,document);
|
|
||||||
@@ -93,6 +93,7 @@ class Tpl_MokoonyxInstallerScript implements InstallerScriptInterface
|
|||||||
$this->migrateFromCassiopeia();
|
$this->migrateFromCassiopeia();
|
||||||
$this->replaceCassiopeiaReferences();
|
$this->replaceCassiopeiaReferences();
|
||||||
$this->clearFaviconStamp();
|
$this->clearFaviconStamp();
|
||||||
|
$this->lockExtension();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -378,6 +379,29 @@ 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.17</version>
|
<version>01.00.18</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,12 +373,6 @@
|
|||||||
<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,6 +210,7 @@ color-scheme: dark;
|
|||||||
|
|
||||||
|
|
||||||
/* ===== HEADER BACKGROUND ===== */
|
/* ===== HEADER BACKGROUND ===== */
|
||||||
|
--header-background-color: #1a1f2b;
|
||||||
--header-background-image: url('../../../../../../media/templates/site/mokoonyx/images/bg.svg');
|
--header-background-image: url('../../../../../../media/templates/site/mokoonyx/images/bg.svg');
|
||||||
--header-background-attachment: fixed;
|
--header-background-attachment: fixed;
|
||||||
--header-background-repeat: repeat;
|
--header-background-repeat: repeat;
|
||||||
|
|||||||
@@ -209,6 +209,7 @@ color-scheme: light;
|
|||||||
|
|
||||||
|
|
||||||
/* ===== HEADER BACKGROUND ===== */
|
/* ===== HEADER BACKGROUND ===== */
|
||||||
|
--header-background-color: #adadad;
|
||||||
--header-background-image: url('../../../../../../media/templates/site/mokoonyx/images/bg.svg');
|
--header-background-image: url('../../../../../../media/templates/site/mokoonyx/images/bg.svg');
|
||||||
--header-background-attachment: fixed;
|
--header-background-attachment: fixed;
|
||||||
--header-background-repeat: repeat;
|
--header-background-repeat: repeat;
|
||||||
|
|||||||
@@ -1,836 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
|
|
||||||
This file is part of a Moko Consulting project.
|
|
||||||
|
|
||||||
SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
-->
|
|
||||||
<html lang="en" data-bs-theme="light">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>MokoOnyx — Theme Test Sheet</title>
|
|
||||||
<!-- Load the template CSS -->
|
|
||||||
<link rel="stylesheet" href="../media/css/template.css">
|
|
||||||
<!-- Load the light custom palette (swap to dark.custom.css for dark mode testing) -->
|
|
||||||
<link rel="stylesheet" href="light.custom.css">
|
|
||||||
<style>
|
|
||||||
/* ── Test Page Layout ── */
|
|
||||||
body { font-family: var(--body-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif); color: var(--body-color, #22262a); background: var(--body-bg, #fff); margin: 0; padding: 0; }
|
|
||||||
.test-container { max-width: 1200px; margin: 0 auto; padding: 1rem 1.5rem; }
|
|
||||||
h1, h2, h3, h4, h5, h6 { color: var(--heading-color, inherit); }
|
|
||||||
h1 { font-size: 2.25rem; margin-bottom: .25rem; }
|
|
||||||
h2 { font-size: 1.75rem; margin-top: 2.5rem; border-bottom: 2px solid var(--border-color, #dfe3e7); padding-bottom: .5rem; }
|
|
||||||
h3 { font-size: 1.25rem; margin-top: 1.5rem; }
|
|
||||||
p.lead { font-size: 1.15rem; color: var(--muted-color, #6d757e); }
|
|
||||||
hr { border: 0; border-top: 1px solid var(--border-color, #dfe3e7); margin: 2rem 0; }
|
|
||||||
a { color: var(--link-color, #224faa); text-decoration: var(--link-decoration, underline); }
|
|
||||||
a:hover { color: var(--link-hover-color, #424077); }
|
|
||||||
code { color: var(--code-color, #e93f8e); background: var(--secondary-bg, #eaedf0); padding: .15em .4em; border-radius: .2rem; font-size: .875em; }
|
|
||||||
pre { background: var(--secondary-bg, #eaedf0); color: var(--body-color); padding: 1rem; border-radius: var(--border-radius, .25rem); overflow-x: auto; font-size: .875rem; }
|
|
||||||
|
|
||||||
/* ── Swatch Grid ── */
|
|
||||||
.swatch-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: .75rem; margin: 1rem 0; }
|
|
||||||
.swatch { border-radius: var(--border-radius, .25rem); border: 1px solid var(--border-color, #dfe3e7); overflow: hidden; }
|
|
||||||
.swatch-color { height: 60px; }
|
|
||||||
.swatch-label { padding: .4rem .6rem; font-size: .75rem; background: var(--body-bg, #fff); }
|
|
||||||
.swatch-label code { font-size: .7rem; }
|
|
||||||
|
|
||||||
/* ── Variable Table ── */
|
|
||||||
.var-table { width: 100%; border-collapse: collapse; margin: 1rem 0; font-size: .875rem; }
|
|
||||||
.var-table th, .var-table td { padding: .5rem .75rem; border: 1px solid var(--border-color, #dfe3e7); text-align: left; }
|
|
||||||
.var-table th { background: var(--secondary-bg, #eaedf0); font-weight: 600; }
|
|
||||||
.var-table tr:nth-child(even) td { background: var(--tertiary-bg, #f9fafb); }
|
|
||||||
|
|
||||||
/* ── Flex row helper ── */
|
|
||||||
.row { display: flex; flex-wrap: wrap; gap: 1rem; }
|
|
||||||
.col { flex: 1; min-width: 200px; }
|
|
||||||
|
|
||||||
/* ── Theme Toggle ── */
|
|
||||||
.theme-toggle { position: fixed; top: 1rem; right: 1.5rem; z-index: 1000; }
|
|
||||||
.theme-toggle button { padding: .5rem 1rem; border: 1px solid var(--border-color); border-radius: var(--border-radius); background: var(--body-bg); color: var(--body-color); cursor: pointer; font-size: .875rem; }
|
|
||||||
|
|
||||||
/* ── Block Color Demo ── */
|
|
||||||
.block-demo { display: flex; gap: .75rem; flex-wrap: wrap; margin: 1rem 0; }
|
|
||||||
.block-demo .card { flex: 1; min-width: 180px; padding: 1.25rem; border-radius: var(--border-radius, .25rem); border: 1px solid var(--border-color, #dfe3e7); }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div class="theme-toggle">
|
|
||||||
<button onclick="toggleTheme()">Toggle Light / Dark</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="test-container">
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
HEADER
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h1>MokoOnyx Theme Test Sheet</h1>
|
|
||||||
<p class="lead">Visual reference for CSS variables, Bootstrap components, hero variants, and block color system. Toggle light/dark mode with the button in the top-right corner.</p>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
1. BRAND COLORS
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>1. Brand & Theme Colors</h2>
|
|
||||||
<div class="swatch-grid">
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--color-primary)"></div>
|
|
||||||
<div class="swatch-label"><code>--color-primary</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--accent-color-primary)"></div>
|
|
||||||
<div class="swatch-label"><code>--accent-color-primary</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--accent-color-secondary)"></div>
|
|
||||||
<div class="swatch-label"><code>--accent-color-secondary</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--body-bg)"></div>
|
|
||||||
<div class="swatch-label"><code>--body-bg</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--body-color)"></div>
|
|
||||||
<div class="swatch-label"><code>--body-color</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--secondary-bg)"></div>
|
|
||||||
<div class="swatch-label"><code>--secondary-bg</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--tertiary-bg)"></div>
|
|
||||||
<div class="swatch-label"><code>--tertiary-bg</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--border-color)"></div>
|
|
||||||
<div class="swatch-label"><code>--border-color</code></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
2. BOOTSTRAP PALETTE
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>2. Bootstrap Color Palette</h2>
|
|
||||||
<div class="swatch-grid">
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--primary)"></div>
|
|
||||||
<div class="swatch-label"><code>--primary</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--secondary)"></div>
|
|
||||||
<div class="swatch-label"><code>--secondary</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--success)"></div>
|
|
||||||
<div class="swatch-label"><code>--success</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--info)"></div>
|
|
||||||
<div class="swatch-label"><code>--info</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--warning)"></div>
|
|
||||||
<div class="swatch-label"><code>--warning</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--danger)"></div>
|
|
||||||
<div class="swatch-label"><code>--danger</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--light)"></div>
|
|
||||||
<div class="swatch-label"><code>--light</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--dark)"></div>
|
|
||||||
<div class="swatch-label"><code>--dark</code></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
3. GRAY SCALE
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>3. Gray Scale</h2>
|
|
||||||
<div class="swatch-grid">
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--gray-100)"></div>
|
|
||||||
<div class="swatch-label"><code>--gray-100</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--gray-200)"></div>
|
|
||||||
<div class="swatch-label"><code>--gray-200</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--gray-300)"></div>
|
|
||||||
<div class="swatch-label"><code>--gray-300</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--gray-400)"></div>
|
|
||||||
<div class="swatch-label"><code>--gray-400</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--gray-500)"></div>
|
|
||||||
<div class="swatch-label"><code>--gray-500</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--gray-600)"></div>
|
|
||||||
<div class="swatch-label"><code>--gray-600</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--gray-700)"></div>
|
|
||||||
<div class="swatch-label"><code>--gray-700</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--gray-800)"></div>
|
|
||||||
<div class="swatch-label"><code>--gray-800</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--gray-900)"></div>
|
|
||||||
<div class="swatch-label"><code>--gray-900</code></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
4. STANDARD COLORS
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>4. Standard Colors</h2>
|
|
||||||
<div class="swatch-grid">
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--blue)"></div>
|
|
||||||
<div class="swatch-label"><code>--blue</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--indigo)"></div>
|
|
||||||
<div class="swatch-label"><code>--indigo</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--purple)"></div>
|
|
||||||
<div class="swatch-label"><code>--purple</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--pink)"></div>
|
|
||||||
<div class="swatch-label"><code>--pink</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--red)"></div>
|
|
||||||
<div class="swatch-label"><code>--red</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--orange)"></div>
|
|
||||||
<div class="swatch-label"><code>--orange</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--yellow)"></div>
|
|
||||||
<div class="swatch-label"><code>--yellow</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--green)"></div>
|
|
||||||
<div class="swatch-label"><code>--green</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--teal)"></div>
|
|
||||||
<div class="swatch-label"><code>--teal</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--cyan)"></div>
|
|
||||||
<div class="swatch-label"><code>--cyan</code></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
5. TYPOGRAPHY
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>5. Typography</h2>
|
|
||||||
<div>
|
|
||||||
<h1>Heading 1 <small style="color: var(--muted-color); font-size: .5em;">h1</small></h1>
|
|
||||||
<h2 style="border: none; padding: 0; margin-top: .5rem;">Heading 2 <small style="color: var(--muted-color); font-size: .5em;">h2</small></h2>
|
|
||||||
<h3>Heading 3 <small style="color: var(--muted-color); font-size: .6em;">h3</small></h3>
|
|
||||||
<h4>Heading 4 <small style="color: var(--muted-color); font-size: .6em;">h4</small></h4>
|
|
||||||
<h5>Heading 5 <small style="color: var(--muted-color); font-size: .6em;">h5</small></h5>
|
|
||||||
<h6>Heading 6 <small style="color: var(--muted-color); font-size: .6em;">h6</small></h6>
|
|
||||||
</div>
|
|
||||||
<p>This is regular body text using <code>--body-color</code> on <code>--body-bg</code>. Font family: <code>--body-font-family</code>. Size: <code>--body-font-size</code> (1rem).</p>
|
|
||||||
<p><strong>Bold text.</strong> <em>Italic text.</em> <a href="#">This is a link</a>. <code>Inline code</code>. <mark style="background: var(--highlight-bg); color: var(--highlight-color);">Highlighted text</mark>.</p>
|
|
||||||
<p class="lead">This is lead text styled with <code>--muted-color</code>.</p>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
6. LINKS
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>6. Link Colors</h2>
|
|
||||||
<table class="var-table">
|
|
||||||
<tr><th>Variable</th><th>Preview</th></tr>
|
|
||||||
<tr><td><code>--link-color</code></td><td><a href="#" style="color: var(--link-color)">Sample link</a></td></tr>
|
|
||||||
<tr><td><code>--link-hover-color</code></td><td><span style="color: var(--link-hover-color); text-decoration: underline; cursor: pointer;">Hover state</span></td></tr>
|
|
||||||
<tr><td><code>--color-link</code></td><td><span style="color: var(--color-link)">color-link value</span></td></tr>
|
|
||||||
<tr><td><code>--color-hover</code></td><td><span style="color: var(--color-hover)">color-hover value</span></td></tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
7. BUTTONS (Bootstrap-style)
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>7. Buttons</h2>
|
|
||||||
<div style="display: flex; flex-wrap: wrap; gap: .5rem; margin: 1rem 0;">
|
|
||||||
<button class="btn btn-primary">Primary</button>
|
|
||||||
<button class="btn btn-secondary">Secondary</button>
|
|
||||||
<button class="btn btn-success">Success</button>
|
|
||||||
<button class="btn btn-danger">Danger</button>
|
|
||||||
<button class="btn btn-warning">Warning</button>
|
|
||||||
<button class="btn btn-info">Info</button>
|
|
||||||
<button class="btn btn-light">Light</button>
|
|
||||||
<button class="btn btn-dark">Dark</button>
|
|
||||||
</div>
|
|
||||||
<div style="display: flex; flex-wrap: wrap; gap: .5rem; margin: 1rem 0;">
|
|
||||||
<button class="btn btn-outline-primary">Outline Primary</button>
|
|
||||||
<button class="btn btn-outline-secondary">Outline Secondary</button>
|
|
||||||
<button class="btn btn-outline-success">Outline Success</button>
|
|
||||||
<button class="btn btn-outline-danger">Outline Danger</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
8. CARDS
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>8. Cards</h2>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<div class="card" style="background: var(--card-bg); border: var(--card-border-width) solid var(--card-border-color); border-radius: var(--card-border-radius); padding: 0;">
|
|
||||||
<div style="padding: var(--card-cap-padding-y) var(--card-cap-padding-x); background: var(--card-cap-bg); color: var(--card-cap-color); border-bottom: 1px solid var(--card-border-color); font-weight: 600;">Card Header</div>
|
|
||||||
<div style="padding: var(--card-spacer-y) var(--card-spacer-x); color: var(--card-color);">
|
|
||||||
<h5 style="margin-top: 0;">Card Title</h5>
|
|
||||||
<p style="margin-bottom: .5rem;">Card body using <code>--card-bg</code>, <code>--card-color</code>, and <code>--card-border-color</code>.</p>
|
|
||||||
<button class="btn btn-primary" style="font-size: .875rem;">Action</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
<div class="card" style="background: var(--card-bg); border: var(--card-border-width) solid var(--card-border-color); border-radius: var(--card-border-radius); padding: var(--card-spacer-y) var(--card-spacer-x); color: var(--card-color);">
|
|
||||||
<h5 style="margin-top: 0;">Simple Card</h5>
|
|
||||||
<p>No header, just body content. Uses the same card variables.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
9. FORMS
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>9. Form Elements</h2>
|
|
||||||
<div style="max-width: 480px;">
|
|
||||||
<div style="margin-bottom: 1rem;">
|
|
||||||
<label style="display: block; margin-bottom: .25rem; font-weight: 500;">Text Input</label>
|
|
||||||
<input type="text" placeholder="Placeholder text" style="width: 100%; padding: .375rem .75rem; border: 1px solid var(--input-border-color, #ced4da); border-radius: var(--border-radius); background: var(--input-bg, #fff); color: var(--input-color, #22262a); font-size: 1rem;">
|
|
||||||
</div>
|
|
||||||
<div style="margin-bottom: 1rem;">
|
|
||||||
<label style="display: block; margin-bottom: .25rem; font-weight: 500;">Select</label>
|
|
||||||
<select style="width: 100%; padding: .375rem .75rem; border: 1px solid var(--input-border-color, #ced4da); border-radius: var(--border-radius); background: var(--input-bg, #fff); color: var(--input-color, #22262a); font-size: 1rem;">
|
|
||||||
<option>Option 1</option>
|
|
||||||
<option>Option 2</option>
|
|
||||||
<option>Option 3</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div style="margin-bottom: 1rem;">
|
|
||||||
<label style="display: block; margin-bottom: .25rem; font-weight: 500;">Textarea</label>
|
|
||||||
<textarea rows="3" style="width: 100%; padding: .375rem .75rem; border: 1px solid var(--input-border-color, #ced4da); border-radius: var(--border-radius); background: var(--input-bg, #fff); color: var(--input-color, #22262a); font-size: 1rem;">Sample text content</textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
10. ALERTS (Bootstrap-style)
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>10. Alerts</h2>
|
|
||||||
<div style="padding: .75rem 1rem; margin-bottom: .75rem; border-radius: var(--border-radius); background: var(--primary-bg-subtle); color: var(--primary-text-emphasis); border: 1px solid var(--primary-border-subtle);">
|
|
||||||
<strong>Primary alert.</strong> Uses <code>--primary-bg-subtle</code> and <code>--primary-text-emphasis</code>.
|
|
||||||
</div>
|
|
||||||
<div style="padding: .75rem 1rem; margin-bottom: .75rem; border-radius: var(--border-radius); background: var(--success-bg-subtle); color: var(--success-text-emphasis); border: 1px solid var(--success-border-subtle);">
|
|
||||||
<strong>Success alert.</strong> Uses <code>--success-bg-subtle</code> and <code>--success-text-emphasis</code>.
|
|
||||||
</div>
|
|
||||||
<div style="padding: .75rem 1rem; margin-bottom: .75rem; border-radius: var(--border-radius); background: var(--warning-bg-subtle); color: var(--warning-text-emphasis); border: 1px solid var(--warning-border-subtle);">
|
|
||||||
<strong>Warning alert.</strong> Uses <code>--warning-bg-subtle</code> and <code>--warning-text-emphasis</code>.
|
|
||||||
</div>
|
|
||||||
<div style="padding: .75rem 1rem; margin-bottom: .75rem; border-radius: var(--border-radius); background: var(--danger-bg-subtle); color: var(--danger-text-emphasis); border: 1px solid var(--danger-border-subtle);">
|
|
||||||
<strong>Danger alert.</strong> Uses <code>--danger-bg-subtle</code> and <code>--danger-text-emphasis</code>.
|
|
||||||
</div>
|
|
||||||
<div style="padding: .75rem 1rem; margin-bottom: .75rem; border-radius: var(--border-radius); background: var(--info-bg-subtle); color: var(--info-text-emphasis); border: 1px solid var(--info-border-subtle);">
|
|
||||||
<strong>Info alert.</strong> Uses <code>--info-bg-subtle</code> and <code>--info-text-emphasis</code>.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
11. BORDERS & SHADOWS
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>11. Borders & Shadows</h2>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col" style="padding: 1.5rem; border: var(--border-width) var(--border-style) var(--border-color); border-radius: var(--border-radius); margin-bottom: 1rem;">
|
|
||||||
Default border: <code>--border-width</code> / <code>--border-color</code> / <code>--border-radius</code>
|
|
||||||
</div>
|
|
||||||
<div class="col" style="padding: 1.5rem; border-radius: var(--border-radius); box-shadow: var(--box-shadow-sm); margin-bottom: 1rem;">
|
|
||||||
<code>--box-shadow-sm</code>
|
|
||||||
</div>
|
|
||||||
<div class="col" style="padding: 1.5rem; border-radius: var(--border-radius); box-shadow: var(--box-shadow); margin-bottom: 1rem;">
|
|
||||||
<code>--box-shadow</code>
|
|
||||||
</div>
|
|
||||||
<div class="col" style="padding: 1.5rem; border-radius: var(--border-radius); box-shadow: var(--box-shadow-lg); margin-bottom: 1rem;">
|
|
||||||
<code>--box-shadow-lg</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
12. NAVIGATION COLORS
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>12. Navigation Colors</h2>
|
|
||||||
<div class="swatch-grid">
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--nav-bg-color)"></div>
|
|
||||||
<div class="swatch-label"><code>--nav-bg-color</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--nav-text-color); border: 1px solid var(--border-color)"></div>
|
|
||||||
<div class="swatch-label"><code>--nav-text-color</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--mainmenu-nav-link-color); border: 1px solid var(--border-color)"></div>
|
|
||||||
<div class="swatch-label"><code>--mainmenu-nav-link-color</code></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
13. CONTAINER BACKGROUNDS
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>13. Container Background Variables</h2>
|
|
||||||
<table class="var-table">
|
|
||||||
<tr><th>Container</th><th>BG Color</th><th>BG Image</th><th>Border</th></tr>
|
|
||||||
<tr><td>below-topbar</td><td><code>--container-below-topbar-bg-color</code></td><td><code>--container-below-topbar-bg-image</code></td><td><code>--container-below-topbar-border</code></td></tr>
|
|
||||||
<tr><td>top-a</td><td><code>--container-top-a-bg-color</code></td><td><code>--container-top-a-bg-image</code></td><td><code>--container-top-a-border</code></td></tr>
|
|
||||||
<tr><td>top-b</td><td><code>--container-top-b-bg-color</code></td><td><code>--container-top-b-bg-image</code></td><td><code>--container-top-b-border</code></td></tr>
|
|
||||||
<tr><td>bottom-a</td><td><code>--container-bottom-a-bg-color</code></td><td><code>--container-bottom-a-bg-image</code></td><td><code>--container-bottom-a-border</code></td></tr>
|
|
||||||
<tr><td>bottom-b</td><td><code>--container-bottom-b-bg-color</code></td><td><code>--container-bottom-b-bg-image</code></td><td><code>--container-bottom-b-border</code></td></tr>
|
|
||||||
<tr><td>sidebar</td><td><code>--container-sidebar-bg-color</code></td><td><code>--container-sidebar-bg-image</code></td><td><code>--container-sidebar-border</code></td></tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
14. HERO VARIANTS (NEW)
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>14. Hero Variants <span style="font-size: .65em; color: var(--success); font-weight: normal;">NEW</span></h2>
|
|
||||||
<p>The <code>.hero#primary</code> and <code>.hero#secondary</code> variants use CSS variables for background color, overlay gradient, and text color. Each adapts automatically with the active theme.</p>
|
|
||||||
|
|
||||||
<h3>Primary Variant — <code>.hero#primary</code></h3>
|
|
||||||
<div class="hero" id="primary" style="background-image: url('data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%22400%22 height=%22200%22><rect fill=%22%23a3cde2%22 width=%22400%22 height=%22200%22/><text x=%2250%25%22 y=%2250%25%22 text-anchor=%22middle%22 dy=%22.3em%22 font-family=%22sans-serif%22 font-size=%2216%22 fill=%22%23112855%22>Hero Background Image Area</text></svg>'); padding: 0;">
|
|
||||||
<div style="padding: 3rem 2rem; text-align: center;">
|
|
||||||
<h2 style="border: none; padding: 0; margin: 0 0 .5rem 0; font-size: 2rem;">Primary Hero</h2>
|
|
||||||
<p style="margin: 0; font-size: 1.1rem;">Homepage & main landing pages — sky blue tint, softer overlay</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>Secondary Variant — <code>.hero#secondary</code></h3>
|
|
||||||
<div class="hero" id="secondary" style="background-image: url('data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%22400%22 height=%22200%22><rect fill=%22%23112855%22 width=%22400%22 height=%22200%22/><text x=%2250%25%22 y=%2250%25%22 text-anchor=%22middle%22 dy=%22.3em%22 font-family=%22sans-serif%22 font-size=%2216%22 fill=%22%23f1f5f9%22>Hero Background Image Area</text></svg>'); padding: 0;">
|
|
||||||
<div style="padding: 3rem 2rem; text-align: center;">
|
|
||||||
<h2 style="border: none; padding: 0; margin: 0 0 .5rem 0; font-size: 2rem;">Secondary Hero</h2>
|
|
||||||
<p style="margin: 0; font-size: 1.1rem;">Inner pages, events, about — navy overlay, lighter text</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>Hero Variable Reference</h3>
|
|
||||||
<table class="var-table">
|
|
||||||
<tr><th>Variable</th><th>Variant</th><th>Purpose</th></tr>
|
|
||||||
<tr><td><code>--hero-primary-bg-color</code></td><td>Primary</td><td>Fallback background color</td></tr>
|
|
||||||
<tr><td><code>--hero-primary-overlay</code></td><td>Primary</td><td>Gradient overlay tint</td></tr>
|
|
||||||
<tr><td><code>--hero-primary-color</code></td><td>Primary</td><td>Text color</td></tr>
|
|
||||||
<tr><td><code>--hero-secondary-bg-color</code></td><td>Secondary</td><td>Fallback background color</td></tr>
|
|
||||||
<tr><td><code>--hero-secondary-overlay</code></td><td>Secondary</td><td>Gradient overlay tint</td></tr>
|
|
||||||
<tr><td><code>--hero-secondary-color</code></td><td>Secondary</td><td>Text color</td></tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
15. BLOCK COLOR SYSTEM (NEW)
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>15. Block Color System <span style="font-size: .65em; color: var(--success); font-weight: normal;">NEW</span></h2>
|
|
||||||
<p>Modules in <code>top-a</code>, <code>top-b</code>, <code>bottom-a</code>, and <code>bottom-b</code> positions automatically receive brand colors based on their order. No classes needed — <code>:nth-child()</code> handles assignment.</p>
|
|
||||||
|
|
||||||
<h3>Slot Palette Preview</h3>
|
|
||||||
<div class="block-demo">
|
|
||||||
<div class="card" style="background-color: var(--block-color-1); color: var(--block-text-1);">
|
|
||||||
<strong>Slot 1</strong><br>
|
|
||||||
<code style="color: inherit; background: rgba(255,255,255,.15);">--block-color-1</code>
|
|
||||||
</div>
|
|
||||||
<div class="card" style="background-color: var(--block-color-2); color: var(--block-text-2);">
|
|
||||||
<strong>Slot 2</strong><br>
|
|
||||||
<code style="color: inherit; background: rgba(255,255,255,.15);">--block-color-2</code>
|
|
||||||
</div>
|
|
||||||
<div class="card" style="background-color: var(--block-color-3); color: var(--block-text-3);">
|
|
||||||
<strong>Slot 3</strong><br>
|
|
||||||
<code style="color: inherit; background: rgba(255,255,255,.15);">--block-color-3</code>
|
|
||||||
</div>
|
|
||||||
<div class="card" style="background-color: var(--block-color-4); color: var(--block-text-4);">
|
|
||||||
<strong>Slot 4</strong><br>
|
|
||||||
<code style="color: inherit; background: rgba(255,255,255,.15);">--block-color-4</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>Named Override Preview</h3>
|
|
||||||
<div class="block-demo">
|
|
||||||
<div class="card" style="background-color: var(--block-highlight-bg); color: var(--block-highlight-text);">
|
|
||||||
<strong>#block-highlight</strong><br>
|
|
||||||
<code style="color: inherit; background: rgba(255,255,255,.15);">--block-highlight-bg</code>
|
|
||||||
</div>
|
|
||||||
<div class="card" style="background-color: var(--block-cta-bg); color: var(--block-cta-text);">
|
|
||||||
<strong>#block-cta</strong><br>
|
|
||||||
<code style="color: inherit; background: rgba(255,255,255,.15);">--block-cta-bg</code>
|
|
||||||
</div>
|
|
||||||
<div class="card" style="background-color: var(--block-alert-bg); color: var(--block-alert-text);">
|
|
||||||
<strong>#block-alert</strong><br>
|
|
||||||
<code style="color: inherit; background: rgba(255,255,255,.15);">--block-alert-bg</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>Block Variable Reference</h3>
|
|
||||||
<table class="var-table">
|
|
||||||
<tr><th>Variable</th><th>Purpose</th></tr>
|
|
||||||
<tr><td><code>--block-color-1</code> / <code>--block-text-1</code></td><td>1st module in position (automatic)</td></tr>
|
|
||||||
<tr><td><code>--block-color-2</code> / <code>--block-text-2</code></td><td>2nd module in position (automatic)</td></tr>
|
|
||||||
<tr><td><code>--block-color-3</code> / <code>--block-text-3</code></td><td>3rd module in position (automatic)</td></tr>
|
|
||||||
<tr><td><code>--block-color-4</code> / <code>--block-text-4</code></td><td>4th module in position (automatic)</td></tr>
|
|
||||||
<tr><td><code>--block-highlight-bg</code> / <code>--block-highlight-text</code></td><td>Named override for <code>#block-highlight</code></td></tr>
|
|
||||||
<tr><td><code>--block-cta-bg</code> / <code>--block-cta-text</code></td><td>Named override for <code>#block-cta</code></td></tr>
|
|
||||||
<tr><td><code>--block-alert-bg</code> / <code>--block-alert-text</code></td><td>Named override for <code>#block-alert</code></td></tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<h3>Override Priority</h3>
|
|
||||||
<table class="var-table">
|
|
||||||
<tr><th>Priority</th><th>Method</th><th>How Applied</th></tr>
|
|
||||||
<tr><td>1 (highest)</td><td>Named module ID (<code>#block-highlight</code>)</td><td>ID in module HTML + named variable</td></tr>
|
|
||||||
<tr><td>2 (default)</td><td>Slot color (<code>--block-color-N</code>)</td><td>Automatic by <code>:nth-child()</code> order</td></tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
16. VIRTUEMART COLORS
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>16. VirtueMart Surface Colors</h2>
|
|
||||||
<div class="swatch-grid">
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--vm-surface, #fff)"></div>
|
|
||||||
<div class="swatch-label"><code>--vm-surface</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--vm-surface-2, #f8f9fa)"></div>
|
|
||||||
<div class="swatch-label"><code>--vm-surface-2</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--vm-price-color, #448344)"></div>
|
|
||||||
<div class="swatch-label"><code>--vm-price-color</code></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
17. GABLE COLORS
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>17. Gable Colors</h2>
|
|
||||||
<div class="swatch-grid">
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--gab-blue)"></div>
|
|
||||||
<div class="swatch-label"><code>--gab-blue</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--gab-green)"></div>
|
|
||||||
<div class="swatch-label"><code>--gab-green</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--gab-red)"></div>
|
|
||||||
<div class="swatch-label"><code>--gab-red</code></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--gab-orange)"></div>
|
|
||||||
<div class="swatch-label"><code>--gab-orange</code></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
18. CODE SAMPLES
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>18. Code & Preformatted Text</h2>
|
|
||||||
<p>Inline code: <code>var(--color-primary)</code></p>
|
|
||||||
<pre>/* Example: overriding block slot 1 in colors_custom.css */
|
|
||||||
--block-color-1: var(--accent-color-primary);
|
|
||||||
--block-text-1: #fff;
|
|
||||||
|
|
||||||
/* Hero variant usage in module HTML */
|
|
||||||
<div class="hero" id="primary"
|
|
||||||
style="background-image:url('/images/hero/main.jpg')">
|
|
||||||
<div class="col-12 py-5 px-4 text-center">
|
|
||||||
...content...
|
|
||||||
</div>
|
|
||||||
</div></pre>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
19. OPACITY UTILITIES
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>19. Opacity Scale</h2>
|
|
||||||
<div style="display: flex; gap: .5rem; flex-wrap: wrap;">
|
|
||||||
<div style="width: 60px; height: 60px; background: var(--color-primary); opacity: var(--opacity-5); border-radius: var(--border-radius); display: flex; align-items: center; justify-content: center; font-size: .7rem;">5%</div>
|
|
||||||
<div style="width: 60px; height: 60px; background: var(--color-primary); opacity: var(--opacity-10); border-radius: var(--border-radius); display: flex; align-items: center; justify-content: center; font-size: .7rem;">10%</div>
|
|
||||||
<div style="width: 60px; height: 60px; background: var(--color-primary); opacity: var(--opacity-15); border-radius: var(--border-radius); display: flex; align-items: center; justify-content: center; font-size: .7rem;">15%</div>
|
|
||||||
<div style="width: 60px; height: 60px; background: var(--color-primary); opacity: var(--opacity-25); border-radius: var(--border-radius); display: flex; align-items: center; justify-content: center; font-size: .7rem; color: #fff;">25%</div>
|
|
||||||
<div style="width: 60px; height: 60px; background: var(--color-primary); opacity: var(--opacity-50); border-radius: var(--border-radius); display: flex; align-items: center; justify-content: center; font-size: .7rem; color: #fff;">50%</div>
|
|
||||||
<div style="width: 60px; height: 60px; background: var(--color-primary); opacity: var(--opacity-75); border-radius: var(--border-radius); display: flex; align-items: center; justify-content: center; font-size: .7rem; color: #fff;">75%</div>
|
|
||||||
<div style="width: 60px; height: 60px; background: var(--color-primary); opacity: var(--opacity-100); border-radius: var(--border-radius); display: flex; align-items: center; justify-content: center; font-size: .7rem; color: #fff;">100%</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════
|
|
||||||
21. BRANDED BOOTSTRAP 5 SHOWCASE
|
|
||||||
══════════════════════════════════════════════ -->
|
|
||||||
<h2>21. Branded Bootstrap 5 Showcase</h2>
|
|
||||||
<p>Comprehensive component demos using MokoOnyx's brand variables. Mirrors the live Joomla article at <code>/style/branded-bootstrap5</code>.</p>
|
|
||||||
|
|
||||||
<!-- BRAND HEADER (gradient + border via variables) -->
|
|
||||||
<div class="text-center p-4 mb-4" style="background: var(--header-background-image); background-position: center; background-attachment: fixed; background-repeat: repeat; background-size: auto; border-bottom: var(--border, 5px) solid var(--accent-color-primary); color: var(--color-primary);">
|
|
||||||
<h3 class="mb-1" style="border: none; padding: 0; margin: 0;">Brand + Bootstrap Showcase</h3>
|
|
||||||
<p class="lead mb-0" style="color: inherit;">Comprehensive components with toggleable code samples</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- NAV SAMPLE (brand colors) -->
|
|
||||||
<nav class="d-flex align-items-center gap-3 px-3 py-2 mb-4" style="background: var(--nav-bg-color); border-radius: var(--border-radius, .25rem);">
|
|
||||||
<span class="fw-bold" style="color: var(--nav-text-color);">Brand Nav</span>
|
|
||||||
<a href="#" class="text-decoration-none" style="color: var(--mainmenu-nav-link-color);">Home</a>
|
|
||||||
<a href="#" class="text-decoration-none" style="color: var(--mainmenu-nav-link-color);">About</a>
|
|
||||||
<a href="#" class="text-decoration-none ms-auto fw-semibold" style="color: var(--accent-color-secondary);">Contact</a>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<!-- BREADCRUMB -->
|
|
||||||
<nav aria-label="Breadcrumbs" style="margin-bottom: 1.5rem;">
|
|
||||||
<ol class="breadcrumb px-3 py-2" style="background: var(--secondary-bg, #eaedf0); border-radius: var(--border-radius, .25rem); margin: 0; list-style: none; display: flex; flex-wrap: wrap; padding: .5rem 1rem; font-size: .875rem;">
|
|
||||||
<li class="breadcrumb-item"><a href="#">Home</a></li>
|
|
||||||
<li class="breadcrumb-item"><a href="#">Style</a></li>
|
|
||||||
<li class="breadcrumb-item active" style="color: var(--muted-color);">Branded Bootstrap5</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<!-- TYPOGRAPHY SECTION -->
|
|
||||||
<h3>Typography</h3>
|
|
||||||
<div class="row" style="margin-bottom: 2rem;">
|
|
||||||
<div class="col">
|
|
||||||
<h1 style="border: none; padding: 0; margin: .5rem 0;">H1 Heading</h1>
|
|
||||||
<h2 style="border: none; padding: 0; margin: .5rem 0;">H2 Heading</h2>
|
|
||||||
<h3 style="margin: .5rem 0;">H3 Heading</h3>
|
|
||||||
<h4>H4 Heading</h4>
|
|
||||||
<h5>H5 Heading</h5>
|
|
||||||
<h6>H6 Heading</h6>
|
|
||||||
<p class="lead">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.</p>
|
|
||||||
<p>Curabitur <strong>ullamcorper</strong> nec <em>nisi</em> a <a href="#">themed link</a>. Nulla vitae <code><section></code> purus.</p>
|
|
||||||
<blockquote style="border-left: var(--border, 5px) solid var(--accent-color-primary); padding-left: 1rem; margin: 1rem 0;">
|
|
||||||
<p style="margin-bottom: .25rem;">"Design is intelligence made visible."</p>
|
|
||||||
<footer style="color: var(--muted-color); font-size: .875rem;">— Alina Wheeler</footer>
|
|
||||||
</blockquote>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- BUTTONS & GROUPS -->
|
|
||||||
<h3>Buttons & Button Groups</h3>
|
|
||||||
<div style="display: flex; flex-wrap: wrap; gap: .5rem; margin-bottom: 1rem;">
|
|
||||||
<button class="btn btn-primary">Primary</button>
|
|
||||||
<button class="btn btn-secondary">Secondary</button>
|
|
||||||
<button class="btn btn-success">Success</button>
|
|
||||||
<button class="btn btn-info">Info</button>
|
|
||||||
<button class="btn btn-warning">Warning</button>
|
|
||||||
<button class="btn btn-danger">Danger</button>
|
|
||||||
<button class="btn btn-light">Light</button>
|
|
||||||
<button class="btn btn-dark">Dark</button>
|
|
||||||
<button class="btn" style="background: var(--accent-color-primary); color: var(--color-primary); border: none;">Accent</button>
|
|
||||||
</div>
|
|
||||||
<div style="display: inline-flex; margin-bottom: 1.5rem;">
|
|
||||||
<button class="btn btn-outline-primary" style="border-radius: var(--border-radius, .25rem) 0 0 var(--border-radius, .25rem);">Left</button>
|
|
||||||
<button class="btn btn-outline-primary" style="border-radius: 0;">Middle</button>
|
|
||||||
<button class="btn btn-outline-primary" style="border-radius: 0 var(--border-radius, .25rem) var(--border-radius, .25rem) 0;">Right</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- BADGES & ALERTS -->
|
|
||||||
<h3>Badges & Alerts</h3>
|
|
||||||
<div style="display: flex; flex-wrap: wrap; gap: .35rem; margin-bottom: 1rem;">
|
|
||||||
<span style="display: inline-block; padding: .25em .5em; font-size: .75em; font-weight: 700; border-radius: var(--border-radius, .25rem); background: var(--primary); color: #fff;">Primary</span>
|
|
||||||
<span style="display: inline-block; padding: .25em .5em; font-size: .75em; font-weight: 700; border-radius: var(--border-radius, .25rem); background: var(--secondary); color: #fff;">Secondary</span>
|
|
||||||
<span style="display: inline-block; padding: .25em .5em; font-size: .75em; font-weight: 700; border-radius: var(--border-radius, .25rem); background: var(--success); color: #fff;">Success</span>
|
|
||||||
<span style="display: inline-block; padding: .25em .5em; font-size: .75em; font-weight: 700; border-radius: var(--border-radius, .25rem); background: var(--warning); color: var(--body-color);">Warning</span>
|
|
||||||
<span style="display: inline-block; padding: .25em .5em; font-size: .75em; font-weight: 700; border-radius: var(--border-radius, .25rem); background: var(--danger); color: #fff;">Danger</span>
|
|
||||||
<span style="display: inline-block; padding: .25em .5em; font-size: .75em; font-weight: 700; border-radius: var(--border-radius, .25rem); background: var(--accent-color-primary); color: var(--color-primary);">Accent</span>
|
|
||||||
</div>
|
|
||||||
<div style="padding: .75rem 1rem; margin-bottom: .75rem; border-radius: var(--border-radius); background: var(--primary-bg-subtle); color: var(--primary-text-emphasis); border: 1px solid var(--primary-border-subtle);">
|
|
||||||
<strong>Primary:</strong> Vivamus sagittis lacus vel augue.
|
|
||||||
</div>
|
|
||||||
<div style="padding: .75rem 1rem; margin-bottom: .75rem; border-radius: var(--border-radius); background: var(--warning-bg-subtle); color: var(--warning-text-emphasis); border: 1px solid var(--warning-border-subtle);">
|
|
||||||
Cras mattis consectetur purus sit amet fermentum.
|
|
||||||
</div>
|
|
||||||
<div style="padding: .75rem 1rem; margin-bottom: 1.5rem; border-radius: var(--border-radius); background: var(--accent-color-primary); color: var(--color-primary); border: var(--border, 5px) solid var(--accent-color-secondary);">
|
|
||||||
Brand alert — Aenean lacinia bibendum nulla sed consectetur.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- TABLES -->
|
|
||||||
<h3>Tables</h3>
|
|
||||||
<div style="overflow-x: auto; margin-bottom: 1.5rem;">
|
|
||||||
<table class="var-table">
|
|
||||||
<thead><tr style="background: var(--dark, #212529); color: #fff;"><th>#</th><th>Name</th><th>Status</th><th>Notes</th></tr></thead>
|
|
||||||
<tbody>
|
|
||||||
<tr><td>1</td><td>Alpha</td><td><span style="display: inline-block; padding: .15em .4em; font-size: .75em; font-weight: 700; border-radius: var(--border-radius); background: var(--success); color: #fff;">Active</span></td><td>Lorem ipsum dolor sit amet.</td></tr>
|
|
||||||
<tr><td>2</td><td>Beta</td><td><span style="display: inline-block; padding: .15em .4em; font-size: .75em; font-weight: 700; border-radius: var(--border-radius); background: var(--warning); color: var(--body-color);">Pending</span></td><td>Integer posuere erat a ante.</td></tr>
|
|
||||||
<tr><td>3</td><td>Gamma</td><td><span style="display: inline-block; padding: .15em .4em; font-size: .75em; font-weight: 700; border-radius: var(--border-radius); background: var(--danger); color: #fff;">Blocked</span></td><td>Donec id elit non mi porta.</td></tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- FORMS (BRANDED) -->
|
|
||||||
<h3>Branded Forms</h3>
|
|
||||||
<div style="max-width: 600px; margin-bottom: 1.5rem;">
|
|
||||||
<div style="display: flex; flex-wrap: wrap; gap: 1rem; margin-bottom: 1rem;">
|
|
||||||
<div style="flex: 1; min-width: 200px;">
|
|
||||||
<label style="display: block; margin-bottom: .25rem; font-weight: 500;">Email</label>
|
|
||||||
<input type="email" placeholder="name@example.com" style="width: 100%; padding: .375rem .75rem; border: 1px solid var(--input-border-color, #ced4da); border-radius: var(--border-radius); background: var(--input-bg, #fff); color: var(--input-color, #22262a);">
|
|
||||||
</div>
|
|
||||||
<div style="flex: 1; min-width: 200px;">
|
|
||||||
<label style="display: block; margin-bottom: .25rem; font-weight: 500;">Password</label>
|
|
||||||
<input type="password" placeholder="••••••••" style="width: 100%; padding: .375rem .75rem; border: 1px solid var(--input-border-color, #ced4da); border-radius: var(--border-radius); background: var(--input-bg, #fff); color: var(--input-color, #22262a);">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="margin-bottom: 1rem;">
|
|
||||||
<label style="display: block; margin-bottom: .25rem; font-weight: 500;">Input Group</label>
|
|
||||||
<div style="display: flex;">
|
|
||||||
<span style="display: flex; align-items: center; padding: .375rem .75rem; background: var(--secondary-bg, #eaedf0); border: 1px solid var(--input-border-color, #ced4da); border-right: none; border-radius: var(--border-radius) 0 0 var(--border-radius);">@</span>
|
|
||||||
<input type="text" placeholder="username" style="flex: 1; padding: .375rem .75rem; border: 1px solid var(--input-border-color, #ced4da); background: var(--input-bg, #fff); color: var(--input-color, #22262a);">
|
|
||||||
<button class="btn btn-outline-secondary" style="border-radius: 0 var(--border-radius) var(--border-radius) 0;">Search</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-primary">Submit</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- CARDS & LIST GROUPS -->
|
|
||||||
<h3>Branded Cards & List Groups</h3>
|
|
||||||
<div class="row" style="margin-bottom: 1.5rem;">
|
|
||||||
<div class="col">
|
|
||||||
<div style="border: 1px solid var(--card-border-color, var(--border-color)); border-radius: var(--card-border-radius, var(--border-radius)); overflow: hidden; background: var(--card-bg, var(--body-bg));">
|
|
||||||
<div style="padding: .5rem 1rem; background: var(--accent-color-primary); color: var(--color-primary); font-weight: 600;">Featured</div>
|
|
||||||
<div style="padding: var(--card-spacer-y, 1rem) var(--card-spacer-x, 1rem); color: var(--card-color, var(--body-color));">
|
|
||||||
<h5 style="margin-top: 0;">Card title</h5>
|
|
||||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.</p>
|
|
||||||
<button class="btn btn-primary" style="font-size: .875rem;">Go somewhere</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
<div style="border: 1px solid var(--border-color); border-radius: var(--border-radius); overflow: hidden;">
|
|
||||||
<div style="padding: .5rem 1rem; background: var(--primary); color: #fff; font-weight: 500;">Active item</div>
|
|
||||||
<div style="padding: .5rem 1rem; border-bottom: 1px solid var(--border-color);">Second item</div>
|
|
||||||
<div style="padding: .5rem 1rem; display: flex; justify-content: space-between; align-items: center;">With badge <span style="display: inline-block; padding: .15em .5em; font-size: .75em; font-weight: 700; border-radius: 50rem; background: var(--primary); color: #fff;">4</span></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- BREADCRUMB & PAGINATION -->
|
|
||||||
<h3>Breadcrumb & Pagination</h3>
|
|
||||||
<nav aria-label="breadcrumb" style="margin-bottom: 1rem;">
|
|
||||||
<ol style="list-style: none; display: flex; flex-wrap: wrap; padding: .5rem 1rem; margin: 0; background: var(--secondary-bg, #eaedf0); border-radius: var(--border-radius); font-size: .875rem; gap: .5rem;">
|
|
||||||
<li><a href="#">Home</a> /</li>
|
|
||||||
<li><a href="#">Library</a> /</li>
|
|
||||||
<li style="color: var(--muted-color);">Data</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
<nav aria-label="Pagination" style="margin-bottom: 1.5rem;">
|
|
||||||
<ul style="list-style: none; display: flex; padding: 0; margin: 0; gap: 0;">
|
|
||||||
<li><span style="display: block; padding: .375rem .75rem; border: 1px solid var(--border-color); color: var(--muted-color); border-radius: var(--border-radius) 0 0 var(--border-radius);">Previous</span></li>
|
|
||||||
<li><a href="#" style="display: block; padding: .375rem .75rem; border: 1px solid var(--border-color); border-left: none; text-decoration: none;">1</a></li>
|
|
||||||
<li><a href="#" style="display: block; padding: .375rem .75rem; border: 1px solid var(--border-color); border-left: none; text-decoration: none;">2</a></li>
|
|
||||||
<li><a href="#" style="display: block; padding: .375rem .75rem; border: 1px solid var(--border-color); border-left: none; text-decoration: none;">3</a></li>
|
|
||||||
<li><a href="#" style="display: block; padding: .375rem .75rem; border: 1px solid var(--border-color); border-left: none; text-decoration: none; border-radius: 0 var(--border-radius) var(--border-radius) 0;">Next</a></li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<!-- PROGRESS BARS -->
|
|
||||||
<h3>Progress Bars</h3>
|
|
||||||
<div style="margin-bottom: 1.5rem;">
|
|
||||||
<div style="height: 20px; background: var(--secondary-bg, #eaedf0); border-radius: var(--border-radius); margin-bottom: .5rem; overflow: hidden;">
|
|
||||||
<div style="width: 25%; height: 100%; background: var(--accent-color-primary); color: var(--color-primary); display: flex; align-items: center; justify-content: center; font-size: .75rem; font-weight: 600;">25%</div>
|
|
||||||
</div>
|
|
||||||
<div style="height: 20px; background: var(--secondary-bg, #eaedf0); border-radius: var(--border-radius); overflow: hidden;">
|
|
||||||
<div style="width: 65%; height: 100%; background: var(--success); color: #fff; display: flex; align-items: center; justify-content: center; font-size: .75rem; font-weight: 600;">65%</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- CSS VARIABLE SWATCHES (COMPUTED VALUES) -->
|
|
||||||
<h3>CSS Variable Swatches (Computed)</h3>
|
|
||||||
<p style="color: var(--muted-color);">Visual preview of key variables with their resolved values displayed via JavaScript.</p>
|
|
||||||
<div class="swatch-grid" id="computed-swatches">
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--color-primary)"></div>
|
|
||||||
<div class="swatch-label"><code>--color-primary</code><br><small class="computed-val" data-var="--color-primary"></small></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--color-link)"></div>
|
|
||||||
<div class="swatch-label"><code>--color-link</code><br><small class="computed-val" data-var="--color-link"></small></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--color-hover)"></div>
|
|
||||||
<div class="swatch-label"><code>--color-hover</code><br><small class="computed-val" data-var="--color-hover"></small></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--accent-color-primary)"></div>
|
|
||||||
<div class="swatch-label"><code>--accent-color-primary</code><br><small class="computed-val" data-var="--accent-color-primary"></small></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--accent-color-secondary)"></div>
|
|
||||||
<div class="swatch-label"><code>--accent-color-secondary</code><br><small class="computed-val" data-var="--accent-color-secondary"></small></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--nav-bg-color)"></div>
|
|
||||||
<div class="swatch-label"><code>--nav-bg-color</code><br><small class="computed-val" data-var="--nav-bg-color"></small></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--body-bg); border: 1px solid var(--border-color);"></div>
|
|
||||||
<div class="swatch-label"><code>--body-bg</code><br><small class="computed-val" data-var="--body-bg"></small></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--body-color)"></div>
|
|
||||||
<div class="swatch-label"><code>--body-color</code><br><small class="computed-val" data-var="--body-color"></small></div>
|
|
||||||
</div>
|
|
||||||
<div class="swatch">
|
|
||||||
<div class="swatch-color" style="background: var(--border-color)"></div>
|
|
||||||
<div class="swatch-label"><code>--border-color</code><br><small class="computed-val" data-var="--border-color"></small></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
<p style="color: var(--muted-color); font-size: .8rem; text-align: center; margin: 2rem 0;">
|
|
||||||
MokoOnyx Theme Test Sheet — v03.09.02 — © 2026 Moko Consulting
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</div><!-- /.test-container -->
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Populate computed CSS variable values
|
|
||||||
function updateComputedValues() {
|
|
||||||
const root = document.documentElement;
|
|
||||||
const style = getComputedStyle(root);
|
|
||||||
document.querySelectorAll('.computed-val').forEach(el => {
|
|
||||||
const varName = el.dataset.var;
|
|
||||||
if (varName) {
|
|
||||||
el.textContent = style.getPropertyValue(varName).trim() || '(not set)';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
updateComputedValues();
|
|
||||||
|
|
||||||
function toggleTheme() {
|
|
||||||
const html = document.documentElement;
|
|
||||||
const current = html.getAttribute('data-bs-theme');
|
|
||||||
const next = current === 'light' ? 'dark' : 'light';
|
|
||||||
html.setAttribute('data-bs-theme', next);
|
|
||||||
|
|
||||||
// Swap stylesheet
|
|
||||||
const links = document.querySelectorAll('link[rel="stylesheet"]');
|
|
||||||
links.forEach(link => {
|
|
||||||
if (link.href.includes('light.custom.css')) {
|
|
||||||
link.href = link.href.replace('light.custom.css', 'dark.custom.css');
|
|
||||||
} else if (link.href.includes('dark.custom.css')) {
|
|
||||||
link.href = link.href.replace('dark.custom.css', 'light.custom.css');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Refresh computed values after stylesheet loads
|
|
||||||
setTimeout(updateComputedValues, 200);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
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.16</version>
|
<version>01.00.24</version>
|
||||||
<creationDate>2026-04-22</creationDate>
|
<creationDate>2026-04-23</creationDate>
|
||||||
<infourl title='MokoOnyx Dev'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/development</infourl>
|
<infourl title='MokoOnyx Dev'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/development</infourl>
|
||||||
<downloads>
|
<downloads>
|
||||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/development/mokoonyx-01.00.16-dev.zip</downloadurl>
|
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/development/mokoonyx-01.00.24-dev.zip</downloadurl>
|
||||||
</downloads>
|
</downloads>
|
||||||
<sha256>6d0e690995eacd1b2e9193d847abd21369d2eb574406b27515d23f8d5a428c51</sha256>
|
<sha256>a2d215c19b3487eeeae62735a618e183c2e665ff75f9031bdb5df4b3c8f299a6</sha256>
|
||||||
<tags><tag>development</tag></tags>
|
<tags><tag>development</tag></tags>
|
||||||
<maintainer>Moko Consulting</maintainer>
|
<maintainer>Moko Consulting</maintainer>
|
||||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||||
@@ -81,7 +81,6 @@
|
|||||||
<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>
|
||||||
@@ -98,14 +97,13 @@
|
|||||||
<element>mokoonyx</element>
|
<element>mokoonyx</element>
|
||||||
<type>template</type>
|
<type>template</type>
|
||||||
<client>site</client>
|
<client>site</client>
|
||||||
<version>01.00.17</version>
|
<version>01.00.25</version>
|
||||||
<creationDate>2026-04-22</creationDate>
|
<creationDate>2026-04-23</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.17.zip</downloadurl>
|
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/stable/mokoonyx-01.00.25.zip</downloadurl>
|
||||||
<downloadurl type='full' format='zip'>https://github.com/mokoconsulting-tech/MokoOnyx/releases/download/stable/mokoonyx-01.00.17.zip</downloadurl>
|
|
||||||
</downloads>
|
</downloads>
|
||||||
<sha256>f4811005774b99e54513cd2cae052b815ef085e2a04f3a91cd5c0bc9cff7ffc2</sha256>
|
<sha256>d111a027bdd5f1eae1fe973933fa4043be2740d62fea3527fb856275f8fb3be4</sha256>
|
||||||
<tags><tag>stable</tag></tags>
|
<tags><tag>stable</tag></tags>
|
||||||
<maintainer>Moko Consulting</maintainer>
|
<maintainer>Moko Consulting</maintainer>
|
||||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||||
|
|||||||
Reference in New Issue
Block a user