Compare commits
54 Commits
development
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b545e7414c | |||
| 024b00a38e | |||
| 8f9a43f32c | |||
| 225ea65881 | |||
| 4c018fac62 | |||
| 41e48945fd | |||
| 7d28fe522d | |||
| 8780ec7c5f | |||
| 9106cfe254 | |||
| f7d70ae95a | |||
| 5c43cf1f02 | |||
| c8c74c7afe | |||
| 7b68963b67 | |||
| 22c2a99f8b | |||
| c974970118 | |||
| c9a8deee0e | |||
| ea05851d0a | |||
| 1178975be3 | |||
| b283fad8bd | |||
| a792772397 | |||
| 4d1be56bad | |||
| 2dc745c5fa | |||
| 9dda78da7c | |||
| 6ceef765eb | |||
| 23d3528676 | |||
| 249b639c70 | |||
| 5c9db551dc | |||
| 408f2329b3 | |||
| 827025bd17 | |||
| 98da1644be | |||
| db596575a0 | |||
| 3c56dc8814 | |||
| dce712fabd | |||
| 78b0ce9650 | |||
| 500a5be6d7 | |||
| 95a747b1d5 | |||
| bb7e99ad40 | |||
| 6c6b7c888e | |||
| 2a1692d599 | |||
| 6984ac108f | |||
| 3fdbe94830 | |||
| e937dd8d8b | |||
| e7b70f54ed | |||
| b161561571 | |||
| b981cf72e3 | |||
| 9964c7e16c | |||
| ff27e77c37 | |||
| 04ce7dc896 | |||
| f87f904a21 | |||
| fc72d8e90a | |||
| 71d52e432e | |||
| 172303b61f | |||
| bfb4b53da3 | |||
| 9149fa100c |
@@ -10,9 +10,9 @@
|
|||||||
# VERSION: 05.00.00
|
# VERSION: 05.00.00
|
||||||
# BRIEF: Universal build & release � detects platform from manifest.xml
|
# BRIEF: Universal build & release � detects platform from manifest.xml
|
||||||
#
|
#
|
||||||
# +========================================================================+
|
# +=======================================================================+
|
||||||
# | UNIVERSAL BUILD & RELEASE PIPELINE |
|
# | UNIVERSAL BUILD & RELEASE PIPELINE |
|
||||||
# +========================================================================+
|
# +=======================================================================+
|
||||||
# | |
|
# | |
|
||||||
# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. |
|
# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. |
|
||||||
# | |
|
# | |
|
||||||
@@ -21,15 +21,24 @@
|
|||||||
# | dolibarr: mod*.class.php, update.txt, dev version reset |
|
# | dolibarr: mod*.class.php, update.txt, dev version reset |
|
||||||
# | generic: README-only, no update stream |
|
# | generic: README-only, no update stream |
|
||||||
# | |
|
# | |
|
||||||
# +========================================================================+
|
# +=======================================================================+
|
||||||
|
|
||||||
name: "Universal: Build & Release"
|
name: "Universal: Build & Release"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, closed]
|
types: [opened, synchronize, closed]
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
paths-ignore:
|
||||||
|
- '.mokogitea/workflows/**'
|
||||||
|
- '*.md'
|
||||||
|
- 'wiki/**'
|
||||||
|
- '.editorconfig'
|
||||||
|
- '.gitignore'
|
||||||
|
- '.gitattributes'
|
||||||
|
- '.gitmessage'
|
||||||
|
- 'LICENSE'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
action:
|
action:
|
||||||
@@ -51,12 +60,13 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# ── PR Opened → Rename branch to RC and build RC release ─────────────────────
|
# ── PR Opened → Rename branch to RC and build RC release ─────────────────────────
|
||||||
promote-rc:
|
promote-rc:
|
||||||
name: Promote to RC
|
name: Promote to RC
|
||||||
runs-on: release
|
runs-on: release
|
||||||
if: >-
|
if: >-
|
||||||
(github.event.action == 'opened' && github.event.pull_request.merged != true) ||
|
(github.event.action == 'opened' && github.event.pull_request.merged != true) ||
|
||||||
|
(github.event.action == 'synchronize' && github.event.pull_request.merged != true) ||
|
||||||
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
|
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -149,7 +159,7 @@ jobs:
|
|||||||
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
|
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "Branch renamed to rc, minor bump, RC release built" >> $GITHUB_STEP_SUMMARY
|
echo "Branch renamed to rc, minor bump, RC release built" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# ── Merged PR → Build & Release (or promote RC to stable) ────────────────────
|
# ── Merged PR → Build & Release (or promote RC to stable) ─────────────────────────
|
||||||
release:
|
release:
|
||||||
name: Build & Release Pipeline
|
name: Build & Release Pipeline
|
||||||
runs-on: release
|
runs-on: release
|
||||||
@@ -241,11 +251,47 @@ jobs:
|
|||||||
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
|
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
|
||||||
[ -z "$VERSION" ] && VERSION="00.00.00" && echo "skip=true" >> "$GITHUB_OUTPUT"
|
[ -z "$VERSION" ] && VERSION="00.00.00" && echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
|
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||||
|
if [[ "$PLATFORM" == joomla* ]]; then
|
||||||
echo "tag=stable" >> "$GITHUB_OUTPUT"
|
echo "tag=stable" >> "$GITHUB_OUTPUT"
|
||||||
echo "release_tag=stable" >> "$GITHUB_OUTPUT"
|
echo "release_tag=stable" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "release_tag=v${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
echo "branch=main" >> "$GITHUB_OUTPUT"
|
echo "branch=main" >> "$GITHUB_OUTPUT"
|
||||||
echo "Published version: ${VERSION}"
|
echo "Published version: ${VERSION}"
|
||||||
|
|
||||||
|
- name: "Create semver tag for non-Joomla repos"
|
||||||
|
id: semver
|
||||||
|
if: |
|
||||||
|
steps.version.outputs.skip != 'true' &&
|
||||||
|
!startsWith(steps.platform.outputs.platform, 'joomla')
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
|
SEMVER_TAG="v${VERSION}"
|
||||||
|
|
||||||
|
echo "Creating semver tag: ${SEMVER_TAG}"
|
||||||
|
|
||||||
|
# Create the git tag via API
|
||||||
|
HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" \
|
||||||
|
-X POST -H "Authorization: token ${TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${API_BASE}/tags" \
|
||||||
|
-d "{\"tag_name\":\"${SEMVER_TAG}\",\"target\":\"main\",\"message\":\"Release ${VERSION}\"}" 2>/dev/null || echo "000")
|
||||||
|
|
||||||
|
if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then
|
||||||
|
echo "Created semver tag: ${SEMVER_TAG}"
|
||||||
|
elif [ "$HTTP_CODE" = "409" ]; then
|
||||||
|
echo "Semver tag ${SEMVER_TAG} already exists (skipped)"
|
||||||
|
else
|
||||||
|
echo "::warning::Failed to create semver tag ${SEMVER_TAG} (HTTP ${HTTP_CODE})"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "semver_tag=${SEMVER_TAG}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Update release notes and promote changelog
|
- name: Update release notes and promote changelog
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
name: "Publish to Composer"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
- '[0-9]*.[0-9]*.[0-9]*'
|
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
publish:
|
|
||||||
name: Publish Package
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: >-
|
|
||||||
!contains(github.event.head_commit.message, '[skip ci]') &&
|
|
||||||
!contains(github.event.head_commit.message, '[skip publish]')
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup PHP
|
|
||||||
run: |
|
|
||||||
if ! command -v php &> /dev/null; then
|
|
||||||
sudo apt-get update -qq
|
|
||||||
sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: composer install --no-dev --no-interaction --prefer-dist --quiet
|
|
||||||
|
|
||||||
- name: Determine version
|
|
||||||
id: version
|
|
||||||
run: |
|
|
||||||
VERSION=$(php -r "echo json_decode(file_get_contents('composer.json'))->version;")
|
|
||||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Package version: ${VERSION}"
|
|
||||||
|
|
||||||
# Gitea Composer Registry — auto-publishes from tags
|
|
||||||
# The tag push itself registers the package at:
|
|
||||||
# https://git.mokoconsulting.tech/api/packages/MokoConsulting/composer
|
|
||||||
- name: Verify Gitea registry
|
|
||||||
run: |
|
|
||||||
echo "Gitea Composer registry auto-publishes from tags."
|
|
||||||
echo "Package available at: ${GITEA_URL}/api/packages/MokoConsulting/composer"
|
|
||||||
echo "Install: composer require mokoconsulting/mokocli"
|
|
||||||
|
|
||||||
# Packagist — notify of new version
|
|
||||||
- name: Notify Packagist
|
|
||||||
if: secrets.PACKAGIST_TOKEN != ''
|
|
||||||
run: |
|
|
||||||
VERSION="${{ steps.version.outputs.version }}"
|
|
||||||
echo "Notifying Packagist of version ${VERSION}..."
|
|
||||||
curl -sf -X POST \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"repository":{"url":"https://git.mokoconsulting.tech/MokoConsulting/mokocli"}}' \
|
|
||||||
"https://packagist.org/api/update-package?username=mokoconsulting&apiToken=${{ secrets.PACKAGIST_TOKEN }}" \
|
|
||||||
&& echo "Packagist notified" \
|
|
||||||
|| echo "::warning::Packagist notification failed (package may not be registered yet)"
|
|
||||||
|
|
||||||
- name: Summary
|
|
||||||
run: |
|
|
||||||
VERSION="${{ steps.version.outputs.version }}"
|
|
||||||
echo "## Composer Package Published" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Registry | Status |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "|----------|--------|" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Gitea | \`composer require mokoconsulting/mokocli:${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Packagist | \`composer require mokoconsulting/mokocli\` |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
# BRIEF: Build and deploy MokoGitea to dev environment on push to dev branch.
|
||||||
|
# Production deploy (deploy-mokogitea.yml) only succeeds if dev is healthy.
|
||||||
|
|
||||||
|
name: Deploy MokoGitea (Dev)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: deploy-mokogitea-dev
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: git.mokoconsulting.tech
|
||||||
|
IMAGE: mokoconsulting/mokogitea
|
||||||
|
DEPLOY_HOST: git.mokoconsulting.tech
|
||||||
|
DEPLOY_PORT: 2918
|
||||||
|
DEPLOY_USER: mokoconsulting
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy-dev:
|
||||||
|
name: "Build & Deploy to Dev"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout source
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Determine version
|
||||||
|
id: config
|
||||||
|
run: |
|
||||||
|
VERSION=$(git describe --tags --always 2>/dev/null || echo "dev-$(git rev-parse --short HEAD)")
|
||||||
|
echo "tag=${VERSION}-dev" >> $GITHUB_OUTPUT
|
||||||
|
echo "Version: ${VERSION}-dev"
|
||||||
|
|
||||||
|
- name: Write deploy key
|
||||||
|
env:
|
||||||
|
DEPLOY_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
echo "$DEPLOY_KEY" > ~/.ssh/deploy_key
|
||||||
|
chmod 600 ~/.ssh/deploy_key
|
||||||
|
|
||||||
|
- name: Build and deploy to dev via SSH
|
||||||
|
env:
|
||||||
|
REGISTRY_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
TAG: ${{ steps.config.outputs.tag }}
|
||||||
|
run: |
|
||||||
|
HEALTH_FMT='${{ '{{' }}.State.Health.Status${{ '}}' }}'
|
||||||
|
|
||||||
|
ssh -i ~/.ssh/deploy_key -p ${{ env.DEPLOY_PORT }} \
|
||||||
|
-o ConnectTimeout=30 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
||||||
|
-o ServerAliveInterval=30 -o ServerAliveCountMax=10 \
|
||||||
|
${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} bash -s <<DEPLOY_EOF
|
||||||
|
set -e
|
||||||
|
echo 'SSH connected to dev environment'
|
||||||
|
|
||||||
|
echo 'Cleaning Docker build cache...'
|
||||||
|
docker builder prune -af 2>/dev/null || true
|
||||||
|
docker image prune -af 2>/dev/null || true
|
||||||
|
|
||||||
|
echo 'Pulling source...'
|
||||||
|
SOURCE_DIR=/opt/gitea-dev/source
|
||||||
|
if [ ! -d \$SOURCE_DIR/.git ]; then
|
||||||
|
git clone -b dev https://git.mokoconsulting.tech/MokoConsulting/MokoGitea-Fork.git \$SOURCE_DIR
|
||||||
|
fi
|
||||||
|
cd \$SOURCE_DIR
|
||||||
|
git remote set-url origin https://git.mokoconsulting.tech/MokoConsulting/MokoGitea-Fork.git 2>/dev/null || true
|
||||||
|
git fetch origin dev
|
||||||
|
git reset --hard origin/dev
|
||||||
|
|
||||||
|
echo 'Building Docker image...'
|
||||||
|
docker build --no-cache --build-arg GOFLAGS='-p 1' \
|
||||||
|
--tag ${{ env.REGISTRY }}/${{ env.IMAGE }}:\$TAG \
|
||||||
|
-f Dockerfile .
|
||||||
|
|
||||||
|
echo 'Pushing to registry...'
|
||||||
|
echo '\$REGISTRY_TOKEN' | docker login ${{ env.REGISTRY }} -u ${{ env.DEPLOY_USER }} --password-stdin
|
||||||
|
docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:\$TAG
|
||||||
|
|
||||||
|
echo 'Restarting dev container...'
|
||||||
|
cd /opt/gitea-dev
|
||||||
|
sed -i "s|${{ env.IMAGE }}:[^ ]*|${{ env.IMAGE }}:\$TAG|" docker-compose.yml
|
||||||
|
docker compose up -d mokogitea-dev
|
||||||
|
|
||||||
|
echo 'Health check...'
|
||||||
|
for i in 1 2 3 4 5 6 7 8; do
|
||||||
|
sleep 15
|
||||||
|
if docker inspect --format='\$HEALTH_FMT' mokogitea-dev 2>/dev/null | grep -q healthy; then
|
||||||
|
echo 'Dev container healthy!'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Waiting... (attempt \$i/8)"
|
||||||
|
done
|
||||||
|
echo 'Health check failed'
|
||||||
|
docker logs mokogitea-dev --tail 20
|
||||||
|
exit 1
|
||||||
|
DEPLOY_EOF
|
||||||
|
|
||||||
|
- name: Verify dev instance
|
||||||
|
run: |
|
||||||
|
sleep 5
|
||||||
|
curl -sf https://git.dev.mokoconsulting.tech/api/healthz && echo " Dev API healthy"
|
||||||
@@ -36,7 +36,23 @@ env:
|
|||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
check-dev:
|
||||||
|
name: "Verify dev environment is healthy"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check dev health
|
||||||
|
run: |
|
||||||
|
echo "Checking git.dev.mokoconsulting.tech health..."
|
||||||
|
if curl -sf --max-time 10 https://git.dev.mokoconsulting.tech/api/healthz; then
|
||||||
|
echo " Dev environment is healthy — proceeding with production deploy"
|
||||||
|
else
|
||||||
|
echo "::error::Dev environment is NOT healthy — blocking production deploy"
|
||||||
|
echo "Deploy to dev first (push to dev branch) and verify it passes before merging to main."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
|
needs: check-dev
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout source (for version detection)
|
- name: Checkout source (for version detection)
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
name: Publish MCP to npm
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
paths:
|
|
||||||
- '.mokogitea/mcp/**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
publish:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: '20'
|
|
||||||
registry-url: 'https://registry.npmjs.org'
|
|
||||||
|
|
||||||
- name: Install and build
|
|
||||||
working-directory: .mokogitea/mcp
|
|
||||||
run: |
|
|
||||||
npm ci
|
|
||||||
npx tsc
|
|
||||||
|
|
||||||
- name: Check version change
|
|
||||||
id: version
|
|
||||||
working-directory: .mokogitea/mcp
|
|
||||||
run: |
|
|
||||||
LOCAL_VERSION=$(node -p "require('./package.json').version")
|
|
||||||
NPM_VERSION=$(npm view @mokoconsulting/mokogitea-mcp version 2>/dev/null || echo "0.0.0")
|
|
||||||
if [ "$LOCAL_VERSION" != "$NPM_VERSION" ]; then
|
|
||||||
echo "changed=true" >> $GITHUB_OUTPUT
|
|
||||||
echo "Version changed: $NPM_VERSION -> $LOCAL_VERSION"
|
|
||||||
else
|
|
||||||
echo "changed=false" >> $GITHUB_OUTPUT
|
|
||||||
echo "Version unchanged: $LOCAL_VERSION"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Publish to npm
|
|
||||||
if: steps.version.outputs.changed == 'true'
|
|
||||||
working-directory: .mokogitea/mcp
|
|
||||||
run: npm publish --access public
|
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|
||||||
- name: Publish to Gitea registry
|
|
||||||
if: steps.version.outputs.changed == 'true'
|
|
||||||
working-directory: .mokogitea/mcp
|
|
||||||
run: |
|
|
||||||
npm publish --registry ${{ github.server_url }}/api/packages/${{ github.repository_owner }}/npm/ \
|
|
||||||
--//$(echo "${{ github.server_url }}" | sed 's|https://||')/api/packages/${{ github.repository_owner }}/npm/:_authToken=${{ secrets.MOKOGITEA_TOKEN }}
|
|
||||||
@@ -88,8 +88,20 @@ jobs:
|
|||||||
php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
|
php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
|
||||||
php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
||||||
|
|
||||||
|
- name: Check platform eligibility (Joomla only)
|
||||||
|
id: eligibility
|
||||||
|
run: |
|
||||||
|
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||||
|
if [[ "$PLATFORM" == joomla* ]] || [[ "$PLATFORM" == "joomla" ]]; then
|
||||||
|
echo "proceed=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "proceed=false" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "::notice::Platform '$PLATFORM' — non-Joomla, skipping pre-release auto-bump"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Resolve metadata and bump version
|
- name: Resolve metadata and bump version
|
||||||
id: meta
|
id: meta
|
||||||
|
if: steps.eligibility.outputs.proceed == 'true'
|
||||||
run: |
|
run: |
|
||||||
# Auto-detect stability from branch name on push, or use input on dispatch
|
# Auto-detect stability from branch name on push, or use input on dispatch
|
||||||
if [ "${{ github.event_name }}" = "push" ]; then
|
if [ "${{ github.event_name }}" = "push" ]; then
|
||||||
@@ -166,6 +178,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
id: release
|
id: release
|
||||||
|
if: steps.eligibility.outputs.proceed == 'true'
|
||||||
run: |
|
run: |
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
TAG="${{ steps.meta.outputs.tag }}"
|
||||||
VERSION="${{ steps.meta.outputs.version }}"
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
@@ -176,6 +189,7 @@ jobs:
|
|||||||
--repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease
|
--repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease
|
||||||
|
|
||||||
- name: Update release notes from CHANGELOG.md
|
- name: Update release notes from CHANGELOG.md
|
||||||
|
if: steps.eligibility.outputs.proceed == 'true'
|
||||||
run: |
|
run: |
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
TAG="${{ steps.meta.outputs.tag }}"
|
||||||
VERSION="${{ steps.meta.outputs.version }}"
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
@@ -212,6 +226,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build package and upload
|
- name: Build package and upload
|
||||||
id: package
|
id: package
|
||||||
|
if: steps.eligibility.outputs.proceed == 'true'
|
||||||
run: |
|
run: |
|
||||||
VERSION="${{ steps.meta.outputs.version }}"
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
TAG="${{ steps.meta.outputs.tag }}"
|
||||||
@@ -225,6 +240,7 @@ jobs:
|
|||||||
# No need to build, commit, or sync updates.xml from workflows
|
# No need to build, commit, or sync updates.xml from workflows
|
||||||
|
|
||||||
- name: "Delete lesser pre-release channels (cascade)"
|
- name: "Delete lesser pre-release channels (cascade)"
|
||||||
|
if: steps.eligibility.outputs.proceed == 'true'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
#
|
|
||||||
# FILE INFORMATION
|
|
||||||
# DEFGROUP: Gitea.Workflow
|
|
||||||
# INGROUP: MokoStandards.Security
|
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
|
||||||
# PATH: /.gitea/workflows/security-audit.yml
|
|
||||||
# VERSION: 01.00.00
|
|
||||||
# BRIEF: Dependency vulnerability scanning for composer and npm packages
|
|
||||||
|
|
||||||
name: "Universal: Security Audit"
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 6 * * 1' # Weekly on Monday at 06:00 UTC
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- 'composer.json'
|
|
||||||
- 'composer.lock'
|
|
||||||
- 'package.json'
|
|
||||||
- 'package-lock.json'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
env:
|
|
||||||
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
|
|
||||||
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-security' }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
audit:
|
|
||||||
name: Dependency Audit
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Composer audit
|
|
||||||
if: hashFiles('composer.lock') != ''
|
|
||||||
run: |
|
|
||||||
echo "=== Composer Security Audit ==="
|
|
||||||
if ! command -v composer &> /dev/null; then
|
|
||||||
sudo apt-get update -qq
|
|
||||||
sudo apt-get install -y -qq php-cli composer >/dev/null 2>&1
|
|
||||||
fi
|
|
||||||
composer audit --format=plain 2>&1 | tee /tmp/composer-audit.txt
|
|
||||||
RESULT=$?
|
|
||||||
if [ $RESULT -ne 0 ]; then
|
|
||||||
echo "::warning::Composer vulnerabilities found"
|
|
||||||
echo "composer_vulnerable=true" >> "$GITHUB_ENV"
|
|
||||||
else
|
|
||||||
echo "No known vulnerabilities in composer dependencies"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: NPM audit
|
|
||||||
if: hashFiles('package-lock.json') != ''
|
|
||||||
run: |
|
|
||||||
echo "=== NPM Security Audit ==="
|
|
||||||
npm audit --production 2>&1 | tee /tmp/npm-audit.txt || true
|
|
||||||
if npm audit --production 2>&1 | grep -q "found 0 vulnerabilities"; then
|
|
||||||
echo "No known vulnerabilities in npm dependencies"
|
|
||||||
else
|
|
||||||
echo "::warning::NPM vulnerabilities found"
|
|
||||||
echo "npm_vulnerable=true" >> "$GITHUB_ENV"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Notify on vulnerabilities
|
|
||||||
if: env.composer_vulnerable == 'true' || env.npm_vulnerable == 'true'
|
|
||||||
run: |
|
|
||||||
REPO="${{ github.event.repository.name }}"
|
|
||||||
curl -sS \
|
|
||||||
-H "Title: ${REPO} has vulnerable dependencies" \
|
|
||||||
-H "Tags: lock,warning" \
|
|
||||||
-H "Priority: high" \
|
|
||||||
-d "Security audit found vulnerabilities. Review dependency updates." \
|
|
||||||
"${NTFY_URL}/${NTFY_TOPIC}" || true
|
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: Gitea.Workflow.Template
|
||||||
|
# INGROUP: MokoStandards.CI
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
|
||||||
|
# PATH: /.mokogitea/workflows/version-set.yml
|
||||||
|
# VERSION: 01.00.00
|
||||||
|
# BRIEF: Set or reset the extension version across all version-bearing files
|
||||||
|
|
||||||
|
name: "Joomla: Set Version"
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: "Version number (e.g. 01.00.00)"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
branch:
|
||||||
|
description: "Branch to update (default: current)"
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
set-version:
|
||||||
|
name: Set Version to ${{ inputs.version }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Validate version format
|
||||||
|
run: |
|
||||||
|
VERSION="${{ inputs.version }}"
|
||||||
|
if ! echo "$VERSION" | grep -qP '^\d{2}\.\d{2}\.\d{2}$'; then
|
||||||
|
echo "::error::Invalid version format '${VERSION}' — expected XX.YY.ZZ (e.g. 01.00.00)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "VERSION=${VERSION}" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.MOKOGITEA_TOKEN || secrets.GA_TOKEN || github.token }}
|
||||||
|
ref: ${{ inputs.branch || github.ref }}
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Update manifest version
|
||||||
|
run: |
|
||||||
|
MANIFEST=""
|
||||||
|
for XML_FILE in $(find . -maxdepth 3 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
|
||||||
|
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
|
||||||
|
MANIFEST="$XML_FILE"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$MANIFEST" ]; then
|
||||||
|
echo "::warning::No Joomla extension manifest found — skipping manifest update"
|
||||||
|
else
|
||||||
|
OLD_VER=$(grep -oP '<version>\K[^<]+' "$MANIFEST" | head -1)
|
||||||
|
sed -i "s|<version>${OLD_VER}</version>|<version>${VERSION}</version>|" "$MANIFEST"
|
||||||
|
echo "Manifest: ${OLD_VER} → ${VERSION} (${MANIFEST})"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Update README.md version
|
||||||
|
run: |
|
||||||
|
if [ -f "README.md" ]; then
|
||||||
|
if grep -qP '^\s*VERSION:\s*\d' README.md; then
|
||||||
|
sed -i -E "s/(VERSION:\s*)[0-9]{2}\.[0-9]{2}\.[0-9]{2}/\1${VERSION}/" README.md
|
||||||
|
echo "README.md version updated to ${VERSION}"
|
||||||
|
else
|
||||||
|
echo "::warning::No VERSION line found in README.md — skipping"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Update CHANGELOG.md
|
||||||
|
run: |
|
||||||
|
if [ -f "CHANGELOG.md" ]; then
|
||||||
|
DATE=$(date +%Y-%m-%d)
|
||||||
|
# Check if this version already has an entry
|
||||||
|
if grep -q "^\#\# \[${VERSION}\]" CHANGELOG.md; then
|
||||||
|
echo "CHANGELOG.md already has entry for ${VERSION} — skipping"
|
||||||
|
else
|
||||||
|
# Insert new version entry after [Unreleased] or at the top after header
|
||||||
|
if grep -q '^\#\# \[Unreleased\]' CHANGELOG.md; then
|
||||||
|
sed -i "/^\#\# \[Unreleased\]/a\\\\n## [${VERSION}] --- ${DATE}" CHANGELOG.md
|
||||||
|
else
|
||||||
|
sed -i "/^\# Changelog/a\\\\n## [Unreleased]\n\n## [${VERSION}] --- ${DATE}" CHANGELOG.md
|
||||||
|
fi
|
||||||
|
echo "CHANGELOG.md: added entry for ${VERSION}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "::warning::No CHANGELOG.md found — skipping"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Update FILE INFORMATION blocks
|
||||||
|
run: |
|
||||||
|
# Update VERSION in file header blocks (# VERSION: XX.YY.ZZ)
|
||||||
|
find . -maxdepth 1 -type f \( -name "*.yml" -o -name "*.yaml" -o -name "*.php" -o -name "*.md" \) \
|
||||||
|
-not -path "./.git/*" -not -path "./vendor/*" -print0 2>/dev/null | \
|
||||||
|
while IFS= read -r -d '' FILE; do
|
||||||
|
if head -20 "$FILE" | grep -qP '^\s*#?\s*VERSION:\s*\d{2}\.\d{2}\.\d{2}'; then
|
||||||
|
sed -i -E "s/(#?\s*VERSION:\s*)[0-9]{2}\.[0-9]{2}\.[0-9]{2}/\1${VERSION}/" "$FILE"
|
||||||
|
echo "Updated FILE INFORMATION VERSION in ${FILE}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Commit and push
|
||||||
|
run: |
|
||||||
|
git config user.name "Moko Consulting [bot]"
|
||||||
|
git config user.email "hello@mokoconsulting.tech"
|
||||||
|
git add -A
|
||||||
|
if git diff --cached --quiet; then
|
||||||
|
echo "No version changes detected — nothing to commit"
|
||||||
|
else
|
||||||
|
git commit -m "chore: set version to ${VERSION} [skip bump]
|
||||||
|
|
||||||
|
Authored-by: Moko Consulting"
|
||||||
|
git push
|
||||||
|
echo "### Version Set" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Version updated to \`${VERSION}\` on branch \`${GITHUB_REF_NAME}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
@@ -3,6 +3,17 @@
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
- Wiki full-text search: case-insensitive search across all wiki page titles and content (#550)
|
||||||
|
- Wiki search API: GET /wiki/search?q=term with paginated JSON results (#550)
|
||||||
|
- Metadata deploy fields: deploy_host, deploy_port, deploy_user, deploy_path, docker_image, docker_registry, container_name, health_url (#692)
|
||||||
|
- Metadata API partial updates: PUT /metadata now merges only sent fields instead of replacing all
|
||||||
|
- Wiki revision diff: line-by-line diff view per commit in wiki page history (#667)
|
||||||
|
- Wiki categories: YAML frontmatter `categories:` with category index page (#668)
|
||||||
|
- Wiki template transclusion: `{{template:Name|key=val}}` with `_Template/` folder (#671)
|
||||||
|
- Wiki enhanced ToC: collapsible, inline via frontmatter, sticky sidebar (#673)
|
||||||
|
- Wiki folder ACL: `_access.yml` per-folder write protection (#674)
|
||||||
|
- Wiki print view and ZIP export of all wiki pages (#675)
|
||||||
|
- Wiki features documentation page in org wiki (standards/Wiki-Features)
|
||||||
- DLID licensing system: license, entitlement, activation, product_tier, audit_log tables (v359 migration)
|
- DLID licensing system: license, entitlement, activation, product_tier, audit_log tables (v359 migration)
|
||||||
- License CRUD with CRC32-checksummed DLID generation and format validation
|
- License CRUD with CRC32-checksummed DLID generation and format validation
|
||||||
- Entitlement model with tier-based rebuild and custom entitlement preservation
|
- Entitlement model with tier-based rebuild and custom entitlement preservation
|
||||||
@@ -10,6 +21,30 @@
|
|||||||
- 13 seeded product tiers from base to enterprise
|
- 13 seeded product tiers from base to enterprise
|
||||||
- DLID-gated update XML endpoint: GET /api/v1/licensing/updates/{product}.xml
|
- DLID-gated update XML endpoint: GET /api/v1/licensing/updates/{product}.xml
|
||||||
- Profile repo fallback chain: .mokogitea > .profile > .github
|
- Profile repo fallback chain: .mokogitea > .profile > .github
|
||||||
|
- Metadata/manifest GET endpoint publicly accessible without auth (#676)
|
||||||
|
- Org wiki: folder-based collapsible tree sidebar, _Sidebar.md overrides (#680)
|
||||||
|
- Wiki backlinks: "What links here" page showing all pages referencing current page (#669)
|
||||||
|
- Wiki wikilinks: [[Page Name]] and [[Page|Display Text]] syntax with red links for missing pages (#666)
|
||||||
|
- Required baseline issue statuses: Open and Closed are indestructible (is_required flag) (#681)
|
||||||
|
- Issue status API response includes is_required field
|
||||||
|
- Wiki recent changes page: cross-page edit activity with pagination (#670)
|
||||||
|
- Wiki page rename with automatic redirects via YAML frontmatter (#672)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Licensing API: handle DB write errors in UpdateLicense, UpdateTier, DeleteTier instead of silently discarding
|
||||||
|
- Wiki API: fix findEntryForFile URL-decode fallback for non-ASCII page names
|
||||||
|
- Metadata settings template 500 error: removed reference to deleted Version field
|
||||||
|
- Wiki recent changes: use commit.MessageTitle() instead of commit.Message()
|
||||||
|
- Wiki backlinks: proper URL encoding for subdirectory pages
|
||||||
|
- Wiki wikilinks: page existence lookup normalizes spaces and hyphens
|
||||||
|
- Issue statuses template: garbled em-dash character replaced
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Issue status seed defaults: Open, In Progress, Waiting, In Review, Closed, Won't Fix
|
||||||
|
- Pre-release workflow: auto-bump skipped for non-Joomla repos (platform check)
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Workflows: gitleaks.yml, npm-publish.yml, notify.yml, workflow-sync-trigger.yml, composer-publish.yml, deploy-manual.yml, security-audit.yml (not applicable to Go repo)
|
||||||
|
|
||||||
## [06.19.00] --- 2026-06-20
|
## [06.19.00] --- 2026-06-20
|
||||||
|
|
||||||
|
|||||||
@@ -1,38 +1,29 @@
|
|||||||
# MokoGitea
|
# MokoGitea
|
||||||
|
|
||||||
Moko fork of Gitea — adding project board REST API endpoints and custom enhancements
|
Custom Gitea fork with enhanced wiki system, DLID licensing, issue statuses, org metadata, and project board API.
|
||||||
|
|
||||||
  
|
 
|
||||||
|
|
||||||
|
|
||||||
Custom Gitea fork with Project Board API
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Pages
|
## Key Features
|
||||||
|
|
||||||
- [Branding](https://code.mokoconsulting.tech/MokoConsulting/MokoGitea/wiki/Branding)
|
- **Wiki System** -- wikilinks, categories, backlinks, template transclusion, revision diffs, rename redirects, folder ACL, enhanced ToC, print view, ZIP export ([details](https://git.mokoconsulting.tech/MokoConsulting/.mokogitea/wiki/standards/Wiki-Features))
|
||||||
- [Deployment](https://code.mokoconsulting.tech/MokoConsulting/MokoGitea/wiki/Deployment)
|
- **DLID Licensing** -- license management, entitlements, domain activations, ed25519-signed downloads
|
||||||
- [Project API](Project API)
|
- **Issue Statuses** -- custom workflow statuses per org with required baseline protection
|
||||||
- [roadmap](https://code.mokoconsulting.tech/MokoConsulting/MokoGitea/wiki/roadmap)
|
- **Org Metadata** -- per-repo metadata API (public GET, admin PUT), platform detection for versioning
|
||||||
|
- **Project Board API** -- REST endpoints for project columns and cards
|
||||||
---
|
- **Dev Deploy Gate** -- builds deploy to dev environment first, production checks dev health
|
||||||
|
|
||||||
**Category:** Infrastructure | **Platform:** [MokoPlatform wiki](https://code.mokoconsulting.tech/MokoConsulting/MokoPlatform/wiki)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
Full documentation is available on the [Wiki](https://code.mokoconsulting.tech/MokoConsulting/MokoGitea/wiki).
|
- [Org Wiki](https://git.mokoconsulting.tech/MokoConsulting/.mokogitea/wiki/) -- standards, CLI reference, API docs
|
||||||
|
- [Wiki Features](https://git.mokoconsulting.tech/MokoConsulting/.mokogitea/wiki/standards/Wiki-Features) -- all 10 wiki enhancements
|
||||||
|
- [Licensing API](https://git.mokoconsulting.tech/MokoConsulting/.mokogitea/wiki/api/Licensing-API)
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
See the wiki for development guidelines and contribution instructions.
|
See the [org wiki](https://git.mokoconsulting.tech/MokoConsulting/.mokogitea/wiki/) for development guidelines, coding standards, and contribution instructions.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
@@ -40,4 +31,4 @@ This project is licensed under the GNU General Public License v3.0 or later -- s
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*[Moko Consulting](https://mokoconsulting.tech) -- [MokoStandards](https://code.mokoconsulting.tech/MokoConsulting/MokoPlatform/wiki/Home)*
|
*[Moko Consulting](https://mokoconsulting.tech)*
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ type IssueStatusDef struct {
|
|||||||
Color string `xorm:"VARCHAR(7)"` // hex color, e.g. "#e11d48"
|
Color string `xorm:"VARCHAR(7)"` // hex color, e.g. "#e11d48"
|
||||||
Description string `xorm:"TEXT"`
|
Description string `xorm:"TEXT"`
|
||||||
ClosesIssue bool `xorm:"NOT NULL DEFAULT false 'closes_issue'"`
|
ClosesIssue bool `xorm:"NOT NULL DEFAULT false 'closes_issue'"`
|
||||||
|
IsRequired bool `xorm:"NOT NULL DEFAULT false 'is_required'"` // cannot be deleted
|
||||||
SortOrder int `xorm:"NOT NULL DEFAULT 0 'sort_order'"`
|
SortOrder int `xorm:"NOT NULL DEFAULT 0 'sort_order'"`
|
||||||
IsActive bool `xorm:"NOT NULL DEFAULT true 'is_active'"`
|
IsActive bool `xorm:"NOT NULL DEFAULT true 'is_active'"`
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED 'created_unix'"`
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED 'created_unix'"`
|
||||||
@@ -56,14 +57,15 @@ func GetIssueStatusDefsByOrg(ctx context.Context, orgID int64) ([]*IssueStatusDe
|
|||||||
}
|
}
|
||||||
|
|
||||||
// seedDefaultIssueStatuses creates the standard status presets for an org.
|
// seedDefaultIssueStatuses creates the standard status presets for an org.
|
||||||
|
// Open and Closed are required (is_required=true) and cannot be deleted.
|
||||||
func seedDefaultIssueStatuses(ctx context.Context, orgID int64) error {
|
func seedDefaultIssueStatuses(ctx context.Context, orgID int64) error {
|
||||||
defaults := []*IssueStatusDef{
|
defaults := []*IssueStatusDef{
|
||||||
{OrgID: orgID, Name: "In Progress", Color: "#2563eb", Description: "Work is actively being done", SortOrder: 1, IsActive: true},
|
{OrgID: orgID, Name: "Open", Color: "#2563eb", Description: "New or active issue", ClosesIssue: false, IsRequired: true, SortOrder: 0, IsActive: true},
|
||||||
{OrgID: orgID, Name: "Needs Info", Color: "#f59e0b", Description: "Waiting for more information", SortOrder: 2, IsActive: true},
|
{OrgID: orgID, Name: "In Progress", Color: "#7c3aed", Description: "Work is actively being done", SortOrder: 1, IsActive: true},
|
||||||
{OrgID: orgID, Name: "Blocked", Color: "#dc2626", Description: "Cannot proceed due to dependency", SortOrder: 3, IsActive: true},
|
{OrgID: orgID, Name: "Waiting", Color: "#f59e0b", Description: "Blocked or waiting for input", SortOrder: 2, IsActive: true},
|
||||||
{OrgID: orgID, Name: "Resolved", Color: "#16a34a", Description: "Fix implemented and verified", ClosesIssue: true, SortOrder: 4, IsActive: true},
|
{OrgID: orgID, Name: "In Review", Color: "#0891b2", Description: "PR submitted, awaiting review", SortOrder: 3, IsActive: true},
|
||||||
|
{OrgID: orgID, Name: "Closed", Color: "#16a34a", Description: "Completed or resolved", ClosesIssue: true, IsRequired: true, SortOrder: 4, IsActive: true},
|
||||||
{OrgID: orgID, Name: "Won't Fix", Color: "#6b7280", Description: "Decided not to address", ClosesIssue: true, SortOrder: 5, IsActive: true},
|
{OrgID: orgID, Name: "Won't Fix", Color: "#6b7280", Description: "Decided not to address", ClosesIssue: true, SortOrder: 5, IsActive: true},
|
||||||
{OrgID: orgID, Name: "Duplicate", Color: "#8b5cf6", Description: "Already tracked elsewhere", ClosesIssue: true, SortOrder: 6, IsActive: true},
|
|
||||||
}
|
}
|
||||||
for _, d := range defaults {
|
for _, d := range defaults {
|
||||||
if _, err := db.GetEngine(ctx).Insert(d); err != nil {
|
if _, err := db.GetEngine(ctx).Insert(d); err != nil {
|
||||||
@@ -111,13 +113,37 @@ func UpdateIssueStatusDef(ctx context.Context, def *IssueStatusDef) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrStatusRequired is returned when trying to delete a required status.
|
||||||
|
type ErrStatusRequired struct {
|
||||||
|
ID int64
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrStatusRequired) Error() string {
|
||||||
|
return "status is required and cannot be deleted"
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsErrStatusRequired checks if an error is ErrStatusRequired.
|
||||||
|
func IsErrStatusRequired(err error) bool {
|
||||||
|
_, ok := err.(ErrStatusRequired)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteIssueStatusDef deletes a status definition and clears references on issues.
|
// DeleteIssueStatusDef deletes a status definition and clears references on issues.
|
||||||
|
// Returns ErrStatusRequired if the status is marked as required.
|
||||||
func DeleteIssueStatusDef(ctx context.Context, id int64) error {
|
func DeleteIssueStatusDef(ctx context.Context, id int64) error {
|
||||||
|
def, err := GetIssueStatusDefByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if def.IsRequired {
|
||||||
|
return ErrStatusRequired{ID: def.ID, Name: def.Name}
|
||||||
|
}
|
||||||
// Clear status_id on all issues that reference this definition
|
// Clear status_id on all issues that reference this definition
|
||||||
if _, err := db.GetEngine(ctx).Exec("UPDATE issue SET status_id = 0 WHERE status_id = ?", id); err != nil {
|
if _, err := db.GetEngine(ctx).Exec("UPDATE issue SET status_id = 0 WHERE status_id = ?", id); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err := db.GetEngine(ctx).ID(id).Delete(new(IssueStatusDef))
|
_, err = db.GetEngine(ctx).ID(id).Delete(new(IssueStatusDef))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -436,6 +436,7 @@ func prepareMigrationTasks() []*migration {
|
|||||||
newMigration(356, "Rename package_type to extension_type in repo manifest", v1_27.RenamePackageTypeToExtensionType),
|
newMigration(356, "Rename package_type to extension_type in repo manifest", v1_27.RenamePackageTypeToExtensionType),
|
||||||
newMigration(357, "Drop display_name from repo manifest and update stream config", v1_27.DropDisplayNameColumns),
|
newMigration(357, "Drop display_name from repo manifest and update stream config", v1_27.DropDisplayNameColumns),
|
||||||
newMigration(358, "Add licensing tables (license, entitlement, activation, product_tier)", v1_27.AddLicensingTables),
|
newMigration(358, "Add licensing tables (license, entitlement, activation, product_tier)", v1_27.AddLicensingTables),
|
||||||
|
newMigration(359, "Add deploy fields to repo manifest", v1_27.AddDeployFieldsToRepoManifest),
|
||||||
}
|
}
|
||||||
return preparedMigrations
|
return preparedMigrations
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package v1_27
|
||||||
|
|
||||||
|
import (
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddDeployFieldsToRepoManifest adds deploy configuration columns to repo_manifest.
|
||||||
|
func AddDeployFieldsToRepoManifest(x *xorm.Engine) error {
|
||||||
|
type RepoManifest struct {
|
||||||
|
DeployHost string `xorm:"VARCHAR(255) 'deploy_host'"`
|
||||||
|
DeployPort string `xorm:"VARCHAR(10) 'deploy_port'"`
|
||||||
|
DeployUser string `xorm:"VARCHAR(100) 'deploy_user'"`
|
||||||
|
DeployPath string `xorm:"TEXT 'deploy_path'"`
|
||||||
|
DockerImage string `xorm:"VARCHAR(255) 'docker_image'"`
|
||||||
|
DockerRegistry string `xorm:"VARCHAR(255) 'docker_registry'"`
|
||||||
|
ContainerName string `xorm:"VARCHAR(100) 'container_name'"`
|
||||||
|
HealthURL string `xorm:"TEXT 'health_url'"`
|
||||||
|
}
|
||||||
|
return x.Sync(new(RepoManifest))
|
||||||
|
}
|
||||||
@@ -50,6 +50,16 @@ type RepoMetadata struct {
|
|||||||
ExtensionType string `xorm:"VARCHAR(50) 'extension_type'"` // component, module, plugin, package, template, library, file
|
ExtensionType string `xorm:"VARCHAR(50) 'extension_type'"` // component, module, plugin, package, template, library, file
|
||||||
EntryPoint string `xorm:"TEXT 'entry_point'"` // build entry point path
|
EntryPoint string `xorm:"TEXT 'entry_point'"` // build entry point path
|
||||||
|
|
||||||
|
// deploy section
|
||||||
|
DeployHost string `xorm:"VARCHAR(255) 'deploy_host'"` // SSH host for deploy
|
||||||
|
DeployPort string `xorm:"VARCHAR(10) 'deploy_port'"` // SSH port (default 2918)
|
||||||
|
DeployUser string `xorm:"VARCHAR(100) 'deploy_user'"` // SSH user
|
||||||
|
DeployPath string `xorm:"TEXT 'deploy_path'"` // remote path for source/compose
|
||||||
|
DockerImage string `xorm:"VARCHAR(255) 'docker_image'"` // e.g. mokoconsulting/mokogitea
|
||||||
|
DockerRegistry string `xorm:"VARCHAR(255) 'docker_registry'"` // e.g. git.mokoconsulting.tech
|
||||||
|
ContainerName string `xorm:"VARCHAR(100) 'container_name'"` // Docker container name
|
||||||
|
HealthURL string `xorm:"TEXT 'health_url'"` // health check URL after deploy
|
||||||
|
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED 'created_unix'"`
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED 'created_unix'"`
|
||||||
UpdatedUnix timeutil.TimeStamp `xorm:"UPDATED 'updated_unix'"`
|
UpdatedUnix timeutil.TimeStamp `xorm:"UPDATED 'updated_unix'"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ type IssueStatusDef struct {
|
|||||||
Color string `json:"color"`
|
Color string `json:"color"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
ClosesIssue bool `json:"closes_issue"`
|
ClosesIssue bool `json:"closes_issue"`
|
||||||
|
IsRequired bool `json:"is_required"`
|
||||||
SortOrder int `json:"sort_order"`
|
SortOrder int `json:"sort_order"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1314,6 +1314,7 @@ func Routes() *web.Router {
|
|||||||
m.Get("/revisions/*", repo.ListPageRevisions)
|
m.Get("/revisions/*", repo.ListPageRevisions)
|
||||||
m.Post("/new", reqToken(), mustNotBeArchived, reqRepoWriter(unit.TypeWiki), bind(api.CreateWikiPageOptions{}), repo.NewWikiPage)
|
m.Post("/new", reqToken(), mustNotBeArchived, reqRepoWriter(unit.TypeWiki), bind(api.CreateWikiPageOptions{}), repo.NewWikiPage)
|
||||||
m.Get("/pages", repo.ListWikiPages)
|
m.Get("/pages", repo.ListWikiPages)
|
||||||
|
m.Get("/search", repo.SearchWikiPages)
|
||||||
}, mustEnableWiki)
|
}, mustEnableWiki)
|
||||||
m.Post("/markup", reqToken(), bind(api.MarkupOption{}), misc.Markup)
|
m.Post("/markup", reqToken(), bind(api.MarkupOption{}), misc.Markup)
|
||||||
m.Post("/markdown", reqToken(), bind(api.MarkdownOption{}), misc.Markdown)
|
m.Post("/markdown", reqToken(), bind(api.MarkdownOption{}), misc.Markdown)
|
||||||
@@ -1480,12 +1481,10 @@ func Routes() *web.Router {
|
|||||||
Delete(reqToken(), repo.DeleteTopic)
|
Delete(reqToken(), repo.DeleteTopic)
|
||||||
}, reqAdmin())
|
}, reqAdmin())
|
||||||
}, reqAnyRepoReader())
|
}, reqAnyRepoReader())
|
||||||
m.Combo("/metadata", reqRepoReader(unit.TypeCode)).
|
m.Get("/metadata", repo.GetRepoMetadata)
|
||||||
Get(repo.GetRepoMetadata).
|
m.Put("/metadata", reqToken(), reqAdmin(), repo.UpdateRepoMetadata)
|
||||||
Put(reqToken(), reqAdmin(), repo.UpdateRepoMetadata)
|
m.Get("/manifest", repo.GetRepoMetadata) // backward compat
|
||||||
m.Combo("/manifest", reqRepoReader(unit.TypeCode)). // backward compat
|
m.Put("/manifest", reqToken(), reqAdmin(), repo.UpdateRepoMetadata)
|
||||||
Get(repo.GetRepoMetadata).
|
|
||||||
Put(reqToken(), reqAdmin(), repo.UpdateRepoMetadata)
|
|
||||||
// MokoGitea badge engine
|
// MokoGitea badge engine
|
||||||
m.Get("/badge/{type}.svg", repo.GetRepoBadge)
|
m.Get("/badge/{type}.svg", repo.GetRepoBadge)
|
||||||
m.Get("/issue_templates", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(), repo.GetIssueTemplates)
|
m.Get("/issue_templates", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(), repo.GetIssueTemplates)
|
||||||
|
|||||||
@@ -207,7 +207,10 @@ func UpdateLicense(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
if len(cols) > 0 {
|
if len(cols) > 0 {
|
||||||
cols = append(cols, "updated_at")
|
cols = append(cols, "updated_at")
|
||||||
db.GetEngine(ctx).ID(id).Cols(cols...).Update(license)
|
if _, err := db.GetEngine(ctx).ID(id).Cols(cols...).Update(license); err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.JSON(http.StatusOK, licenseToJSON(ctx, license))
|
ctx.JSON(http.StatusOK, licenseToJSON(ctx, license))
|
||||||
@@ -399,7 +402,10 @@ func UpdateTier(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(cols) > 0 {
|
if len(cols) > 0 {
|
||||||
db.GetEngine(ctx).ID(id).Cols(cols...).Update(tier)
|
if _, err := db.GetEngine(ctx).ID(id).Cols(cols...).Update(tier); err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.JSON(http.StatusOK, tierToJSON(tier))
|
ctx.JSON(http.StatusOK, tierToJSON(tier))
|
||||||
@@ -427,7 +433,10 @@ func DeleteTier(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
db.GetEngine(ctx).ID(id).Delete(new(licensing_model.ProductTier))
|
if _, err := db.GetEngine(ctx).ID(id).Delete(new(licensing_model.ProductTier)); err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
ctx.Status(http.StatusNoContent)
|
ctx.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,19 @@ import (
|
|||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// checkOrgVisibility returns true if the current user can view org metadata.
|
||||||
|
// Public orgs are visible to everyone. Private/limited orgs require authentication.
|
||||||
|
func checkOrgVisibility(ctx *context.APIContext) bool {
|
||||||
|
if ctx.Org.Organization.Visibility == api.VisibleTypePublic {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if ctx.Doer == nil {
|
||||||
|
ctx.APIErrorNotFound()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// ListIssueStatuses returns active issue status definitions for an org.
|
// ListIssueStatuses returns active issue status definitions for an org.
|
||||||
func ListIssueStatuses(ctx *context.APIContext) {
|
func ListIssueStatuses(ctx *context.APIContext) {
|
||||||
// swagger:operation GET /orgs/{org}/issue-statuses organization orgListIssueStatuses
|
// swagger:operation GET /orgs/{org}/issue-statuses organization orgListIssueStatuses
|
||||||
@@ -34,6 +47,10 @@ func ListIssueStatuses(ctx *context.APIContext) {
|
|||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
if !checkOrgVisibility(ctx) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
defs, err := issues_model.GetIssueStatusDefsByOrg(ctx, ctx.Org.Organization.ID)
|
defs, err := issues_model.GetIssueStatusDefsByOrg(ctx, ctx.Org.Organization.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
@@ -47,6 +64,7 @@ func ListIssueStatuses(ctx *context.APIContext) {
|
|||||||
Color: d.Color,
|
Color: d.Color,
|
||||||
Description: d.Description,
|
Description: d.Description,
|
||||||
ClosesIssue: d.ClosesIssue,
|
ClosesIssue: d.ClosesIssue,
|
||||||
|
IsRequired: d.IsRequired,
|
||||||
SortOrder: d.SortOrder,
|
SortOrder: d.SortOrder,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -76,6 +94,10 @@ func ListIssuePriorities(ctx *context.APIContext) {
|
|||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
if !checkOrgVisibility(ctx) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
defs, err := issues_model.GetIssuePriorityDefsByOrg(ctx, ctx.Org.Organization.ID)
|
defs, err := issues_model.GetIssuePriorityDefsByOrg(ctx, ctx.Org.Organization.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
@@ -118,6 +140,10 @@ func ListIssueTypes(ctx *context.APIContext) {
|
|||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
if !checkOrgVisibility(ctx) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
defs, err := issues_model.GetIssueTypeDefsByOrg(ctx, ctx.Org.Organization.ID)
|
defs, err := issues_model.GetIssueTypeDefsByOrg(ctx, ctx.Org.Organization.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
|
|||||||
@@ -32,6 +32,16 @@ type apiMetadata struct {
|
|||||||
Language string `json:"language"`
|
Language string `json:"language"`
|
||||||
ExtensionType string `json:"extension_type"`
|
ExtensionType string `json:"extension_type"`
|
||||||
EntryPoint string `json:"entry_point"`
|
EntryPoint string `json:"entry_point"`
|
||||||
|
|
||||||
|
// deploy
|
||||||
|
DeployHost string `json:"deploy_host,omitempty"`
|
||||||
|
DeployPort string `json:"deploy_port,omitempty"`
|
||||||
|
DeployUser string `json:"deploy_user,omitempty"`
|
||||||
|
DeployPath string `json:"deploy_path,omitempty"`
|
||||||
|
DockerImage string `json:"docker_image,omitempty"`
|
||||||
|
DockerRegistry string `json:"docker_registry,omitempty"`
|
||||||
|
ContainerName string `json:"container_name,omitempty"`
|
||||||
|
HealthURL string `json:"health_url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRepoMetadata returns the manifest settings for a repository.
|
// GetRepoMetadata returns the manifest settings for a repository.
|
||||||
@@ -81,6 +91,14 @@ func GetRepoMetadata(ctx *context.APIContext) {
|
|||||||
Language: m.Language,
|
Language: m.Language,
|
||||||
ExtensionType: m.ExtensionType,
|
ExtensionType: m.ExtensionType,
|
||||||
EntryPoint: m.EntryPoint,
|
EntryPoint: m.EntryPoint,
|
||||||
|
DeployHost: m.DeployHost,
|
||||||
|
DeployPort: m.DeployPort,
|
||||||
|
DeployUser: m.DeployUser,
|
||||||
|
DeployPath: m.DeployPath,
|
||||||
|
DockerImage: m.DockerImage,
|
||||||
|
DockerRegistry: m.DockerRegistry,
|
||||||
|
ContainerName: m.ContainerName,
|
||||||
|
HealthURL: m.HealthURL,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,34 +114,58 @@ func UpdateRepoMetadata(ctx *context.APIContext) {
|
|||||||
// responses:
|
// responses:
|
||||||
// "200":
|
// "200":
|
||||||
// "$ref": "#/responses/Manifest"
|
// "$ref": "#/responses/Manifest"
|
||||||
var req apiMetadata
|
// Decode into a map to detect which fields were actually sent.
|
||||||
if err := json.NewDecoder(ctx.Req.Body).Decode(&req); err != nil {
|
var raw map[string]any
|
||||||
|
if err := json.NewDecoder(ctx.Req.Body).Decode(&raw); err != nil {
|
||||||
ctx.APIError(http.StatusBadRequest, err)
|
ctx.APIError(http.StatusBadRequest, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
m := &repo_model.RepoMetadata{
|
// Load existing metadata (or create defaults).
|
||||||
|
m, _ := repo_model.GetRepoMetadata(ctx, ctx.Repo.Repository.ID)
|
||||||
|
if m == nil {
|
||||||
|
m = &repo_model.RepoMetadata{
|
||||||
RepoID: ctx.Repo.Repository.ID,
|
RepoID: ctx.Repo.Repository.ID,
|
||||||
Name: req.Name,
|
Name: ctx.Repo.Repository.Name,
|
||||||
Org: req.Org,
|
Org: ctx.Repo.Repository.OwnerName,
|
||||||
Description: req.Description,
|
Description: ctx.Repo.Repository.Description,
|
||||||
|
|
||||||
LicenseSPDX: req.LicenseSPDX,
|
|
||||||
LicenseName: req.LicenseName,
|
|
||||||
VersionPrefix: req.VersionPrefix,
|
|
||||||
ElementName: req.ElementName,
|
|
||||||
Platform: req.Platform,
|
|
||||||
StandardsVersion: req.StandardsVersion,
|
|
||||||
StandardsSource: req.StandardsSource,
|
|
||||||
Maintainer: req.Maintainer,
|
|
||||||
MaintainerURL: req.MaintainerURL,
|
|
||||||
InfoURL: req.InfoURL,
|
|
||||||
TargetVersion: req.TargetVersion,
|
|
||||||
PHPMinimum: req.PHPMinimum,
|
|
||||||
Language: req.Language,
|
|
||||||
ExtensionType: req.ExtensionType,
|
|
||||||
EntryPoint: req.EntryPoint,
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply only the fields present in the request.
|
||||||
|
setStr := func(key string, target *string) {
|
||||||
|
if v, ok := raw[key]; ok {
|
||||||
|
if s, ok := v.(string); ok {
|
||||||
|
*target = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setStr("name", &m.Name)
|
||||||
|
setStr("org", &m.Org)
|
||||||
|
setStr("description", &m.Description)
|
||||||
|
setStr("license_spdx", &m.LicenseSPDX)
|
||||||
|
setStr("license_name", &m.LicenseName)
|
||||||
|
setStr("version_prefix", &m.VersionPrefix)
|
||||||
|
setStr("element_name", &m.ElementName)
|
||||||
|
setStr("platform", &m.Platform)
|
||||||
|
setStr("standards_version", &m.StandardsVersion)
|
||||||
|
setStr("standards_source", &m.StandardsSource)
|
||||||
|
setStr("maintainer", &m.Maintainer)
|
||||||
|
setStr("maintainer_url", &m.MaintainerURL)
|
||||||
|
setStr("info_url", &m.InfoURL)
|
||||||
|
setStr("target_version", &m.TargetVersion)
|
||||||
|
setStr("php_minimum", &m.PHPMinimum)
|
||||||
|
setStr("language", &m.Language)
|
||||||
|
setStr("extension_type", &m.ExtensionType)
|
||||||
|
setStr("entry_point", &m.EntryPoint)
|
||||||
|
setStr("deploy_host", &m.DeployHost)
|
||||||
|
setStr("deploy_port", &m.DeployPort)
|
||||||
|
setStr("deploy_user", &m.DeployUser)
|
||||||
|
setStr("deploy_path", &m.DeployPath)
|
||||||
|
setStr("docker_image", &m.DockerImage)
|
||||||
|
setStr("docker_registry", &m.DockerRegistry)
|
||||||
|
setStr("container_name", &m.ContainerName)
|
||||||
|
setStr("health_url", &m.HealthURL)
|
||||||
|
|
||||||
if err := repo_model.CreateOrUpdateRepoMetadata(ctx, m); err != nil {
|
if err := repo_model.CreateOrUpdateRepoMetadata(ctx, m); err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
@@ -151,5 +193,13 @@ func UpdateRepoMetadata(ctx *context.APIContext) {
|
|||||||
Language: m.Language,
|
Language: m.Language,
|
||||||
ExtensionType: m.ExtensionType,
|
ExtensionType: m.ExtensionType,
|
||||||
EntryPoint: m.EntryPoint,
|
EntryPoint: m.EntryPoint,
|
||||||
|
DeployHost: m.DeployHost,
|
||||||
|
DeployPort: m.DeployPort,
|
||||||
|
DeployUser: m.DeployUser,
|
||||||
|
DeployPath: m.DeployPath,
|
||||||
|
DockerImage: m.DockerImage,
|
||||||
|
DockerRegistry: m.DockerRegistry,
|
||||||
|
ContainerName: m.ContainerName,
|
||||||
|
HealthURL: m.HealthURL,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+140
-1
@@ -8,6 +8,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/git"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/git"
|
||||||
@@ -461,10 +462,148 @@ func ListPageRevisions(ctx *context.APIContext) {
|
|||||||
ctx.JSON(http.StatusOK, convert.ToWikiCommitList(commitsHistory, commitsCount))
|
ctx.JSON(http.StatusOK, convert.ToWikiCommitList(commitsHistory, commitsCount))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SearchWikiPages searches wiki page titles and content.
|
||||||
|
func SearchWikiPages(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/wiki/search repository repoSearchWikiPages
|
||||||
|
// ---
|
||||||
|
// summary: Search wiki pages
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: q
|
||||||
|
// in: query
|
||||||
|
// description: search query
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: page
|
||||||
|
// in: query
|
||||||
|
// description: page number of results to return (1-based)
|
||||||
|
// type: integer
|
||||||
|
// - name: limit
|
||||||
|
// in: query
|
||||||
|
// description: page size of results
|
||||||
|
// type: integer
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// description: "SearchResults"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
query := strings.TrimSpace(ctx.FormString("q"))
|
||||||
|
if query == "" {
|
||||||
|
ctx.JSON(http.StatusOK, []interface{}{})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wikiRepo, commit := findWikiRepoCommit(ctx)
|
||||||
|
if wikiRepo != nil {
|
||||||
|
defer wikiRepo.Close()
|
||||||
|
}
|
||||||
|
if ctx.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
queryLower := strings.ToLower(query)
|
||||||
|
|
||||||
|
type WikiSearchResult struct {
|
||||||
|
PageName string `json:"page_name"`
|
||||||
|
PageURL string `json:"page_url"`
|
||||||
|
Context string `json:"context,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
entries, err := commit.ListEntriesRecursiveFast()
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var results []WikiSearchResult
|
||||||
|
for _, entry := range entries {
|
||||||
|
if !entry.IsRegular() || !strings.HasSuffix(entry.Name(), ".md") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
baseName := strings.TrimSuffix(entry.Name(), ".md")
|
||||||
|
// Extract just the filename without path for special file check
|
||||||
|
parts := strings.Split(baseName, "/")
|
||||||
|
shortName := parts[len(parts)-1]
|
||||||
|
if shortName == "_Sidebar" || shortName == "_Footer" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
blob := entry.Blob()
|
||||||
|
content, err := blob.GetBlobContent(setting.UI.MaxDisplayFileSize)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
titleMatch := strings.Contains(strings.ToLower(baseName), queryLower)
|
||||||
|
contentMatch := strings.Contains(strings.ToLower(content), queryLower)
|
||||||
|
|
||||||
|
if !titleMatch && !contentMatch {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
contextLine := ""
|
||||||
|
if contentMatch {
|
||||||
|
for _, line := range strings.Split(content, "\n") {
|
||||||
|
if strings.Contains(strings.ToLower(line), queryLower) {
|
||||||
|
contextLine = strings.TrimSpace(line)
|
||||||
|
if len(contextLine) > 200 {
|
||||||
|
contextLine = contextLine[:200] + "..."
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wikiName, err := wiki_service.GitPathToWebPath(entry.Name())
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, displayName := wiki_service.WebPathToUserTitle(wikiName)
|
||||||
|
|
||||||
|
results = append(results, WikiSearchResult{
|
||||||
|
PageName: displayName,
|
||||||
|
PageURL: string(wikiName),
|
||||||
|
Context: contextLine,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
page := max(ctx.FormInt("page"), 1)
|
||||||
|
limit := ctx.FormInt("limit")
|
||||||
|
if limit <= 0 {
|
||||||
|
limit = setting.API.DefaultPagingNum
|
||||||
|
}
|
||||||
|
total := len(results)
|
||||||
|
start := (page - 1) * limit
|
||||||
|
end := start + limit
|
||||||
|
if start > total {
|
||||||
|
start = total
|
||||||
|
}
|
||||||
|
if end > total {
|
||||||
|
end = total
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetLinkHeader(int64(total), limit)
|
||||||
|
ctx.SetTotalCountHeader(int64(total))
|
||||||
|
ctx.JSON(http.StatusOK, results[start:end])
|
||||||
|
}
|
||||||
|
|
||||||
// findEntryForFile finds the tree entry for a target filepath.
|
// findEntryForFile finds the tree entry for a target filepath.
|
||||||
func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) {
|
func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) {
|
||||||
entry, err := commit.GetTreeEntryByPath(target)
|
entry, err := commit.GetTreeEntryByPath(target)
|
||||||
if err != nil {
|
if err != nil && !git.IsErrNotExist(err) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if entry != nil {
|
if entry != nil {
|
||||||
|
|||||||
@@ -103,6 +103,11 @@ func SettingsIssueStatusesDeletePost(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := issues_model.DeleteIssueStatusDef(ctx, id); err != nil {
|
if err := issues_model.DeleteIssueStatusDef(ctx, id); err != nil {
|
||||||
|
if issues_model.IsErrStatusRequired(err) {
|
||||||
|
ctx.Flash.Error("Cannot delete required status: " + def.Name)
|
||||||
|
ctx.Redirect(ctx.Org.OrgLink + "/settings/issue-statuses")
|
||||||
|
return
|
||||||
|
}
|
||||||
ctx.ServerError("DeleteIssueStatusDef", err)
|
ctx.ServerError("DeleteIssueStatusDef", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+73
-25
@@ -29,6 +29,14 @@ type OrgWikiPage struct {
|
|||||||
SubURL string
|
SubURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OrgWikiTreeNode represents a node in the org wiki folder tree for sidebar navigation.
|
||||||
|
type OrgWikiTreeNode struct {
|
||||||
|
Name string
|
||||||
|
SubURL string
|
||||||
|
IsDir bool
|
||||||
|
Children []*OrgWikiTreeNode
|
||||||
|
}
|
||||||
|
|
||||||
// Wiki renders the org wiki tab.
|
// Wiki renders the org wiki tab.
|
||||||
func Wiki(ctx *context.Context) {
|
func Wiki(ctx *context.Context) {
|
||||||
org := ctx.Org.Organization
|
org := ctx.Org.Organization
|
||||||
@@ -71,31 +79,9 @@ func Wiki(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
ctx.Data["WikiRepoLink"] = wikiRepo.Link()
|
ctx.Data["WikiRepoLink"] = wikiRepo.Link()
|
||||||
|
|
||||||
// Build page list from repo root.
|
// Build folder tree for sidebar navigation.
|
||||||
entries, err := commit.ListEntries()
|
wikiTree := buildOrgWikiTree(commit)
|
||||||
if err != nil {
|
ctx.Data["WikiTree"] = wikiTree
|
||||||
ctx.ServerError("ListEntries", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pages := make([]OrgWikiPage, 0, len(entries))
|
|
||||||
for _, entry := range entries {
|
|
||||||
if !entry.IsRegular() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
name := entry.Name()
|
|
||||||
if !isMarkdownFile(name) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
displayName := strings.TrimSuffix(name, path.Ext(name))
|
|
||||||
if strings.EqualFold(displayName, "_sidebar") || strings.EqualFold(displayName, "_footer") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
pages = append(pages, OrgWikiPage{
|
|
||||||
Name: displayName,
|
|
||||||
SubURL: displayName,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
ctx.Data["Pages"] = pages
|
|
||||||
|
|
||||||
// Determine which page to render.
|
// Determine which page to render.
|
||||||
pageName := ctx.PathParamRaw("*")
|
pageName := ctx.PathParamRaw("*")
|
||||||
@@ -157,6 +143,68 @@ func Wiki(ctx *context.Context) {
|
|||||||
ctx.HTML(http.StatusOK, tplOrgWiki)
|
ctx.HTML(http.StatusOK, tplOrgWiki)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// buildOrgWikiTree builds a hierarchical folder tree from the org wiki git repo.
|
||||||
|
// Shows up to 2 levels deep (folders and their immediate children).
|
||||||
|
func buildOrgWikiTree(commit *git.Commit) []*OrgWikiTreeNode {
|
||||||
|
if commit == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
entries, err := commit.ListEntries()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var topLevel []*OrgWikiTreeNode
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
name := entry.Name()
|
||||||
|
if entry.IsDir() {
|
||||||
|
node := &OrgWikiTreeNode{
|
||||||
|
Name: name,
|
||||||
|
SubURL: name,
|
||||||
|
IsDir: true,
|
||||||
|
}
|
||||||
|
// List children of this directory (1 level deep).
|
||||||
|
subTree := entry.Tree()
|
||||||
|
if subTree != nil {
|
||||||
|
children, _ := subTree.ListEntries()
|
||||||
|
for _, child := range children {
|
||||||
|
childName := child.Name()
|
||||||
|
if child.IsDir() {
|
||||||
|
node.Children = append(node.Children, &OrgWikiTreeNode{
|
||||||
|
Name: childName,
|
||||||
|
SubURL: name + "/" + childName,
|
||||||
|
IsDir: true,
|
||||||
|
})
|
||||||
|
} else if isMarkdownFile(childName) {
|
||||||
|
displayName := strings.TrimSuffix(childName, path.Ext(childName))
|
||||||
|
if strings.EqualFold(displayName, "_sidebar") || strings.EqualFold(displayName, "_footer") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
node.Children = append(node.Children, &OrgWikiTreeNode{
|
||||||
|
Name: displayName,
|
||||||
|
SubURL: name + "/" + displayName,
|
||||||
|
IsDir: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
topLevel = append(topLevel, node)
|
||||||
|
} else if isMarkdownFile(name) {
|
||||||
|
displayName := strings.TrimSuffix(name, path.Ext(name))
|
||||||
|
if strings.EqualFold(displayName, "_sidebar") || strings.EqualFold(displayName, "_footer") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
topLevel = append(topLevel, &OrgWikiTreeNode{
|
||||||
|
Name: displayName,
|
||||||
|
SubURL: displayName,
|
||||||
|
IsDir: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return topLevel
|
||||||
|
}
|
||||||
|
|
||||||
// findOrgWikiCommit locates the profile repo's wiki and returns its HEAD commit.
|
// findOrgWikiCommit locates the profile repo's wiki and returns its HEAD commit.
|
||||||
// The org wiki lives in the .wiki.git sidecar of the profile repo (e.g. .mokogitea.wiki.git).
|
// The org wiki lives in the .wiki.git sidecar of the profile repo (e.g. .mokogitea.wiki.git).
|
||||||
// Tries fallback repo names (.profile, .github) if the primary doesn't exist.
|
// Tries fallback repo names (.profile, .github) if the primary doesn't exist.
|
||||||
|
|||||||
@@ -123,6 +123,14 @@ func saveMetadata(ctx *context.Context) {
|
|||||||
manifest.Maintainer = existing.Maintainer
|
manifest.Maintainer = existing.Maintainer
|
||||||
manifest.MaintainerURL = existing.MaintainerURL
|
manifest.MaintainerURL = existing.MaintainerURL
|
||||||
manifest.Language = existing.Language
|
manifest.Language = existing.Language
|
||||||
|
manifest.DeployHost = existing.DeployHost
|
||||||
|
manifest.DeployPort = existing.DeployPort
|
||||||
|
manifest.DeployUser = existing.DeployUser
|
||||||
|
manifest.DeployPath = existing.DeployPath
|
||||||
|
manifest.DockerImage = existing.DockerImage
|
||||||
|
manifest.DockerRegistry = existing.DockerRegistry
|
||||||
|
manifest.ContainerName = existing.ContainerName
|
||||||
|
manifest.HealthURL = existing.HealthURL
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := repo_model.CreateOrUpdateRepoMetadata(ctx, manifest); err != nil {
|
if err := repo_model.CreateOrUpdateRepoMetadata(ctx, manifest); err != nil {
|
||||||
|
|||||||
+1096
-51
File diff suppressed because it is too large
Load Diff
@@ -28,6 +28,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<strong>{{.Name}}</strong>
|
<strong>{{.Name}}</strong>
|
||||||
|
{{if .IsRequired}}<span class="ui mini blue label" title="Required status - cannot be deleted">{{svg "octicon-lock" 10}} required</span>{{end}}
|
||||||
{{if not .IsActive}}<span class="ui mini grey label">{{ctx.Locale.Tr "org.settings.issue_status_inactive"}}</span>{{end}}
|
{{if not .IsActive}}<span class="ui mini grey label">{{ctx.Locale.Tr "org.settings.issue_status_inactive"}}</span>{{end}}
|
||||||
{{if .Description}}<br><small class="text grey">{{.Description}}</small>{{end}}
|
{{if .Description}}<br><small class="text grey">{{.Description}}</small>{{end}}
|
||||||
</td>
|
</td>
|
||||||
@@ -40,10 +41,14 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>{{.SortOrder}}</td>
|
<td>{{.SortOrder}}</td>
|
||||||
<td class="tw-text-right">
|
<td class="tw-text-right">
|
||||||
|
{{if .IsRequired}}
|
||||||
|
<span class="ui tiny icon button disabled" title="Required - cannot be deleted">{{svg "octicon-lock" 14}}</span>
|
||||||
|
{{else}}
|
||||||
<form method="post" action="{{$.OrgLink}}/settings/issue-statuses/{{.ID}}/delete" class="tw-inline">
|
<form method="post" action="{{$.OrgLink}}/settings/issue-statuses/{{.ID}}/delete" class="tw-inline">
|
||||||
{{$.CsrfTokenHtml}}
|
{{$.CsrfTokenHtml}}
|
||||||
<button class="ui tiny red icon button" type="submit" title="{{ctx.Locale.Tr "remove"}}">{{svg "octicon-trash" 14}}</button>
|
<button class="ui tiny red icon button" type="submit" title="{{ctx.Locale.Tr "remove"}}">{{svg "octicon-trash" 14}}</button>
|
||||||
</form>
|
</form>
|
||||||
|
{{end}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
This organization doesn't have a wiki yet.
|
This organization doesn't have a wiki yet.
|
||||||
</div>
|
</div>
|
||||||
<p class="tw-text-center">
|
<p class="tw-text-center">
|
||||||
Enable the wiki on the <code>.profile</code> (public) or <code>.profile-private</code> (members-only)
|
Enable the wiki on the <code>.mokogitea</code> (public) or <code>.mokogitea-private</code> (members-only)
|
||||||
repository to get started.
|
repository to get started.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -47,34 +47,59 @@
|
|||||||
<p>The page "{{.CurrentPage}}" does not exist in this wiki.</p>
|
<p>The page "{{.CurrentPage}}" does not exist in this wiki.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{if .Pages}}
|
{{if .WikiTree}}
|
||||||
<h4>Available pages:</h4>
|
<h4>Available pages:</h4>
|
||||||
<ul>
|
<ul>
|
||||||
{{range .Pages}}
|
{{range .WikiTree}}
|
||||||
|
{{if .IsDir}}
|
||||||
|
{{range .Children}}
|
||||||
<li><a href="{{$.Org.HomeLink}}/-/wiki/{{.SubURL}}">{{.Name}}</a></li>
|
<li><a href="{{$.Org.HomeLink}}/-/wiki/{{.SubURL}}">{{.Name}}</a></li>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{else}}
|
||||||
|
<li><a href="{{$.Org.HomeLink}}/-/wiki/{{.SubURL}}">{{.Name}}</a></li>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
</ul>
|
</ul>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="wiki-content-parts">
|
<div class="wiki-content-parts">
|
||||||
<div class="render-content markup wiki-content-main {{if or .WikiSidebarHTML .Pages}}with-sidebar{{end}}">
|
<div class="render-content markup wiki-content-main {{if or .WikiSidebarHTML .WikiTree}}with-sidebar{{end}}">
|
||||||
{{.WikiContent}}
|
{{.WikiContent}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{if or .WikiSidebarHTML .Pages}}
|
{{if or .WikiSidebarHTML .WikiTree}}
|
||||||
<div class="render-content markup wiki-content-sidebar">
|
<div class="render-content markup wiki-content-sidebar">
|
||||||
{{if .WikiSidebarHTML}}
|
{{if .WikiSidebarHTML}}
|
||||||
{{.WikiSidebarHTML}}
|
{{.WikiSidebarHTML}}
|
||||||
<div class="ui divider"></div>
|
{{else if .WikiTree}}
|
||||||
{{end}}
|
|
||||||
{{if .Pages}}
|
|
||||||
<strong>{{svg "octicon-list-unordered" 14}} Pages</strong>
|
<strong>{{svg "octicon-list-unordered" 14}} Pages</strong>
|
||||||
<ul class="wiki-tree-list">
|
<ul class="wiki-tree-list">
|
||||||
{{range .Pages}}
|
{{range .WikiTree}}
|
||||||
<li>
|
<li>
|
||||||
|
{{if .IsDir}}
|
||||||
|
<details open>
|
||||||
|
<summary>{{svg "octicon-file-directory" 14}} <strong>{{.Name}}</strong></summary>
|
||||||
|
{{if .Children}}
|
||||||
|
<ul>
|
||||||
|
{{range .Children}}
|
||||||
|
<li>
|
||||||
|
{{if .IsDir}}
|
||||||
|
{{svg "octicon-file-directory" 14}}
|
||||||
|
<a href="{{$.Org.HomeLink}}/-/wiki/{{.SubURL}}"><strong>{{.Name}}</strong></a>
|
||||||
|
{{else}}
|
||||||
{{svg "octicon-file" 14}}
|
{{svg "octicon-file" 14}}
|
||||||
<a href="{{$.Org.HomeLink}}/-/wiki/{{.SubURL}}" {{if eq $.CurrentPage .Name}}class="active"{{end}}>{{.Name}}</a>
|
<a href="{{$.Org.HomeLink}}/-/wiki/{{.SubURL}}" {{if eq $.CurrentPage .Name}}class="active"{{end}}>{{.Name}}</a>
|
||||||
|
{{end}}
|
||||||
|
</li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
{{end}}
|
||||||
|
</details>
|
||||||
|
{{else}}
|
||||||
|
{{svg "octicon-file" 14}}
|
||||||
|
<a href="{{$.Org.HomeLink}}/-/wiki/{{.SubURL}}" {{if eq $.CurrentPage .Name}}class="active"{{end}}>{{.Name}}</a>
|
||||||
|
{{end}}
|
||||||
</li>
|
</li>
|
||||||
{{end}}
|
{{end}}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -21,11 +21,7 @@
|
|||||||
<input name="org" value="{{.Manifest.Org}}" placeholder="Organization">
|
<input name="org" value="{{.Manifest.Org}}" placeholder="Organization">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="four fields">
|
<div class="three fields">
|
||||||
<div class="field">
|
|
||||||
<label>Version</label>
|
|
||||||
<input name="version" value="{{.Manifest.Version}}" placeholder="e.g. 06.00.00">
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Version Prefix</label>
|
<label>Version Prefix</label>
|
||||||
<input name="version_prefix" value="{{.Manifest.VersionPrefix}}" placeholder="e.g. v1.26.1-moko.">
|
<input name="version_prefix" value="{{.Manifest.VersionPrefix}}" placeholder="e.g. v1.26.1-moko.">
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
{{template "base/head" .}}
|
||||||
|
<div role="main" aria-label="{{.Title}}" class="page-content repository wiki">
|
||||||
|
{{template "repo/header" .}}
|
||||||
|
<div class="ui container">
|
||||||
|
<div class="repo-button-row">
|
||||||
|
<div class="tw-flex tw-items-center tw-gap-2">
|
||||||
|
<a class="ui small button" href="{{.RepoLink}}/wiki/{{.PageURL}}">
|
||||||
|
{{svg "octicon-arrow-left" 14}} Back to {{.title}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>{{svg "octicon-cross-reference" 20}} What links here: {{.title}}</h2>
|
||||||
|
|
||||||
|
{{if .Backlinks}}
|
||||||
|
<div class="ui relaxed divided list">
|
||||||
|
{{range .Backlinks}}
|
||||||
|
<div class="item">
|
||||||
|
<div class="content">
|
||||||
|
<a class="header" href="{{$.RepoLink}}/wiki/{{.PageURL}}">{{.PageName}}</a>
|
||||||
|
{{if .Context}}
|
||||||
|
<div class="description">
|
||||||
|
<code class="tw-text-sm">{{.Context}}</code>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<p class="tw-mt-4 text grey">{{.BacklinkCount}} {{if eq .BacklinkCount 1}}page{{else}}pages{{end}} linking here.</p>
|
||||||
|
{{else}}
|
||||||
|
<div class="ui placeholder segment">
|
||||||
|
<div class="ui icon header">
|
||||||
|
{{svg "octicon-unlink" 48}}
|
||||||
|
<br>
|
||||||
|
No pages link to "{{.title}}"
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "base/footer" .}}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{{template "base/head" .}}
|
||||||
|
<div role="main" aria-label="{{.Title}}" class="page-content repository wiki">
|
||||||
|
{{template "repo/header" .}}
|
||||||
|
<div class="ui container">
|
||||||
|
<div class="repo-button-row tw-flex tw-items-center tw-gap-2 tw-mb-4">
|
||||||
|
<div class="tw-flex-1">
|
||||||
|
<a class="ui small button" href="{{.RepoLink}}/wiki/">
|
||||||
|
{{svg "octicon-arrow-left" 14}} Back to wiki
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>{{svg "octicon-tag" 20}} Category: {{.CategoryName}}</h2>
|
||||||
|
|
||||||
|
{{if .CategoryPages}}
|
||||||
|
<div class="ui relaxed divided list">
|
||||||
|
{{range .CategoryPages}}
|
||||||
|
<div class="item">
|
||||||
|
{{svg "octicon-file" 14}}
|
||||||
|
<a href="{{$.RepoLink}}/wiki/{{.SubURL}}">{{.Name}}</a>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<p class="tw-mt-4 text grey">{{.CategoryCount}} {{if eq .CategoryCount 1}}page{{else}}pages{{end}} in this category.</p>
|
||||||
|
{{else}}
|
||||||
|
<div class="ui placeholder segment">
|
||||||
|
<div class="ui icon header">
|
||||||
|
{{svg "octicon-tag" 48}}
|
||||||
|
<br>
|
||||||
|
No pages in category "{{.CategoryName}}"
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "base/footer" .}}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
{{template "base/head" .}}
|
||||||
|
<div role="main" aria-label="{{.Title}}" class="page-content repository wiki">
|
||||||
|
{{template "repo/header" .}}
|
||||||
|
<div class="ui container">
|
||||||
|
<div class="repo-button-row tw-flex tw-items-center tw-gap-2 tw-mb-4">
|
||||||
|
<div class="tw-flex-1">
|
||||||
|
<a href="{{.RepoLink}}/wiki/{{.PageURL}}">{{svg "octicon-arrow-left" 14}} {{.title}}</a>
|
||||||
|
·
|
||||||
|
<a href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_revision">Revision history</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ui segment">
|
||||||
|
<h3>{{svg "octicon-diff" 20}} Changes in <code>{{.CommitID}}</code></h3>
|
||||||
|
<p>
|
||||||
|
<strong>{{.CommitAuthor}}</strong> — {{.CommitMessage}}
|
||||||
|
<br>
|
||||||
|
<small class="text grey">{{DateUtils.TimeSince .CommitWhen}}</small>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{{if .IsNewPage}}
|
||||||
|
<div class="ui info message">New page created</div>
|
||||||
|
{{else if .IsDeletedPage}}
|
||||||
|
<div class="ui warning message">Page deleted</div>
|
||||||
|
{{else if not .HasDiff}}
|
||||||
|
<div class="ui info message">No content changes in this revision</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if .HasDiff}}
|
||||||
|
<div class="diff-file-box" style="overflow-x: auto;">
|
||||||
|
<table class="chroma" style="width: 100%; border-collapse: collapse; font-family: monospace; font-size: 13px;">
|
||||||
|
{{range .DiffLines}}
|
||||||
|
<tr class="{{if eq .Type "add"}}diff-line-add{{else if eq .Type "del"}}diff-line-del{{else}}diff-line-context{{end}}">
|
||||||
|
<td style="width: 40px; text-align: right; padding: 0 8px; color: #999; user-select: none; {{if eq .Type "add"}}background: #e6ffec;{{else if eq .Type "del"}}background: #ffebe9;{{else}}background: #f6f8fa;{{end}}">
|
||||||
|
{{if .OldNum}}{{.OldNum}}{{end}}
|
||||||
|
</td>
|
||||||
|
<td style="width: 40px; text-align: right; padding: 0 8px; color: #999; user-select: none; {{if eq .Type "add"}}background: #e6ffec;{{else if eq .Type "del"}}background: #ffebe9;{{else}}background: #f6f8fa;{{end}}">
|
||||||
|
{{if .NewNum}}{{.NewNum}}{{end}}
|
||||||
|
</td>
|
||||||
|
<td style="padding: 0 8px; white-space: pre-wrap; word-break: break-all; {{if eq .Type "add"}}background: #e6ffec;{{else if eq .Type "del"}}background: #ffebe9;{{else}}background: #fff;{{end}}">
|
||||||
|
{{if eq .Type "add"}}+{{else if eq .Type "del"}}-{{else}} {{end}} {{.Content}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "base/footer" .}}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>{{.Title}}</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 40px 20px;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
h1 { font-size: 2em; border-bottom: 1px solid #eee; padding-bottom: 0.3em; }
|
||||||
|
h2 { font-size: 1.5em; border-bottom: 1px solid #eee; padding-bottom: 0.3em; }
|
||||||
|
code { background: #f6f8fa; padding: 2px 6px; border-radius: 3px; font-size: 85%; }
|
||||||
|
pre { background: #f6f8fa; padding: 16px; border-radius: 6px; overflow-x: auto; }
|
||||||
|
pre code { background: none; padding: 0; }
|
||||||
|
table { border-collapse: collapse; width: 100%; }
|
||||||
|
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
|
||||||
|
th { background: #f6f8fa; }
|
||||||
|
img { max-width: 100%; }
|
||||||
|
blockquote { border-left: 4px solid #ddd; margin: 0; padding: 0 16px; color: #666; }
|
||||||
|
a { color: #0366d6; }
|
||||||
|
@media print {
|
||||||
|
body { padding: 0; }
|
||||||
|
a { color: inherit; text-decoration: none; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>{{.Title}}</h1>
|
||||||
|
{{.WikiContentHTML}}
|
||||||
|
<hr>
|
||||||
|
<p style="font-size: 12px; color: #999;">
|
||||||
|
Printed from wiki · {{.Title}}
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
{{template "base/head" .}}
|
||||||
|
<div role="main" aria-label="{{.Title}}" class="page-content repository wiki">
|
||||||
|
{{template "repo/header" .}}
|
||||||
|
<div class="ui container">
|
||||||
|
<div class="repo-button-row tw-flex tw-items-center tw-gap-2 tw-mb-4">
|
||||||
|
<div class="tw-flex-1">
|
||||||
|
<h2>{{svg "octicon-history" 20}} Recent changes</h2>
|
||||||
|
</div>
|
||||||
|
<a class="ui small button" href="{{.RepoLink}}/wiki/">
|
||||||
|
{{svg "octicon-arrow-left" 14}} Back to wiki
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{if .RecentChanges}}
|
||||||
|
<table class="ui compact table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Page</th>
|
||||||
|
<th>Author</th>
|
||||||
|
<th>Edit summary</th>
|
||||||
|
<th>When</th>
|
||||||
|
<th>Commit</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{range .RecentChanges}}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{if .PageURL}}
|
||||||
|
{{svg "octicon-file" 14}}
|
||||||
|
<a href="{{$.RepoLink}}/wiki/{{.PageURL}}">{{.PageName}}</a>
|
||||||
|
{{else if .PageName}}
|
||||||
|
{{svg "octicon-file" 14}} {{.PageName}}
|
||||||
|
{{else}}
|
||||||
|
<span class="text grey">—</span>
|
||||||
|
{{end}}
|
||||||
|
</td>
|
||||||
|
<td>{{.Author}}</td>
|
||||||
|
<td class="gt-ellipsis" style="max-width: 400px;">{{.Message}}</td>
|
||||||
|
<td>{{DateUtils.TimeSince .When}}</td>
|
||||||
|
<td><code class="tw-text-xs">{{.SHA}}</code></td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="tw-flex tw-justify-between tw-mt-4">
|
||||||
|
{{if .HasPrevPage}}
|
||||||
|
<a class="ui small button" href="{{.RepoLink}}/wiki/?action=_recent&page={{Eval .CurrentPage "-" 1}}">
|
||||||
|
{{svg "octicon-chevron-left" 14}} Newer
|
||||||
|
</a>
|
||||||
|
{{else}}
|
||||||
|
<span></span>
|
||||||
|
{{end}}
|
||||||
|
{{if .HasNextPage}}
|
||||||
|
<a class="ui small button" href="{{.RepoLink}}/wiki/?action=_recent&page={{Eval .CurrentPage "+" 1}}">
|
||||||
|
Older {{svg "octicon-chevron-right" 14}}
|
||||||
|
</a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="ui placeholder segment">
|
||||||
|
<div class="ui icon header">
|
||||||
|
{{svg "octicon-history" 48}}
|
||||||
|
<br>
|
||||||
|
No recent changes
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "base/footer" .}}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
{{template "base/head" .}}
|
||||||
|
<div role="main" aria-label="{{.Title}}" class="page-content repository wiki">
|
||||||
|
{{template "repo/header" .}}
|
||||||
|
<div class="ui container">
|
||||||
|
<div class="repo-button-row">
|
||||||
|
<div class="tw-flex tw-items-center tw-gap-2">
|
||||||
|
<a class="ui small button" href="{{.RepoLink}}/wiki/">
|
||||||
|
{{svg "octicon-arrow-left" 14}} Back to wiki
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>{{svg "octicon-search" 20}} Wiki search</h2>
|
||||||
|
|
||||||
|
<form class="ui form tw-mb-4" action="{{.RepoLink}}/wiki/" method="get">
|
||||||
|
<input type="hidden" name="action" value="_search">
|
||||||
|
<div class="ui action input tw-w-full">
|
||||||
|
<input type="text" name="q" value="{{.Query}}" placeholder="Search wiki pages..." autofocus>
|
||||||
|
<button class="ui primary button" type="submit">{{svg "octicon-search" 14}} Search</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{{if .Query}}
|
||||||
|
{{if .Results}}
|
||||||
|
<div class="ui relaxed divided list">
|
||||||
|
{{range .Results}}
|
||||||
|
<div class="item">
|
||||||
|
<div class="content">
|
||||||
|
<a class="header" href="{{$.RepoLink}}/wiki/{{.PageURL}}">{{.PageName}}</a>
|
||||||
|
{{if .Context}}
|
||||||
|
<div class="description">
|
||||||
|
<code class="tw-text-sm">{{.Context}}</code>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<p class="tw-mt-4 text grey">{{.ResultCount}} {{if eq .ResultCount 1}}result{{else}}results{{end}} for "{{.Query}}"</p>
|
||||||
|
{{else}}
|
||||||
|
<div class="ui placeholder segment">
|
||||||
|
<div class="ui icon header">
|
||||||
|
{{svg "octicon-search" 48}}
|
||||||
|
<br>
|
||||||
|
No results for "{{.Query}}"
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "base/footer" .}}
|
||||||
@@ -20,6 +20,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="scrolling menu">
|
<div class="scrolling menu">
|
||||||
<a class="item muted" href="{{.RepoLink}}/wiki/?action=_pages">{{ctx.Locale.Tr "repo.wiki.pages"}}</a>
|
<a class="item muted" href="{{.RepoLink}}/wiki/?action=_pages">{{ctx.Locale.Tr "repo.wiki.pages"}}</a>
|
||||||
|
<a class="item muted" href="{{.RepoLink}}/wiki/?action=_search">{{svg "octicon-search" 14}} Search wiki</a>
|
||||||
|
<a class="item muted" href="{{.RepoLink}}/wiki/?action=_recent">{{svg "octicon-history" 14}} Recent changes</a>
|
||||||
|
t <a class="item muted" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_print" target="_blank">{{svg "octicon-browser" 14}} Print view</a>
|
||||||
|
<a class="item muted" href="{{.RepoLink}}/wiki/?action=_export&format=zip">{{svg "octicon-download" 14}} Export wiki (ZIP)</a>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
{{range .Pages}}
|
{{range .Pages}}
|
||||||
<a class="item {{if eq $.Title .Name}}selected{{end}}" href="{{$.RepoLink}}/wiki/{{.SubURL}}">{{.Name}}</a>
|
<a class="item {{if eq $.Title .Name}}selected{{end}}" href="{{$.RepoLink}}/wiki/{{.SubURL}}">{{.Name}}</a>
|
||||||
@@ -34,6 +38,8 @@
|
|||||||
<div class="flex-text-block tw-flex-wrap tw-justify-end">
|
<div class="flex-text-block tw-flex-wrap tw-justify-end">
|
||||||
<div class="flex-text-block tw-flex-1 tw-min-w-[300px]">
|
<div class="flex-text-block tw-flex-1 tw-min-w-[300px]">
|
||||||
<a class="ui basic button tw-px-3 tw-gap-3" title="{{ctx.Locale.Tr "repo.wiki.file_revision"}}" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_revision" >{{if .CommitCount}}<span>{{.CommitCount}}</span> {{end}}{{svg "octicon-history"}}</a>
|
<a class="ui basic button tw-px-3 tw-gap-3" title="{{ctx.Locale.Tr "repo.wiki.file_revision"}}" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_revision" >{{if .CommitCount}}<span>{{.CommitCount}}</span> {{end}}{{svg "octicon-history"}}</a>
|
||||||
|
<a class="ui basic button tw-px-3" title="What links here" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_backlinks">{{svg "octicon-cross-reference"}}</a>
|
||||||
|
{{if .LastCommitID}}<a class="ui basic button tw-px-3" title="View last change" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_diff&commit={{.LastCommitID}}">{{svg "octicon-diff"}}</a>{{end}}
|
||||||
<div class="tw-flex-1 gt-ellipsis">
|
<div class="tw-flex-1 gt-ellipsis">
|
||||||
{{$title}}
|
{{$title}}
|
||||||
<div class="ui sub header gt-ellipsis">
|
<div class="ui sub header gt-ellipsis">
|
||||||
@@ -47,7 +53,7 @@
|
|||||||
<a class="ui small button unescape-button tw-hidden" data-unicode-content-selector=".wiki-content-parts">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</a>
|
<a class="ui small button unescape-button tw-hidden" data-unicode-content-selector=".wiki-content-parts">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</a>
|
||||||
<a class="ui small button escape-button" data-unicode-content-selector=".wiki-content-parts">{{ctx.Locale.Tr "repo.escape_control_characters"}}</a>
|
<a class="ui small button escape-button" data-unicode-content-selector=".wiki-content-parts">{{ctx.Locale.Tr "repo.escape_control_characters"}}</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if and .CanWriteWiki (not .Repository.IsMirror)}}
|
{{if and .CanWriteWiki (not .Repository.IsMirror) (not .WikiFolderProtected)}}
|
||||||
<a class="ui small button" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_edit">{{ctx.Locale.Tr "repo.wiki.edit_page_button"}}</a>
|
<a class="ui small button" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_edit">{{ctx.Locale.Tr "repo.wiki.edit_page_button"}}</a>
|
||||||
<a class="ui small primary button" href="{{.RepoLink}}/wiki?action=_new">{{ctx.Locale.Tr "repo.wiki.new_page_button"}}</a>
|
<a class="ui small primary button" href="{{.RepoLink}}/wiki?action=_new">{{ctx.Locale.Tr "repo.wiki.new_page_button"}}</a>
|
||||||
<a class="ui small red button link-action" href data-modal-confirm="#repo-wiki-delete-page-modal" data-url="{{.RepoLink}}/wiki/{{.PageURL}}?action=_delete">{{ctx.Locale.Tr "repo.wiki.delete_page_button"}}</a>
|
<a class="ui small red button link-action" href data-modal-confirm="#repo-wiki-delete-page-modal" data-url="{{.RepoLink}}/wiki/{{.PageURL}}?action=_delete">{{ctx.Locale.Tr "repo.wiki.delete_page_button"}}</a>
|
||||||
@@ -69,6 +75,12 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
{{if .WikiFolderProtected}}
|
||||||
|
<div class="ui warning message">
|
||||||
|
<p>{{svg "octicon-lock" 14}} This page is in a protected folder. Only users with the required role can edit it.</p>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
{{if .FormatWarning}}
|
{{if .FormatWarning}}
|
||||||
<div class="ui negative message">
|
<div class="ui negative message">
|
||||||
<p>{{.FormatWarning}}</p>
|
<p>{{.FormatWarning}}</p>
|
||||||
@@ -103,13 +115,30 @@
|
|||||||
<div class="wiki-content-parts">
|
<div class="wiki-content-parts">
|
||||||
{{if .WikiSidebarTocHTML}}
|
{{if .WikiSidebarTocHTML}}
|
||||||
<div class="render-content markup wiki-content-sidebar wiki-content-toc">
|
<div class="render-content markup wiki-content-sidebar wiki-content-toc">
|
||||||
|
<details open>
|
||||||
|
<summary><strong>{{svg "octicon-list-unordered" 14}} Contents</strong></summary>
|
||||||
{{.WikiSidebarTocHTML}}
|
{{.WikiSidebarTocHTML}}
|
||||||
|
</details>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<div class="render-content markup wiki-content-main {{if or .WikiSidebarTocHTML .WikiSidebarHTML .WikiTree}}with-sidebar{{end}}">
|
<div class="render-content markup wiki-content-main {{if or .WikiSidebarTocHTML .WikiSidebarHTML .WikiTree}}with-sidebar{{end}}">
|
||||||
{{template "repo/unicode_escape_prompt" dict "EscapeStatus" .EscapeStatus}}
|
{{template "repo/unicode_escape_prompt" dict "EscapeStatus" .EscapeStatus}}
|
||||||
|
{{if .WikiInlineTocHTML}}
|
||||||
|
<details open class="wiki-toc-inline tw-mb-4">
|
||||||
|
<summary><strong>{{svg "octicon-list-unordered" 14}} Contents</strong></summary>
|
||||||
|
{{.WikiInlineTocHTML}}
|
||||||
|
</details>
|
||||||
|
{{end}}
|
||||||
{{.WikiContentHTML}}
|
{{.WikiContentHTML}}
|
||||||
|
{{if .WikiCategories}}
|
||||||
|
<div class="tw-mt-4 tw-pt-2" style="border-top: 1px solid var(--color-secondary);">
|
||||||
|
{{svg "octicon-tag" 14}} Categories:
|
||||||
|
{{range .WikiCategories}}
|
||||||
|
<a class="ui small label" href="{{$.RepoLink}}/wiki/?action=_category&name={{.}}">{{.}}</a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{if .WikiTree}}
|
{{if .WikiTree}}
|
||||||
@@ -121,6 +150,7 @@
|
|||||||
{{if .IsDir}}
|
{{if .IsDir}}
|
||||||
{{svg "octicon-file-directory" 14}}
|
{{svg "octicon-file-directory" 14}}
|
||||||
<a href="{{$.RepoLink}}/wiki/{{.SubURL}}"><strong>{{.Name}}</strong></a>
|
<a href="{{$.RepoLink}}/wiki/{{.SubURL}}"><strong>{{.Name}}</strong></a>
|
||||||
|
{{if .Protected}}{{svg "octicon-lock" 12}}{{end}}
|
||||||
{{if .Children}}
|
{{if .Children}}
|
||||||
<ul>
|
<ul>
|
||||||
{{range .Children}}
|
{{range .Children}}
|
||||||
@@ -128,6 +158,7 @@
|
|||||||
{{if .IsDir}}
|
{{if .IsDir}}
|
||||||
{{svg "octicon-file-directory" 14}}
|
{{svg "octicon-file-directory" 14}}
|
||||||
<a href="{{$.RepoLink}}/wiki/{{.SubURL}}"><strong>{{.Name}}</strong></a>
|
<a href="{{$.RepoLink}}/wiki/{{.SubURL}}"><strong>{{.Name}}</strong></a>
|
||||||
|
{{if .Protected}}{{svg "octicon-lock" 12}}{{end}}
|
||||||
{{else}}
|
{{else}}
|
||||||
{{svg "octicon-file" 14}}
|
{{svg "octicon-file" 14}}
|
||||||
<a href="{{$.RepoLink}}/wiki/{{.SubURL}}" {{if eq $.PageURL .SubURL}}class="active"{{end}}>{{.Name}}</a>
|
<a href="{{$.RepoLink}}/wiki/{{.SubURL}}" {{if eq $.PageURL .SubURL}}class="active"{{end}}>{{.Name}}</a>
|
||||||
|
|||||||
@@ -86,3 +86,34 @@
|
|||||||
max-width: unset;
|
max-width: unset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Wikilinks: red links for non-existent pages */
|
||||||
|
.wiki .wiki-link-new {
|
||||||
|
color: var(--color-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki .wiki-link-new:hover {
|
||||||
|
color: var(--color-red);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wiki inline ToC */
|
||||||
|
.wiki .wiki-toc-inline {
|
||||||
|
border: 1px solid var(--color-secondary);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: var(--color-box-body);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki .wiki-toc-inline summary {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sticky sidebar ToC */
|
||||||
|
.wiki .wiki-content-toc {
|
||||||
|
position: sticky;
|
||||||
|
top: 16px;
|
||||||
|
max-height: calc(100vh - 100px);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user