Compare commits
1 Commits
rc
..
development
| Author | SHA1 | Date | |
|---|---|---|---|
| add7c0da4d |
@@ -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,7 +21,7 @@
|
|||||||
# | 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"
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ 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
|
||||||
@@ -149,7 +149,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,47 +241,11 @@ 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 }}"
|
echo "tag=stable" >> "$GITHUB_OUTPUT"
|
||||||
if [[ "$PLATFORM" == joomla* ]]; then
|
echo "release_tag=stable" >> "$GITHUB_OUTPUT"
|
||||||
echo "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}"
|
||||||
|
|||||||
@@ -88,20 +88,8 @@ 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
|
||||||
@@ -178,7 +166,6 @@ 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 }}"
|
||||||
@@ -189,7 +176,6 @@ 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 }}"
|
||||||
@@ -226,7 +212,6 @@ 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 }}"
|
||||||
@@ -240,7 +225,6 @@ 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}"
|
||||||
|
|||||||
@@ -10,25 +10,6 @@
|
|||||||
- 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
|
|
||||||
- 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)
|
|
||||||
|
|
||||||
## [06.19.00] --- 2026-06-20
|
## [06.19.00] --- 2026-06-20
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ 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'"`
|
||||||
@@ -57,15 +56,14 @@ 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: "Open", Color: "#2563eb", Description: "New or active issue", ClosesIssue: false, IsRequired: true, SortOrder: 0, IsActive: true},
|
{OrgID: orgID, Name: "In Progress", Color: "#2563eb", Description: "Work is actively being done", SortOrder: 1, IsActive: true},
|
||||||
{OrgID: orgID, Name: "In Progress", Color: "#7c3aed", Description: "Work is actively being done", SortOrder: 1, IsActive: true},
|
{OrgID: orgID, Name: "Needs Info", Color: "#f59e0b", Description: "Waiting for more information", SortOrder: 2, IsActive: true},
|
||||||
{OrgID: orgID, Name: "Waiting", Color: "#f59e0b", Description: "Blocked or waiting for input", SortOrder: 2, IsActive: true},
|
{OrgID: orgID, Name: "Blocked", Color: "#dc2626", Description: "Cannot proceed due to dependency", SortOrder: 3, IsActive: true},
|
||||||
{OrgID: orgID, Name: "In Review", Color: "#0891b2", Description: "PR submitted, awaiting review", SortOrder: 3, IsActive: true},
|
{OrgID: orgID, Name: "Resolved", Color: "#16a34a", Description: "Fix implemented and verified", ClosesIssue: true, SortOrder: 4, 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 {
|
||||||
@@ -113,37 +111,13 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -165,7 +165,6 @@ 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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,19 +11,6 @@ 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
|
||||||
@@ -47,10 +34,6 @@ 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)
|
||||||
@@ -64,7 +47,6 @@ 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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -94,10 +76,6 @@ 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)
|
||||||
@@ -140,10 +118,6 @@ 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)
|
||||||
|
|||||||
@@ -103,11 +103,6 @@ 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
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-73
@@ -29,14 +29,6 @@ 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
|
||||||
@@ -79,9 +71,31 @@ func Wiki(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
ctx.Data["WikiRepoLink"] = wikiRepo.Link()
|
ctx.Data["WikiRepoLink"] = wikiRepo.Link()
|
||||||
|
|
||||||
// Build folder tree for sidebar navigation.
|
// Build page list from repo root.
|
||||||
wikiTree := buildOrgWikiTree(commit)
|
entries, err := commit.ListEntries()
|
||||||
ctx.Data["WikiTree"] = wikiTree
|
if err != nil {
|
||||||
|
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("*")
|
||||||
@@ -143,68 +157,6 @@ 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.
|
||||||
|
|||||||
+6
-540
@@ -6,14 +6,11 @@ package repo
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"html"
|
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/renderhelper"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/renderhelper"
|
||||||
@@ -41,14 +38,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
tplWikiStart templates.TplName = "repo/wiki/start"
|
tplWikiStart templates.TplName = "repo/wiki/start"
|
||||||
tplWikiView templates.TplName = "repo/wiki/view"
|
tplWikiView templates.TplName = "repo/wiki/view"
|
||||||
tplWikiRevision templates.TplName = "repo/wiki/revision"
|
tplWikiRevision templates.TplName = "repo/wiki/revision"
|
||||||
tplWikiNew templates.TplName = "repo/wiki/new"
|
tplWikiNew templates.TplName = "repo/wiki/new"
|
||||||
tplWikiPages templates.TplName = "repo/wiki/pages"
|
tplWikiPages templates.TplName = "repo/wiki/pages"
|
||||||
tplWikiBacklinks templates.TplName = "repo/wiki/backlinks"
|
|
||||||
tplWikiRecentChanges templates.TplName = "repo/wiki/recent"
|
|
||||||
tplWikiDiff templates.TplName = "repo/wiki/diff"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MustEnableWiki check if wiki is enabled, if external then redirect
|
// MustEnableWiki check if wiki is enabled, if external then redirect
|
||||||
@@ -307,14 +301,6 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for redirect frontmatter: ---\nredirect: target\n---
|
|
||||||
if target := extractWikiRedirect(data); target != "" {
|
|
||||||
redirectURL := ctx.Repo.RepoLink + "/wiki/" + wiki_service.WebPathToURLPath(wiki_service.WebPath(target))
|
|
||||||
ctx.Flash.Info("Redirected from " + displayName)
|
|
||||||
ctx.Redirect(redirectURL)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
rctx := renderhelper.NewRenderContextRepoWiki(ctx, ctx.Repo.Repository)
|
rctx := renderhelper.NewRenderContextRepoWiki(ctx, ctx.Repo.Repository)
|
||||||
|
|
||||||
renderFn := func(data []byte) (escaped *charset.EscapeStatus, output template.HTML, err error) {
|
renderFn := func(data []byte) (escaped *charset.EscapeStatus, output template.HTML, err error) {
|
||||||
@@ -335,9 +321,6 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
|
|||||||
return escaped, output, err
|
return escaped, output, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preprocess wikilinks: [[Page Name]] → HTML links before markdown rendering.
|
|
||||||
data = preprocessWikilinks(data, commit, ctx.Repo.RepoLink+"/wiki/")
|
|
||||||
|
|
||||||
ctx.Data["EscapeStatus"], ctx.Data["WikiContentHTML"], err = renderFn(data)
|
ctx.Data["EscapeStatus"], ctx.Data["WikiContentHTML"], err = renderFn(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("Render", err)
|
ctx.ServerError("Render", err)
|
||||||
@@ -374,15 +357,10 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get commit count and last commit for wiki revisions
|
// get commit count - wiki revisions
|
||||||
commitsCount, _ := gitrepo.FileCommitsCount(ctx, ctx.Repo.Repository.WikiStorageRepo(), ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
|
commitsCount, _ := gitrepo.FileCommitsCount(ctx, ctx.Repo.Repository.WikiStorageRepo(), ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
|
||||||
ctx.Data["CommitCount"] = commitsCount
|
ctx.Data["CommitCount"] = commitsCount
|
||||||
|
|
||||||
// Pass last commit ID for diff link
|
|
||||||
if lastCommit, err := wikiGitRepo.GetCommitByPath(pageFilename); err == nil && lastCommit != nil {
|
|
||||||
ctx.Data["LastCommitID"] = lastCommit.ID.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return wikiGitRepo, entry
|
return wikiGitRepo, entry
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -526,15 +504,6 @@ func Wiki(ctx *context.Context) {
|
|||||||
case "_revision":
|
case "_revision":
|
||||||
WikiRevision(ctx)
|
WikiRevision(ctx)
|
||||||
return
|
return
|
||||||
case "_backlinks":
|
|
||||||
WikiBacklinks(ctx)
|
|
||||||
return
|
|
||||||
case "_recent":
|
|
||||||
WikiRecentChanges(ctx)
|
|
||||||
return
|
|
||||||
case "_diff":
|
|
||||||
WikiDiff(ctx)
|
|
||||||
return
|
|
||||||
case "_edit":
|
case "_edit":
|
||||||
if !ctx.Repo.Permission.CanWrite(unit.TypeWiki) {
|
if !ctx.Repo.Permission.CanWrite(unit.TypeWiki) {
|
||||||
ctx.NotFound(nil)
|
ctx.NotFound(nil)
|
||||||
@@ -592,376 +561,6 @@ func Wiki(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WikiRevision renders file revision list of wiki page
|
// WikiRevision renders file revision list of wiki page
|
||||||
// WikiBacklinks shows all wiki pages that link to the current page.
|
|
||||||
func WikiBacklinks(ctx *context.Context) {
|
|
||||||
ctx.Data["CanWriteWiki"] = ctx.Repo.Permission.CanWrite(unit.TypeWiki) && !ctx.Repo.Repository.IsArchived
|
|
||||||
|
|
||||||
if !repo_service.HasWiki(ctx, ctx.Repo.Repository) {
|
|
||||||
ctx.Data["Title"] = ctx.Tr("repo.wiki")
|
|
||||||
ctx.HTML(http.StatusOK, tplWikiStart)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, commit, err := findWikiRepoCommit(ctx)
|
|
||||||
if err != nil {
|
|
||||||
if !git.IsErrNotExist(err) {
|
|
||||||
ctx.ServerError("findWikiRepoCommit", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
|
|
||||||
if len(pageName) == 0 {
|
|
||||||
pageName = "Home"
|
|
||||||
}
|
|
||||||
|
|
||||||
_, displayName := wiki_service.WebPathToUserTitle(pageName)
|
|
||||||
ctx.Data["Title"] = "What links here: " + displayName
|
|
||||||
ctx.Data["PageURL"] = wiki_service.WebPathToURLPath(pageName)
|
|
||||||
ctx.Data["title"] = displayName
|
|
||||||
|
|
||||||
searchTerms := []string{
|
|
||||||
string(pageName), // raw path
|
|
||||||
wiki_service.WebPathToURLPath(wiki_service.WebPath(pageName)), // URL-encoded path
|
|
||||||
displayName, // display name
|
|
||||||
}
|
|
||||||
|
|
||||||
type BacklinkResult struct {
|
|
||||||
PageName string
|
|
||||||
PageURL string
|
|
||||||
Context string
|
|
||||||
}
|
|
||||||
|
|
||||||
seen := make(map[string]bool)
|
|
||||||
var backlinks []BacklinkResult
|
|
||||||
|
|
||||||
entries, _ := commit.ListEntries()
|
|
||||||
for _, entry := range entries {
|
|
||||||
if !entry.IsRegular() || !strings.HasSuffix(entry.Name(), ".md") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
entryName := strings.TrimSuffix(entry.Name(), ".md")
|
|
||||||
if entryName == string(pageName) || entryName == "_Sidebar" || entryName == "_Footer" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
blob := entry.Blob()
|
|
||||||
content, err := blob.GetBlobContent(setting.UI.MaxDisplayFileSize)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, term := range searchTerms {
|
|
||||||
if strings.Contains(content, term) && !seen[entryName] {
|
|
||||||
seen[entryName] = true
|
|
||||||
// Extract a line of context around the match
|
|
||||||
contextLine := ""
|
|
||||||
for _, line := range strings.Split(content, "\n") {
|
|
||||||
if strings.Contains(line, term) {
|
|
||||||
contextLine = strings.TrimSpace(line)
|
|
||||||
if len(contextLine) > 200 {
|
|
||||||
contextLine = contextLine[:200] + "..."
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wpName, _ := wiki_service.GitPathToWebPath(entry.Name())
|
|
||||||
_, linkDisplay := wiki_service.WebPathToUserTitle(wpName)
|
|
||||||
backlinks = append(backlinks, BacklinkResult{
|
|
||||||
PageName: linkDisplay,
|
|
||||||
PageURL: string(wpName),
|
|
||||||
Context: contextLine,
|
|
||||||
})
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also search subdirectories (1 level deep)
|
|
||||||
for _, entry := range entries {
|
|
||||||
if !entry.IsDir() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
subTree := entry.Tree()
|
|
||||||
if subTree == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
children, _ := subTree.ListEntries()
|
|
||||||
for _, child := range children {
|
|
||||||
if !child.IsRegular() || !strings.HasSuffix(child.Name(), ".md") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fullPath := entry.Name() + "/" + strings.TrimSuffix(child.Name(), ".md")
|
|
||||||
if fullPath == string(pageName) || seen[fullPath] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
childName := strings.TrimSuffix(child.Name(), ".md")
|
|
||||||
if childName == "_Sidebar" || childName == "_Footer" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
blob := child.Blob()
|
|
||||||
content, err := blob.GetBlobContent(setting.UI.MaxDisplayFileSize)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, term := range searchTerms {
|
|
||||||
if strings.Contains(content, term) && !seen[fullPath] {
|
|
||||||
seen[fullPath] = true
|
|
||||||
contextLine := ""
|
|
||||||
for _, line := range strings.Split(content, "\n") {
|
|
||||||
if strings.Contains(line, term) {
|
|
||||||
contextLine = strings.TrimSpace(line)
|
|
||||||
if len(contextLine) > 200 {
|
|
||||||
contextLine = contextLine[:200] + "..."
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wpChild, _ := wiki_service.GitPathToWebPath(child.Name())
|
|
||||||
_, childDisplay := wiki_service.WebPathToUserTitle(wpChild)
|
|
||||||
backlinks = append(backlinks, BacklinkResult{
|
|
||||||
PageName: childDisplay,
|
|
||||||
PageURL: entry.Name() + "/" + string(wpChild),
|
|
||||||
Context: contextLine,
|
|
||||||
})
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Data["Backlinks"] = backlinks
|
|
||||||
ctx.Data["BacklinkCount"] = len(backlinks)
|
|
||||||
ctx.HTML(http.StatusOK, tplWikiBacklinks)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WikiRecentChanges shows all recent wiki edits across all pages.
|
|
||||||
func WikiRecentChanges(ctx *context.Context) {
|
|
||||||
ctx.Data["CanWriteWiki"] = ctx.Repo.Permission.CanWrite(unit.TypeWiki) && !ctx.Repo.Repository.IsArchived
|
|
||||||
ctx.Data["Title"] = "Recent changes"
|
|
||||||
|
|
||||||
if !repo_service.HasWiki(ctx, ctx.Repo.Repository) {
|
|
||||||
ctx.Data["Title"] = ctx.Tr("repo.wiki")
|
|
||||||
ctx.HTML(http.StatusOK, tplWikiStart)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
wikiGitRepo, _, err := findWikiRepoCommit(ctx)
|
|
||||||
if err != nil {
|
|
||||||
if !git.IsErrNotExist(err) {
|
|
||||||
ctx.ServerError("findWikiRepoCommit", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
page := ctx.FormInt("page")
|
|
||||||
if page <= 0 {
|
|
||||||
page = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
branch := ctx.Repo.Repository.DefaultWikiBranch
|
|
||||||
if branch == "" {
|
|
||||||
branch = "main"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all commits (no file filter = all wiki changes).
|
|
||||||
commits, err := wikiGitRepo.CommitsByFileAndRange(
|
|
||||||
git.CommitsByFileAndRangeOptions{
|
|
||||||
Revision: branch,
|
|
||||||
Page: page,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("CommitsByFileAndRange", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type RecentChange struct {
|
|
||||||
PageName string
|
|
||||||
PageURL string
|
|
||||||
Author string
|
|
||||||
Message string
|
|
||||||
When timeutil.TimeStamp
|
|
||||||
SHA string
|
|
||||||
}
|
|
||||||
|
|
||||||
var changes []RecentChange
|
|
||||||
for _, commit := range commits {
|
|
||||||
// Get files changed by comparing to parent
|
|
||||||
var changedFiles []string
|
|
||||||
parents := commit.Parents
|
|
||||||
if len(parents) > 0 {
|
|
||||||
changedFiles, _ = commit.GetFilesChangedSinceCommit(parents[0].String())
|
|
||||||
}
|
|
||||||
|
|
||||||
pageNames := make([]string, 0)
|
|
||||||
for _, f := range changedFiles {
|
|
||||||
if strings.HasSuffix(f, ".md") {
|
|
||||||
name := strings.TrimSuffix(f, ".md")
|
|
||||||
if name != "_Sidebar" && name != "_Footer" {
|
|
||||||
pageNames = append(pageNames, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
displayPage := ""
|
|
||||||
pageURL := ""
|
|
||||||
if len(pageNames) == 1 {
|
|
||||||
displayPage = pageNames[0]
|
|
||||||
pageURL = strings.ReplaceAll(pageNames[0], " ", "-")
|
|
||||||
} else if len(pageNames) > 1 {
|
|
||||||
displayPage = pageNames[0] + " (+" + strconv.Itoa(len(pageNames)-1) + " more)"
|
|
||||||
pageURL = strings.ReplaceAll(pageNames[0], " ", "-")
|
|
||||||
} else if len(changedFiles) > 0 {
|
|
||||||
// Non-markdown files changed (images, etc.)
|
|
||||||
displayPage = changedFiles[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := commit.MessageTitle()
|
|
||||||
|
|
||||||
changes = append(changes, RecentChange{
|
|
||||||
PageName: displayPage,
|
|
||||||
PageURL: pageURL,
|
|
||||||
Author: commit.Author.Name,
|
|
||||||
Message: msg,
|
|
||||||
When: timeutil.TimeStamp(commit.Author.When.Unix()),
|
|
||||||
SHA: commit.ID.String()[:10],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Data["RecentChanges"] = changes
|
|
||||||
ctx.Data["CurrentPage"] = page
|
|
||||||
ctx.Data["HasNextPage"] = len(commits) >= setting.Git.CommitsRangeSize
|
|
||||||
ctx.Data["HasPrevPage"] = page > 1
|
|
||||||
|
|
||||||
ctx.HTML(http.StatusOK, tplWikiRecentChanges)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WikiDiff shows the diff between a commit and its parent for a wiki page.
|
|
||||||
func WikiDiff(ctx *context.Context) {
|
|
||||||
ctx.Data["CanWriteWiki"] = ctx.Repo.Permission.CanWrite(unit.TypeWiki) && !ctx.Repo.Repository.IsArchived
|
|
||||||
|
|
||||||
if !repo_service.HasWiki(ctx, ctx.Repo.Repository) {
|
|
||||||
ctx.Data["Title"] = ctx.Tr("repo.wiki")
|
|
||||||
ctx.HTML(http.StatusOK, tplWikiStart)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
wikiGitRepo, _, err := findWikiRepoCommit(ctx)
|
|
||||||
if err != nil {
|
|
||||||
if !git.IsErrNotExist(err) {
|
|
||||||
ctx.ServerError("findWikiRepoCommit", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
commitID := ctx.FormString("commit")
|
|
||||||
if commitID == "" {
|
|
||||||
ctx.Redirect(ctx.Repo.RepoLink + "/wiki/")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
|
|
||||||
if len(pageName) == 0 {
|
|
||||||
pageName = "Home"
|
|
||||||
}
|
|
||||||
_, displayName := wiki_service.WebPathToUserTitle(pageName)
|
|
||||||
ctx.Data["Title"] = "Diff: " + displayName
|
|
||||||
ctx.Data["PageURL"] = wiki_service.WebPathToURLPath(pageName)
|
|
||||||
ctx.Data["title"] = displayName
|
|
||||||
|
|
||||||
commit, err := wikiGitRepo.GetCommit(commitID)
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("GetCommit", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Data["CommitID"] = commit.ID.String()[:10]
|
|
||||||
ctx.Data["CommitMessage"] = commit.MessageTitle()
|
|
||||||
ctx.Data["CommitAuthor"] = commit.Author.Name
|
|
||||||
ctx.Data["CommitWhen"] = timeutil.TimeStamp(commit.Author.When.Unix())
|
|
||||||
|
|
||||||
// Get the file path for this wiki page
|
|
||||||
wikiPath := string(pageName) + ".md"
|
|
||||||
|
|
||||||
// Get content at this commit
|
|
||||||
blob, _ := commit.GetBlobByPath(wikiPath)
|
|
||||||
newContent := ""
|
|
||||||
if blob != nil {
|
|
||||||
newContent, _ = blob.GetBlobContent(setting.UI.MaxDisplayFileSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get content at parent commit
|
|
||||||
oldContent := ""
|
|
||||||
if len(commit.Parents) > 0 {
|
|
||||||
parentCommit, err := wikiGitRepo.GetCommit(commit.Parents[0].String())
|
|
||||||
if err == nil {
|
|
||||||
parentBlob, _ := parentCommit.GetBlobByPath(wikiPath)
|
|
||||||
if parentBlob != nil {
|
|
||||||
oldContent, _ = parentBlob.GetBlobContent(setting.UI.MaxDisplayFileSize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build a simple line-by-line diff
|
|
||||||
type DiffLine struct {
|
|
||||||
Type string // "add", "del", "ctx"
|
|
||||||
Content string
|
|
||||||
OldNum int
|
|
||||||
NewNum int
|
|
||||||
}
|
|
||||||
|
|
||||||
oldLines := strings.Split(oldContent, "\n")
|
|
||||||
newLines := strings.Split(newContent, "\n")
|
|
||||||
|
|
||||||
var diffLines []DiffLine
|
|
||||||
|
|
||||||
// Simple diff: show removed lines then added lines
|
|
||||||
// For a proper diff we'd use a real diff algorithm, but this gives useful output
|
|
||||||
oldSet := make(map[string]bool)
|
|
||||||
newSet := make(map[string]bool)
|
|
||||||
for _, l := range oldLines {
|
|
||||||
oldSet[l] = true
|
|
||||||
}
|
|
||||||
for _, l := range newLines {
|
|
||||||
newSet[l] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
oldNum := 0
|
|
||||||
newNum := 0
|
|
||||||
maxLines := len(oldLines)
|
|
||||||
if len(newLines) > maxLines {
|
|
||||||
maxLines = len(newLines)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk through both files showing context, deletions, and additions
|
|
||||||
for i := 0; i < maxLines; i++ {
|
|
||||||
if i < len(oldLines) && i < len(newLines) && oldLines[i] == newLines[i] {
|
|
||||||
oldNum++
|
|
||||||
newNum++
|
|
||||||
diffLines = append(diffLines, DiffLine{Type: "ctx", Content: oldLines[i], OldNum: oldNum, NewNum: newNum})
|
|
||||||
} else {
|
|
||||||
if i < len(oldLines) {
|
|
||||||
oldNum++
|
|
||||||
diffLines = append(diffLines, DiffLine{Type: "del", Content: oldLines[i], OldNum: oldNum})
|
|
||||||
}
|
|
||||||
if i < len(newLines) {
|
|
||||||
newNum++
|
|
||||||
diffLines = append(diffLines, DiffLine{Type: "add", Content: newLines[i], NewNum: newNum})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Data["DiffLines"] = diffLines
|
|
||||||
ctx.Data["HasDiff"] = oldContent != newContent
|
|
||||||
ctx.Data["IsNewPage"] = oldContent == ""
|
|
||||||
ctx.Data["IsDeletedPage"] = newContent == ""
|
|
||||||
|
|
||||||
ctx.HTML(http.StatusOK, tplWikiDiff)
|
|
||||||
}
|
|
||||||
|
|
||||||
func WikiRevision(ctx *context.Context) {
|
func WikiRevision(ctx *context.Context) {
|
||||||
ctx.Data["CanWriteWiki"] = ctx.Repo.Permission.CanWrite(unit.TypeWiki) && !ctx.Repo.Repository.IsArchived
|
ctx.Data["CanWriteWiki"] = ctx.Repo.Permission.CanWrite(unit.TypeWiki) && !ctx.Repo.Repository.IsArchived
|
||||||
|
|
||||||
@@ -1191,13 +790,6 @@ func EditWikiPost(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If page was renamed, create a redirect at the old path.
|
|
||||||
if oldWikiName != newWikiName {
|
|
||||||
_, newDisplay := wiki_service.WebPathToUserTitle(newWikiName)
|
|
||||||
redirectContent := "---\nredirect: " + string(newWikiName) + "\n---\nThis page has moved to [" + newDisplay + "](" + wiki_service.WebPathToURLPath(newWikiName) + ").\n"
|
|
||||||
_ = wiki_service.AddWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, oldWikiName, redirectContent, "redirect: "+string(oldWikiName)+" → "+string(newWikiName))
|
|
||||||
}
|
|
||||||
|
|
||||||
notify_service.EditWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(newWikiName), form.Message)
|
notify_service.EditWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(newWikiName), form.Message)
|
||||||
|
|
||||||
ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + wiki_service.WebPathToURLPath(newWikiName))
|
ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + wiki_service.WebPathToURLPath(newWikiName))
|
||||||
@@ -1237,132 +829,6 @@ func buildWikiBreadcrumbs(pageName wiki_service.WebPath) []WikiBreadcrumb {
|
|||||||
return crumbs
|
return crumbs
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractWikiRedirect checks if page content starts with YAML frontmatter containing
|
|
||||||
// a "redirect:" field. Returns the target page path or empty string.
|
|
||||||
func extractWikiRedirect(data []byte) string {
|
|
||||||
content := string(data)
|
|
||||||
if !strings.HasPrefix(content, "---\n") {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
endIdx := strings.Index(content[4:], "\n---")
|
|
||||||
if endIdx < 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
frontmatter := content[4 : 4+endIdx]
|
|
||||||
for _, line := range strings.Split(frontmatter, "\n") {
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
if strings.HasPrefix(line, "redirect:") {
|
|
||||||
target := strings.TrimSpace(strings.TrimPrefix(line, "redirect:"))
|
|
||||||
return target
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// wikilinkPattern matches [[Page Name]] and [[Page Name|Display Text]] syntax.
|
|
||||||
var wikilinkPattern = regexp.MustCompile(`\[\[([^\]|]+?)(?:\|([^\]]+?))?\]\]`)
|
|
||||||
|
|
||||||
// preprocessWikilinks replaces [[Page Name]] syntax with HTML links before markdown rendering.
|
|
||||||
// Existing pages get normal links; non-existent pages get red "new page" links.
|
|
||||||
func preprocessWikilinks(data []byte, commit *git.Commit, wikiBaseURL string) []byte {
|
|
||||||
if commit == nil {
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build a set of existing page paths for quick lookup.
|
|
||||||
existingPages := make(map[string]bool)
|
|
||||||
collectWikiPages(commit, "", existingPages)
|
|
||||||
|
|
||||||
result := wikilinkPattern.ReplaceAllFunc(data, func(match []byte) []byte {
|
|
||||||
parts := wikilinkPattern.FindSubmatch(match)
|
|
||||||
if len(parts) < 2 {
|
|
||||||
return match
|
|
||||||
}
|
|
||||||
|
|
||||||
pagePath := strings.TrimSpace(string(parts[1]))
|
|
||||||
displayText := pagePath
|
|
||||||
if len(parts) >= 3 && len(parts[2]) > 0 {
|
|
||||||
displayText = strings.TrimSpace(string(parts[2]))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle anchor links: [[#Section]] or [[Page#Section]]
|
|
||||||
anchor := ""
|
|
||||||
if idx := strings.Index(pagePath, "#"); idx >= 0 {
|
|
||||||
anchor = pagePath[idx:]
|
|
||||||
pagePath = pagePath[:idx]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pure anchor link on current page
|
|
||||||
if pagePath == "" && anchor != "" {
|
|
||||||
return []byte(`<a href="` + html.EscapeString(anchor) + `">` + html.EscapeString(displayText) + `</a>`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalize the page path for lookup
|
|
||||||
lookupKey := strings.ReplaceAll(pagePath, " ", "-")
|
|
||||||
|
|
||||||
// Check if page exists (try with and without folder prefix)
|
|
||||||
pageExists := existingPages[lookupKey] ||
|
|
||||||
existingPages[strings.ToLower(lookupKey)]
|
|
||||||
|
|
||||||
escapedURL := html.EscapeString(wikiBaseURL + url.PathEscape(lookupKey) + anchor)
|
|
||||||
escapedText := html.EscapeString(displayText)
|
|
||||||
|
|
||||||
if pageExists {
|
|
||||||
return []byte(`<a href="` + escapedURL + `">` + escapedText + `</a>`)
|
|
||||||
}
|
|
||||||
// Red link for non-existent pages — links to create page
|
|
||||||
createURL := html.EscapeString(wikiBaseURL + "?action=_new&title=" + url.QueryEscape(pagePath))
|
|
||||||
return []byte(`<a href="` + createURL + `" class="wiki-link-new" title="Page does not exist (click to create)">` + escapedText + `</a>`)
|
|
||||||
})
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// collectWikiPages builds a set of all wiki page paths from the commit tree.
|
|
||||||
// Stores both raw names and hyphen-normalized names for flexible lookup.
|
|
||||||
func collectWikiPages(commit *git.Commit, prefix string, pages map[string]bool) {
|
|
||||||
entries, err := commit.ListEntries()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, entry := range entries {
|
|
||||||
name := entry.Name()
|
|
||||||
if entry.IsDir() {
|
|
||||||
subTree := entry.Tree()
|
|
||||||
if subTree == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
children, _ := subTree.ListEntries()
|
|
||||||
for _, child := range children {
|
|
||||||
childName := child.Name()
|
|
||||||
if child.IsRegular() && strings.HasSuffix(childName, ".md") {
|
|
||||||
pageName := strings.TrimSuffix(childName, ".md")
|
|
||||||
fullPath := name + "/" + pageName
|
|
||||||
registerWikiPage(pages, fullPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if strings.HasSuffix(name, ".md") {
|
|
||||||
pageName := strings.TrimSuffix(name, ".md")
|
|
||||||
if prefix != "" {
|
|
||||||
pageName = prefix + "/" + pageName
|
|
||||||
}
|
|
||||||
registerWikiPage(pages, pageName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// registerWikiPage adds a page name to the lookup map with multiple normalizations.
|
|
||||||
func registerWikiPage(pages map[string]bool, name string) {
|
|
||||||
pages[name] = true
|
|
||||||
pages[strings.ToLower(name)] = true
|
|
||||||
// Also store hyphen-normalized version (spaces → hyphens)
|
|
||||||
hyphenized := strings.ReplaceAll(name, " ", "-")
|
|
||||||
if hyphenized != name {
|
|
||||||
pages[hyphenized] = true
|
|
||||||
pages[strings.ToLower(hyphenized)] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildWikiTree builds a hierarchical folder tree from the wiki git repo.
|
// buildWikiTree builds a hierarchical folder tree from the wiki git repo.
|
||||||
func buildWikiTree(commit *git.Commit) []*WikiTreeNode {
|
func buildWikiTree(commit *git.Commit) []*WikiTreeNode {
|
||||||
if commit == nil {
|
if commit == nil {
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
</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>
|
||||||
@@ -41,14 +40,10 @@
|
|||||||
</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>.mokogitea</code> (public) or <code>.mokogitea-private</code> (members-only)
|
Enable the wiki on the <code>.profile</code> (public) or <code>.profile-private</code> (members-only)
|
||||||
repository to get started.
|
repository to get started.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -47,59 +47,34 @@
|
|||||||
<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 .WikiTree}}
|
{{if .Pages}}
|
||||||
<h4>Available pages:</h4>
|
<h4>Available pages:</h4>
|
||||||
<ul>
|
<ul>
|
||||||
{{range .WikiTree}}
|
{{range .Pages}}
|
||||||
{{if .IsDir}}
|
<li><a href="{{$.Org.HomeLink}}/-/wiki/{{.SubURL}}">{{.Name}}</a></li>
|
||||||
{{range .Children}}
|
|
||||||
<li><a href="{{$.Org.HomeLink}}/-/wiki/{{.SubURL}}">{{.Name}}</a></li>
|
|
||||||
{{end}}
|
|
||||||
{{else}}
|
|
||||||
<li><a href="{{$.Org.HomeLink}}/-/wiki/{{.SubURL}}">{{.Name}}</a></li>
|
|
||||||
{{end}}
|
|
||||||
{{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 .WikiTree}}with-sidebar{{end}}">
|
<div class="render-content markup wiki-content-main {{if or .WikiSidebarHTML .Pages}}with-sidebar{{end}}">
|
||||||
{{.WikiContent}}
|
{{.WikiContent}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{if or .WikiSidebarHTML .WikiTree}}
|
{{if or .WikiSidebarHTML .Pages}}
|
||||||
<div class="render-content markup wiki-content-sidebar">
|
<div class="render-content markup wiki-content-sidebar">
|
||||||
{{if .WikiSidebarHTML}}
|
{{if .WikiSidebarHTML}}
|
||||||
{{.WikiSidebarHTML}}
|
{{.WikiSidebarHTML}}
|
||||||
{{else if .WikiTree}}
|
<div class="ui divider"></div>
|
||||||
|
{{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 .WikiTree}}
|
{{range .Pages}}
|
||||||
<li>
|
<li>
|
||||||
{{if .IsDir}}
|
{{svg "octicon-file" 14}}
|
||||||
<details open>
|
<a href="{{$.Org.HomeLink}}/-/wiki/{{.SubURL}}" {{if eq $.CurrentPage .Name}}class="active"{{end}}>{{.Name}}</a>
|
||||||
<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}}
|
|
||||||
<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,7 +21,11 @@
|
|||||||
<input name="org" value="{{.Manifest.Org}}" placeholder="Organization">
|
<input name="org" value="{{.Manifest.Org}}" placeholder="Organization">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="three fields">
|
<div class="four 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.">
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
{{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" .}}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
{{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" .}}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
{{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" .}}
|
|
||||||
@@ -20,7 +20,6 @@
|
|||||||
</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=_recent">{{svg "octicon-history" 14}} Recent changes</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>
|
||||||
@@ -35,8 +34,6 @@
|
|||||||
<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">
|
||||||
|
|||||||
@@ -86,13 +86,3 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user