From 23f045662205364447d81c48def9c579497135a3 Mon Sep 17 00:00:00 2001 From: KenanZhu Date: Fri, 9 Oct 2026 11:43:02 +0800 Subject: [PATCH] chore: streamline compatibility tracking and documentation --- .github/release-notes/v1.27.2.md | 2 +- .github/release-notes/v1.27.3.md | 2 +- .github/release-notes/v28.0.0.md | 2 +- .github/scripts/test_track_gitea_release.py | 137 +++++++++++++ .github/scripts/track_gitea_release.py | 189 ++++++++++++++++++ .github/workflows/gitea-tracker.yml | 118 +++-------- .github/workflows/release.yml | 12 +- .gitignore | 10 +- AGENTS.md | 27 ++- COMPATIBILITY.md | 31 +-- CONTRIBUTING.md | 4 +- README.md | 38 ++-- docs/CONTRIBUTING.zh-CN.md | 11 +- docs/README.zh-CN.md | 54 +++-- docs/images/README.md | 6 +- ...23-remove-juice-simplify-preview-design.md | 92 --------- 16 files changed, 476 insertions(+), 259 deletions(-) create mode 100644 .github/scripts/test_track_gitea_release.py create mode 100644 .github/scripts/track_gitea_release.py delete mode 100644 docs/superpowers/specs/2026-06-23-remove-juice-simplify-preview-design.md diff --git a/.github/release-notes/v1.27.2.md b/.github/release-notes/v1.27.2.md index 8b294a9..dfb7f72 100644 --- a/.github/release-notes/v1.27.2.md +++ b/.github/release-notes/v1.27.2.md @@ -19,4 +19,4 @@ DOCS --- -**Full Changelog:** [v1.0.1...v1.27.2](https://github.com/KenanZhu/GiteaMailTemplates/compare/v1.0.1...v1.27.2) +**Full Changelog:** [v1.0.1...v1.27.2](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/compare/v1.0.1...v1.27.2) diff --git a/.github/release-notes/v1.27.3.md b/.github/release-notes/v1.27.3.md index 9388528..5d41b90 100644 --- a/.github/release-notes/v1.27.3.md +++ b/.github/release-notes/v1.27.3.md @@ -19,4 +19,4 @@ DOCS --- -**Full Changelog:** [v1.27.2...v1.27.3](https://github.com/KenanZhu/GiteaMailTemplates/compare/v1.27.2...v1.27.3) +**Full Changelog:** [v1.27.2...v1.27.3](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/compare/v1.27.2...v1.27.3) diff --git a/.github/release-notes/v28.0.0.md b/.github/release-notes/v28.0.0.md index 6d6daea..cd1bcce 100644 --- a/.github/release-notes/v28.0.0.md +++ b/.github/release-notes/v28.0.0.md @@ -18,4 +18,4 @@ DOCS --- -**Full Changelog:** [v1.27.3...v28.0.0](https://github.com/KenanZhu/GiteaMailTemplates/compare/v1.27.3...v28.0.0) +**Full Changelog:** [v1.27.3...v28.0.0](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/compare/v1.27.3...v28.0.0) diff --git a/.github/scripts/test_track_gitea_release.py b/.github/scripts/test_track_gitea_release.py new file mode 100644 index 0000000..dc9d447 --- /dev/null +++ b/.github/scripts/test_track_gitea_release.py @@ -0,0 +1,137 @@ +"""Offline tests for the compatibility tracker.""" + +import importlib.util +import io +import json +import tempfile +import unittest +from pathlib import Path +from unittest.mock import patch + + +SCRIPT = Path(__file__).with_name("track_gitea_release.py") +SPEC = importlib.util.spec_from_file_location("track_gitea_release", SCRIPT) +TRACKER = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(TRACKER) + + +class TrackerTests(unittest.TestCase): + def setUp(self): + self.directory = tempfile.TemporaryDirectory(prefix="tracker-test-", dir=TRACKER.ROOT) + self.addCleanup(self.directory.cleanup) + self.root = Path(self.directory.name) + self.paths = ("COMPATIBILITY.md", "README.md", "AGENTS.md", "docs/README.zh-CN.md") + self.originals = {} + for name in self.paths: + destination = self.root / name + destination.parent.mkdir(parents=True, exist_ok=True) + content = (TRACKER.ROOT / name).read_text(encoding="utf-8") + destination.write_text(content, encoding="utf-8") + self.originals[name] = content + + def read(self, name): + return (self.root / name).read_text(encoding="utf-8") + + def assert_unchanged(self): + for name, content in self.originals.items(): + self.assertEqual(content, self.read(name), name) + + def test_new_release_adds_only_pending_rows(self): + changed = TRACKER.apply_release(self.root, "28.2.0", "2026-11-01") + self.assertEqual({self.root / name for name in self.paths}, set(changed)) + compatibility = self.read("COMPATIBILITY.md") + self.assertIn("| 28.2.0 | [PENDING] Compatibility verification", compatibility) + self.assertIn("| **28.2.0** | 2026-11-01 | [PENDING]", compatibility) + self.assertIn("| **v28.0.0** | **28.0.0** | **28.0.0** | [PASS] Active |", compatibility) + self.assertIn("Latest verified:** Release v28.0.0", compatibility) + self.assertIn("Gitea-28.2.0%20%5BPENDING%5D%20%7C%2028.0.0%20tested-yellow", self.read("README.md")) + self.assertIn("Gitea 28.2.0:** [PENDING]", self.read("README.md")) + self.assertIn("Gitea 28.2.0:** [PENDING]", self.read("docs/README.zh-CN.md")) + self.assertIn("Gitea release: 28.2.0 [PENDING]", self.read("AGENTS.md")) + self.assertIn("Latest tested:** Gitea 28.0.0", self.read("README.md")) + self.assertIn("最新测试:** Gitea 28.0.0", self.read("docs/README.zh-CN.md")) + self.assertEqual([], TRACKER.apply_release(self.root, "28.2.0", "2026-11-01")) + + def test_existing_version_is_idempotent(self): + self.assertEqual([], TRACKER.apply_release(self.root, "28.1.0", "2026-10-06")) + self.assert_unchanged() + + def test_partial_update_is_repaired(self): + compatibility = self.root / "COMPATIBILITY.md" + partial = self.read("COMPATIBILITY.md").replace("| 28.1.0 | [PENDING] Compatibility verification; no tested template release yet |\n", "") + compatibility.write_text(partial, encoding="utf-8") + self.assertEqual([compatibility], TRACKER.apply_release(self.root, "28.1.0", "2026-10-06")) + self.assertIn("| 28.1.0 | [PENDING] Compatibility verification", self.read("COMPATIBILITY.md")) + + def test_older_version_does_not_rewrite_history(self): + self.assertEqual([], TRACKER.apply_release(self.root, "28.0.0", "2026-09-29")) + self.assert_unchanged() + + def test_dry_run_does_not_write(self): + self.assertEqual(4, len(TRACKER.apply_release(self.root, "28.2.0", "2026-11-01", dry_run=True))) + self.assert_unchanged() + + def test_missing_marker_fails_without_writing(self): + compatibility = self.root / "COMPATIBILITY.md" + compatibility.write_text(self.read("COMPATIBILITY.md").replace("", ""), encoding="utf-8") + with self.assertRaises(ValueError): + TRACKER.apply_release(self.root, "28.2.0", "2026-11-01") + self.assertEqual(self.originals["README.md"], self.read("README.md")) + + def test_unknown_marker_fails_without_writing(self): + news = self.root / "docs/NEWS.md" + news.write_text("Gitea 28.1.0 [PENDING] \n", encoding="utf-8") + with self.assertRaisesRegex(ValueError, "Unknown TRACKER marker"): + TRACKER.apply_release(self.root, "28.2.0", "2026-11-01") + self.assert_unchanged() + + def test_new_marked_document_is_discovered(self): + news = self.root / "docs/NEWS.md" + news.write_text("Gitea 28.1.0 [PENDING] \n", encoding="utf-8") + changed = TRACKER.apply_release(self.root, "28.2.0", "2026-11-01") + self.assertIn(news, changed) + self.assertEqual("Gitea 28.2.0 [PENDING] \n", news.read_text(encoding="utf-8")) + + def test_disagreeing_tested_versions_fail(self): + chinese = self.root / "docs/README.zh-CN.md" + chinese.write_text(self.read("docs/README.zh-CN.md").replace("**最新测试:** Gitea 28.0.0", "**最新测试:** Gitea 28.1.0"), encoding="utf-8") + with self.assertRaisesRegex(ValueError, "disagree"): + TRACKER.apply_release(self.root, "28.2.0", "2026-11-01") + + def test_after_verification_new_version_becomes_pending(self): + english = self.root / "README.md" + chinese = self.root / "docs/README.zh-CN.md" + agents = self.root / "AGENTS.md" + english.write_text(self.read("README.md") + .replace("**Latest tested:** Gitea 28.0.0", "**Latest tested:** Gitea 28.1.0") + .replace("Gitea-28.1.0%20%5BPENDING%5D%20%7C%2028.0.0%20tested-yellow", "Gitea-28.1.0%20%7C%2028.1.0%20tested-blue") + .replace("**Upstream Gitea 28.1.0:** [PENDING]", "**Upstream Gitea 28.1.0:** [PASS]"), encoding="utf-8") + chinese.write_text(self.read("docs/README.zh-CN.md") + .replace("**最新测试:** Gitea 28.0.0", "**最新测试:** Gitea 28.1.0") + .replace("**上游 Gitea 28.1.0:** [PENDING]", "**上游 Gitea 28.1.0:** [PASS]"), encoding="utf-8") + agents.write_text(self.read("AGENTS.md").replace("28.1.0 [PENDING]", "28.1.0 [PASS]"), encoding="utf-8") + TRACKER.apply_release(self.root, "28.2.0", "2026-11-01") + self.assertIn("Gitea-28.2.0%20%5BPENDING%5D%20%7C%2028.1.0%20tested-yellow", self.read("README.md")) + self.assertIn("Gitea 28.2.0:** [PENDING]", self.read("README.md")) + self.assertIn("Latest tested:** Gitea 28.1.0", self.read("README.md")) + self.assertIn("最新测试:** Gitea 28.1.0", self.read("docs/README.zh-CN.md")) + + def test_invalid_version_rejected(self): + with self.assertRaises(ValueError): + TRACKER.apply_release(self.root, "28.2.0-rc1", "2026-11-01") + + def test_release_api_uses_published_date(self): + payload = {"tag_name": "v28.2.0", "published_at": "2026-11-01T08:15:00Z", "draft": False, "prerelease": False} + with patch.object(TRACKER, "urlopen", return_value=io.BytesIO(json.dumps(payload).encode("utf-8"))) as mocked: + self.assertEqual(("28.2.0", "2026-11-01"), TRACKER.fetch_release("28.2.0")) + self.assertEqual(f"{TRACKER.RELEASES_API}/tags/v28.2.0", mocked.call_args.args[0].full_url) + + def test_prerelease_is_rejected(self): + payload = {"tag_name": "v28.2.0", "published_at": "2026-11-01T08:15:00Z", "prerelease": True} + with patch.object(TRACKER, "urlopen", return_value=io.BytesIO(json.dumps(payload).encode("utf-8"))): + with self.assertRaises(ValueError): + TRACKER.fetch_release(None) + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/scripts/track_gitea_release.py b/.github/scripts/track_gitea_release.py new file mode 100644 index 0000000..2b417c6 --- /dev/null +++ b/.github/scripts/track_gitea_release.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +"""Record a new upstream Gitea release without changing verified compatibility.""" + +import argparse +import json +import os +import re +import sys +from datetime import date +from pathlib import Path +from urllib.request import Request, urlopen + + +ROOT = Path(__file__).resolve().parents[2] +RELEASES_API = "https://api.github.com/repos/go-gitea/gitea/releases" +VERSION_RE = re.compile(r"v?(\d+)\.(\d+)\.(\d+)\Z") +ROW_RE = re.compile(r"^\|\s*(?:\*\*)?(\d+\.\d+\.\d+)(?:\*\*)?\s*\|") +INLINE_VERSION_RE = re.compile(r"(?") +BADGE_RE = re.compile(r"Gitea-(\d+\.\d+\.\d+)(?:%20(?:pending|%5BPENDING%5D))?%20%7C%20(\d+\.\d+\.\d+)%20tested-(?:blue|yellow)") +TABLES = { + "VERSION-MAP": "| Gitea version | Template release |", + "HISTORY": "| Gitea | Release Date | Mail Template Changes | Breaking? |", +} +AUTO_MARKERS = frozenset((*TABLES, "BADGE", "UPSTREAM")) +MANUAL_MARKERS = frozenset(("LATEST-TESTED", "LATEST-VERIFIED")) +REQUIRED_MARKERS = {"VERSION-MAP": 1, "HISTORY": 1, "BADGE": 1, "UPSTREAM": 3, "LATEST-TESTED": 2, "LATEST-VERIFIED": 1} + + +def version_key(value): + match = VERSION_RE.fullmatch(value) + if not match: + raise ValueError(f"Expected a stable X.Y.Z version, got {value!r}") + return tuple(int(part) for part in match.groups()) + + +def fetch_release(requested_version): + endpoint = f"{RELEASES_API}/tags/v{requested_version}" if requested_version else f"{RELEASES_API}/latest" + request = Request(endpoint, headers={"Accept": "application/vnd.github+json", "User-Agent": "gitea-mail-templates-tracker"}) + token = os.environ.get("GITHUB_TOKEN") + if token: + request.add_header("Authorization", f"Bearer {token}") + with urlopen(request, timeout=20) as response: + release = json.load(response) + if release.get("draft") or release.get("prerelease"): + raise ValueError("Draft and prerelease Gitea versions are not tracked") + version = str(release["tag_name"]).removeprefix("v") + version_key(version) + if requested_version and version != requested_version: + raise ValueError(f"Release tag mismatch: expected {requested_version}, got {version}") + published = release.get("published_at") + if not published: + raise ValueError(f"Release v{version} has no published_at date") + release_date = date.fromisoformat(published[:10]).isoformat() + return version, release_date + + +def table(lines, marker_index, expected_header): + header = marker_index + 1 + if header + 1 >= len(lines) or lines[header].strip() != expected_header: + raise ValueError(f"Unexpected table header after {lines[marker_index].strip()}") + if not re.fullmatch(r"\|[\s|:-]+\|", lines[header + 1].strip()): + raise ValueError(f"Missing Markdown table separator after {lines[marker_index].strip()}") + first = header + 2 + end = first + while end < len(lines) and lines[end].startswith("|"): + end += 1 + versions = set() + for line in lines[first:end]: + match = ROW_RE.match(line) + if not match: + if expected_header == TABLES["HISTORY"] and re.match(r"^\|\s*\*\*≤\s*\d+\.\d+\.x\*\*\s*\|", line): + continue + raise ValueError(f"Malformed version row after {lines[marker_index].strip()}: {line.strip()}") + if match.group(1) in versions: + raise ValueError(f"Duplicate version after {lines[marker_index].strip()}: {match.group(1)}") + versions.add(match.group(1)) + if not versions: + raise ValueError(f"Empty version table after {lines[marker_index].strip()}") + return first, versions + + +def documents(root): + """Discover Markdown docs; new docs opt in by adding a known TRACKER marker.""" + paths = sorted((*root.glob("*.md"), *root.glob("docs/**/*.md"))) + result = {path: path.read_text(encoding="utf-8").splitlines(keepends=True) for path in paths} + markers = [] + for path, lines in result.items(): + for index, line in enumerate(lines): + for match in MARKER_RE.finditer(line): + name = match.group(1) + if name not in AUTO_MARKERS | MANUAL_MARKERS: + raise ValueError(f"Unknown TRACKER marker {name} in {path}") + markers.append((path, index, name)) + for name, minimum in REQUIRED_MARKERS.items(): + count = sum(item[2] == name for item in markers) + if count < minimum or (name in TABLES or name == "BADGE") and count != minimum: + raise ValueError(f"Expected {minimum} TRACKER:{name} marker(s), found {count}") + return result, markers + + +def latest_tested(documents_by_path, markers): + versions = set() + for path, index, name in markers: + if name == "LATEST-TESTED": + match = re.search(r"Gitea\s+(\d+\.\d+\.\d+)", documents_by_path[path][index]) + if not match: + raise ValueError(f"Missing Gitea version beside TRACKER:LATEST-TESTED in {path}") + versions.add(match.group(1)) + if len(versions) != 1: + raise ValueError(f"LATEST-TESTED markers disagree: {sorted(versions)}") + return versions.pop() + + +def update_marker(lines, index, name, version, release_date, tested): + if name in TABLES: + first, versions = table(lines, index, TABLES[name]) + if version not in versions: + if name == "VERSION-MAP": + row = f"| {version} | [PENDING] Compatibility verification; no tested template release yet |\n" + else: + row = f"| **{version}** | {release_date} | [PENDING] Mail-template compatibility verification | TBD |\n" + lines.insert(first, row) + elif name == "UPSTREAM": + versions = list(INLINE_VERSION_RE.finditer(lines[index])) + if len(STATUS_RE.findall(lines[index])) != 1 or len(versions) != 1: + raise ValueError("TRACKER:UPSTREAM needs exactly one status and version on the same line") + line = INLINE_VERSION_RE.sub(version, lines[index], count=1) + lines[index] = STATUS_RE.sub("[PENDING]", line, count=1) + elif name == "BADGE": + match = BADGE_RE.search(lines[index]) + if not match or match.group(2) != tested: + raise ValueError("TRACKER:BADGE has an unexpected format or tested version") + badge = f"Gitea-{version}%20%5BPENDING%5D%20%7C%20{tested}%20tested-yellow" + lines[index] = BADGE_RE.sub(badge, lines[index], count=1) + + +def apply_release(root, version, release_date, dry_run=False): + target = version_key(version) + date.fromisoformat(release_date) + originals, markers = documents(root) + tested = latest_tested(originals, markers) + history_path, history_index, _ = next(item for item in markers if item[2] == "HISTORY") + history_first, _ = table(originals[history_path], history_index, TABLES["HISTORY"]) + latest = version_key(ROW_RE.match(originals[history_path][history_first]).group(1)) + if target < latest or target <= version_key(tested): + return [] + + updated = {path: lines.copy() for path, lines in originals.items()} + for path, index, name in sorted(markers, key=lambda item: (str(item[0]), item[1]), reverse=True): + if name in AUTO_MARKERS: + update_marker(updated[path], index, name, version, release_date, tested) + changed = [path for path in updated if updated[path] != originals[path]] + if not dry_run: + for path in changed: + path.write_text("".join(updated[path]), encoding="utf-8") + return changed + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--version", default=os.environ.get("GITEA_VERSION"), help="Specific stable Gitea version; omit for latest release") + parser.add_argument("--release-date", help="YYYY-MM-DD; use with --version for offline checks") + parser.add_argument("--dry-run", action="store_true", help="Check without changing repository documentation") + args = parser.parse_args() + if args.release_date and not args.version: + parser.error("--release-date requires --version") + if args.version: + args.version = args.version.removeprefix("v") + version_key(args.version) + if args.release_date: + version, release_date = args.version, date.fromisoformat(args.release_date).isoformat() + else: + version, release_date = fetch_release(args.version) + changed = apply_release(ROOT, version, release_date, dry_run=args.dry_run) + print(f"Gitea {version}: {', '.join(str(path.relative_to(ROOT)) for path in changed) if changed else 'already tracked'}") + output_path = os.environ.get("GITHUB_OUTPUT") + if output_path: + with open(output_path, "a", encoding="utf-8") as output: + output.write(f"version={version}\nchanged={'true' if changed else 'false'}\n") + return 0 + + +if __name__ == "__main__": + try: + sys.exit(main()) + except (KeyError, OSError, ValueError) as error: + sys.exit(f"Tracker failed: {error}") diff --git a/.github/workflows/gitea-tracker.yml b/.github/workflows/gitea-tracker.yml index 489b763..b5a43d0 100644 --- a/.github/workflows/gitea-tracker.yml +++ b/.github/workflows/gitea-tracker.yml @@ -2,12 +2,11 @@ name: Gitea Version Tracker on: schedule: - # Daily at 08:00 UTC — check for new Gitea releases - cron: '0 8 * * *' workflow_dispatch: inputs: version: - description: 'Gitea version (e.g. 1.27.0) — leave empty to auto-detect' + description: 'Stable Gitea version (e.g. 28.2.0); empty means latest' required: false jobs: @@ -20,103 +19,32 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Determine latest Gitea version + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Test tracker + run: python -B -m unittest discover -s .github/scripts -p 'test_*.py' + + - name: Record pending Gitea release id: version - run: | - if [ -n "${{ github.event.inputs.version }}" ]; then - NEW_VERSION="${{ github.event.inputs.version }}" - else - # Fetch latest release from GitHub API (go-gitea/gitea mirror) - RELEASE_JSON=$(curl -sSf https://api.github.com/repos/go-gitea/gitea/releases/latest) - NEW_VERSION=$(echo "$RELEASE_JSON" | jq -r '.tag_name') - fi - # Strip 'v' prefix if present (API returns v1.27.0, we use 1.27.0) - NEW_VERSION="${NEW_VERSION#v}" - echo "New Gitea version: $NEW_VERSION" - - # Parse marker line AND embedded OFFSET to locate the data row - MARKER_INFO=$(grep -n 'TRACKER:QUICK-REF-MAX' COMPATIBILITY.md) - MARKER_LINE=$(echo "$MARKER_INFO" | cut -d: -f1) - OFFSET=$(echo "$MARKER_INFO" | sed 's/.*OFFSET=\([0-9]*\).*/\1/') - CURRENT=$(sed -n "$((MARKER_LINE + OFFSET))p" COMPATIBILITY.md | awk -F'|' '{gsub(/\*| /, "", $4); print $4}') - echo "Current max tested: $CURRENT" - - echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT - echo "current_version=$CURRENT" >> $GITHUB_OUTPUT - - if [ "$NEW_VERSION" = "$CURRENT" ]; then - echo "No new Gitea release detected. Skipping." - echo "skip=true" >> $GITHUB_OUTPUT - else - echo "New version detected: $CURRENT → $NEW_VERSION" - echo "skip=false" >> $GITHUB_OUTPUT - fi - - - name: COMPATIBILITY.md — Quick Reference (max tested version) - if: steps.version.outputs.skip != 'true' - run: | - NEW="${{ steps.version.outputs.new_version }}" - # Parse marker line and embedded OFFSET to locate the data row - MARKER_INFO=$(grep -n 'TRACKER:QUICK-REF-MAX' COMPATIBILITY.md) - MARKER=$(echo "$MARKER_INFO" | cut -d: -f1) - OFFSET=$(echo "$MARKER_INFO" | sed 's/.*OFFSET=\([0-9]*\).*/\1/') - TARGET=$((MARKER + OFFSET)) - # Update column 4 (Max Tested) and column 5 (Status) in the pipe-separated row - awk -i inplace -F'|' -v OFS='|' -v ver="${NEW}" ' - NR == '$TARGET' { $4 = " **" ver "** "; $5 = " ⏳ Pending Verification " } - { print } - ' COMPATIBILITY.md - - - name: COMPATIBILITY.md — Version History table - if: steps.version.outputs.skip != 'true' - run: | - NEW="${{ steps.version.outputs.new_version }}" - TODAY=$(date +%Y-%m-%d) - # Parse marker line and embedded OFFSET for the insertion point - MARKER_INFO=$(grep -n 'TRACKER:VERSION-INSERT' COMPATIBILITY.md) - MARKER=$(echo "$MARKER_INFO" | cut -d: -f1) - OFFSET=$(echo "$MARKER_INFO" | sed 's/.*OFFSET=\([0-9]*\).*/\1/') - INSERT=$((MARKER + OFFSET)) - ROW="| **${NEW}** | ${TODAY} | ⏳ Pending Verification | TBD |" - sed -i "${INSERT}a ${ROW}" COMPATIBILITY.md - - - name: README.md — pending badge - if: steps.version.outputs.skip != 'true' - run: | - NEW="${{ steps.version.outputs.new_version }}" - # Badge: update version, change label tested→pending, color blue→yellow - sed -i "/TRACKER:BADGE/s/%20[0-9a-z.-]*%20tested-blue/%20${NEW}%20pending-yellow/" README.md + run: python -B .github/scripts/track_gitea_release.py + env: + GITEA_VERSION: ${{ github.event.inputs.version }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Create Pull Request - if: steps.version.outputs.skip != 'true' + if: steps.version.outputs.changed == 'true' uses: peter-evans/create-pull-request@v7 with: - branch: track/gitea-${{ steps.version.outputs.new_version }} - commit-message: "docs: track Gitea ${{ steps.version.outputs.new_version }} — pending verification" - title: "Track Gitea ${{ steps.version.outputs.new_version }} — ⏳ Pending Verification" + add-paths: '*.md' + branch: track/gitea-${{ steps.version.outputs.version }} + commit-message: "docs: track Gitea ${{ steps.version.outputs.version }} pending verification" + title: "Track Gitea ${{ steps.version.outputs.version }} compatibility" body: | - Gitea **${{ steps.version.outputs.new_version }}** has been released. + Gitea **${{ steps.version.outputs.version }}** is recorded as [PENDING] across the marked documentation. Verified/tested ranges remain unchanged. - This PR: - - Updates the compatibility matrix in `COMPATIBILITY.md` - - Marks the new version as pending in the `README.md` badge - - Marks the new version as **⏳ Pending Verification** - - ## Verification Checklist - - [ ] Check [Gitea changelog](https://github.com/go-gitea/gitea/blob/main/CHANGELOG.md) for mail template changes - - [ ] Review diff of official templates: `git diff v${{ steps.version.outputs.current_version }}..v${{ steps.version.outputs.new_version }} -- templates/mail/ services/mailer/` - - [ ] Run `cd tools && go run . preview all` to confirm templates still render - - [ ] Run `cd tools && go test ./...` to verify push-notification data paths - - [ ] Update status from ⏳ to ✅ (compatible) or ❌ (breaking) in COMPATIBILITY.md - - [ ] Update the `Mail Template Changes` and `Breaking?` columns with accurate notes - - [ ] After verification, update `TRACKER:LATEST-VERIFIED` and the English/Chinese `TRACKER:LATEST-TESTED` lines; switch the README badge from pending to tested - - ## Semantic tracking - Once verified, update the tracked Gitea version: - - [ ] If templates changed, mark the top **Template Release** row `Unreleased` until the fix is tagged; if no change is needed, retain the verified release tag - - [ ] Update the README release status and compatibility matrix to distinguish source fixes from published packages - - [ ] Write `.github/release-notes/vX.Y.Z.md` before tagging; the release workflow uses this file for the published notes - - [ ] Tag a new release when shipping template fixes — `git tag vX.Y.Z && git push origin vX.Y.Z` (the release workflow packages automatically) - labels: compatibility, automated - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - [ ] Review upstream mail templates, mailer data, functions, and translation keys. + - [ ] Run `cd tools && go test ./...` and `go run . preview all`. + - [ ] Record findings in `COMPATIBILITY.md`; update tested ranges and status text only after verification. + - [ ] If template changes are needed, release them separately with reviewed notes and assets. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 52b5a1f..60a3e9e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,16 +34,26 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: '1.21' + - name: Verify release notes run: test -s ".github/release-notes/${GITHUB_REF_NAME}.md" + - name: Generate static preview for the archive + working-directory: tools + run: go run . preview all + - name: Package themes/ run: | VERSION=${GITHUB_REF#refs/tags/} ARCHIVE="gitea-mail-templates-${VERSION}" - mkdir -p "dist/${ARCHIVE}/docs" + mkdir -p "dist/${ARCHIVE}/docs" "dist/${ARCHIVE}/preview" cp -r themes LICENSE README.md CONTRIBUTING.md COMPATIBILITY.md "dist/${ARCHIVE}/" cp docs/README.zh-CN.md docs/CONTRIBUTING.zh-CN.md "dist/${ARCHIVE}/docs/" + cp -r docs/images "dist/${ARCHIVE}/docs/" + cp preview/index.html preview/rendered.js "dist/${ARCHIVE}/preview/" cd dist zip -r "${ARCHIVE}.zip" "${ARCHIVE}" tar -czf "${ARCHIVE}.tar.gz" "${ARCHIVE}" diff --git a/.gitignore b/.gitignore index 5696321..bd634b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,10 @@ -# Dependencies -node_modules/ +# Go dependencies vendor/ +# Python test cache +__pycache__/ +*.pyc + # OS files .DS_Store Thumbs.db @@ -18,8 +21,5 @@ Desktop.ini *.zip *.tar.gz -# Package lock file -package-lock.json - # Preview generated files preview/rendered.js diff --git a/AGENTS.md b/AGENTS.md index 9d7eb11..b5de58f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,22 +18,23 @@ themes/ # Template themes (10 styles, 11 .tmpl each = 110 source fil neon/ # Cyberpunk / Gaming terminal/ # Developers / Tech terra/ # Nature / Sustainability -tools/ # Go CLI tooling (modular, zero dependencies) +tools/ # Go CLI tooling (modular; uses urfave/cli/v2) tools.go # Main entry point cli/ # CLI subcommands: list, create, delete, preview config/ # Config types and templates_config.json loading data/ # templates_config.json — single source of truth for template metadata preview/ # Template rendering engine (funcs, locale, engine) - go.mod # Go module (stdlib only, zero dependencies) + go.mod # Go module (CLI dependency declared here) preview/ # Browser-based live preview index.html # SPA with style/template/client/viewport switching - rendered.js # Pre-rendered HTML (generated, committed for clone-and-preview) + rendered.js # Pre-rendered HTML (generated, ignored in source clones) docs/ # Bilingual documentation (English + Simplified Chinese) ``` ## Working With Templates ### Template Files + - All `.tmpl` files use Go `html/template` syntax - Must use only Gitea 28's built-in template functions: `AppUrl`, `DotEscape`, `QueryEscape`, `ShortSha`, `HTMLFormat`, `PathEscapeSegments`, `FormatByteSize` - Must use only official Gitea translation keys (`mail.*` namespace) @@ -41,20 +42,23 @@ docs/ # Bilingual documentation (English + Simplified Chinese) - Each style must have all 11 template types ### Adding a New Theme + 1. Scaffold the new theme: `cd tools && go run . create ` — creates the full directory structure with placeholder `.tmpl` files for all 11 email types 2. Write all 11 `.tmpl` files with unique visual design 3. Run `cd tools && go run . preview all` to regenerate preview data 4. Update README.md style gallery table ### Preview System + - `preview/index.html` loads `preview/rendered.js` (pre-rendered by Go) and displays in iframes - Supports theme/template switching, view mode (Modern/Source), and viewport toggle (Desktop 1386x780 / Mobile 390x780) - Keyboard navigation: `←→` cycles focus between Theme/Template/View selects, `↑↓` selects within the focused dropdown, `d`/`m` toggles viewport - `REGISTRY` and `PARAMS` are auto-generated from `templates_config.json` — no manual syncing needed -- Static preview (open `index.html` directly) — works via `file://` protocol with Modern and Source views +- Static preview works via `file://` after running `go run . preview all` in a source clone; the updated packaging workflow includes generated preview data and screenshots for future builds (v28.0.0 and older archives include neither) - Dev server (`go run . dev`) — pure Go HTTP server with SSE live reload; watches `themes/` for `.tmpl` changes, re-renders in-process, and pushes reload events to the browser ### Build Tool + - `tools/tools.go` is the main entry point for the modular CLI - Subcommands: `list`, `create`, `delete`, `preview`, `dev` - Template metadata lives in `tools/data/templates_config.json` — the single source of truth @@ -67,13 +71,15 @@ docs/ # Bilingual documentation (English + Simplified Chinese) ## Versioning - Release tags identify actual downloadable packages; the compatibility matrix distinguishes released tags from source-only fixes -- The current release is **v28.0.0**, verified against Gitea 28.0.0. Gitea 28 removes `FileSize` in favor of `FormatByteSize`, so v28.0.0 is not compatible with Gitea 1.25.0–1.27.3; use v1.27.3 for those versions. The quick-reference table in `COMPATIBILITY.md` lists the active release first; the tracker workflow updates that row by position -- When a new Gitea version is checked: update the top `COMPATIBILITY.md` row and README badge; keep unreleased fixes distinct from the published release, and update verified/tested wording only after verification -- Tag a new release (`vX.Y.Z`) only when the template content itself changes — the release workflow packages automatically on tag push -- Before tagging, add `.github/release-notes/vX.Y.Z.md`; the release workflow validates the file, runs `go test ./...`, and publishes those reviewed notes with the archives -- Keep the `TRACKER:` markers in `COMPATIBILITY.md` / `README.md` adjacent to their rows so the automated workflow keeps parsing them +- The current release is **v28.0.0**, verified against Gitea 28.0.0. Gitea 28 removes `FileSize` in favor of `FormatByteSize`, so v28.0.0 is not compatible with Gitea 1.25.0–1.27.3; use v1.27.3 for those versions. The quick-reference table in `COMPATIBILITY.md` lists the active release first +- Latest upstream Gitea release: 28.1.0 [PENDING]. +- When a new Gitea version appears, the tracker updates pending rows, marked version lines, and the pending README badge. After verification, update the top `COMPATIBILITY.md` tested range and README tested text/badge; keep unreleased fixes distinct from the published release +- Tag a new release (`vX.Y.Z`) only when the template content itself changes. On the new Gitea host, do not assume tag pushes automatically build or upload archives; verify the Gitea workflow before relying on it +- Before tagging, add `.github/release-notes/vX.Y.Z.md`, run `go test ./...` and `go run . preview all` from `tools/`, then build and upload the archives with the reviewed notes. `.github/workflows/release.yml` retains the earlier GitHub Actions flow as a reference +- Keep `TRACKER:VERSION-MAP` and `TRACKER:HISTORY` above their tables, and inline `TRACKER:BADGE`, `TRACKER:UPSTREAM`, `TRACKER:LATEST-TESTED`, and `TRACKER:LATEST-VERIFIED` markers with their text. The Python tracker updates pending markers; tested/verified markers are manual-only ## Commit Conventions + - `style(name):` — template changes for a specific theme - `preview:` — preview tooling changes - `tools:` — Go CLI/build tooling changes @@ -84,7 +90,8 @@ docs/ # Bilingual documentation (English + Simplified Chinese) - `chore:` — maintenance (config updates, build scripts) ## Constraints + - No JavaScript framework dependencies — preview is vanilla JS -- No external Go dependencies — build uses stdlib only +- The CLI uses `github.com/urfave/cli/v2`; the preview server itself uses Go's standard HTTP library - Templates must remain compatible with Gitea's `html/template` execution environment - Preview works with `file://` protocol (no server needed) diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index c339774..8f60dc7 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -4,14 +4,13 @@ This document tracks the compatibility between **Gitea Mail Templates** releases ## Quick Reference - | Template Release | Min Gitea | Max Tested Gitea | Status | |-----------------|-----------|-----------------|--------| -| **v28.0.0** | **28.0.0** | **28.0.0** | ✅ Active | -| **v1.27.3** | **1.25.0** | **1.27.3** | ✅ Superseded; release emails fail on Gitea 28.0.0 (`FileSize` removed) | -| **v1.27.2** | **1.25.0** | **1.27.3** | ⚠️ Push notices fail in Bloom, Ember, and Heritage on Gitea 1.27.1+ | -| **v1.0.1** | **1.25.0** | **1.27.0** | ✅ Superseded; push notices need newer release on 1.27.1+ | -| **v1.0.0** | **1.25.0** | **1.26.4** | ✅ Superseded | +| **v28.0.0** | **28.0.0** | **28.0.0** | [PASS] Active | +| **v1.27.3** | **1.25.0** | **1.27.3** | [PASS] Superseded; release emails fail on Gitea 28.0.0 (`FileSize` removed) | +| **v1.27.2** | **1.25.0** | **1.27.3** | [WARN] Push notices fail in Bloom, Ember, and Heritage on Gitea 1.27.1+ | +| **v1.0.1** | **1.25.0** | **1.27.0** | [PASS] Superseded; push notices need newer release on 1.27.1+ | +| **v1.0.0** | **1.25.0** | **1.26.4** | [PASS] Superseded | > **Latest verified:** Release v28.0.0 passes the Gitea 28.0.0 mail-template, mailer-context, function, and translation-key source audit plus all-theme rendering tests. This release requires Gitea 28.0.0; use v1.27.3 for Gitea 1.25.0–1.27.3. @@ -19,15 +18,17 @@ This document tracks the compatibility between **Gitea Mail Templates** releases The release tag identifies the downloadable template package. The supported Gitea version may be appended in parentheses in this compatibility matrix; the parenthesized version is not a Git tag. An **unreleased** row describes fixes available in the repository but not yet in a downloadable release. + | Gitea version | Template release | |---------------|------------------| +| 28.1.0 | [PENDING] Compatibility verification; no tested template release yet | | 28.0.0 | **v28.0.0**; older releases use the removed `FileSize` function in release emails | | 1.27.3 | **v1.27.3**; v1.27.2 has a push-notification issue in three themes | | 1.27.2 | **v1.27.3**; v1.27.2 has the same issue | | 1.27.1 | **v1.27.3**; v1.27.2 has the same issue | -- The [tracker workflow](.github/workflows/gitea-tracker.yml) opens a PR when a new Gitea release appears, marking it ⏳ Pending Verification. -- After verification, update the top **Template Release** row. Keep fixes on `main` marked **unreleased** until a new tag is published; the [release workflow](.github/workflows/release.yml) packages the archive on tag push. +- The [tracker workflow](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/src/branch/main/.github/workflows/gitea-tracker.yml) records new upstream versions as [PENDING] in this table, the history below, and the marked README and AGENTS lines. Its automatic PR creation has not been verified on the new Gitea host; check releases manually until Gitea automation is configured. +- After verification, update the top **Template Release** row only when that package has been tested against the new Gitea version. Keep fixes on `main` marked **unreleased** until a new tag and downloadable Gitea Release are published; do not assume a tag push uploads archives automatically. - The older `v1.0.1` tag predates the Gitea 1.27.1 push notification data fix and should not be used for Gitea 1.27.1 or newer. - Gitea 28.0.0 replaces the mail-template `FileSize` function with `FormatByteSize`. The two functions are not interchangeable across these Gitea versions; use the matching template release. @@ -41,9 +42,10 @@ gitea --version ## Gitea Version History — Mail Template Impact - + | Gitea | Release Date | Mail Template Changes | Breaking? | |-------|-------------|----------------------|-----------| +| **28.1.0** | 2026-10-06 | [PENDING] Mail-template compatibility verification | TBD | | **28.0.0** | 2026-09-29 | `FileSize` removed and `FormatByteSize` added; mail templates now have `mail/`-prefixed internal names and shared head/footer partials; workflow emails gain status-icon fields. Our custom template paths and referenced data fields remain valid. | **Yes** (release attachment formatting) | | **1.27.3** | 2026-08-29 | No upstream mail template, mailer, or locale changes; an existing push-notification defect in three themes is fixed in template release v1.27.3 | No upstream break | | **1.27.2** | 2026-08-14 | None — security + bug fixes | No | @@ -56,7 +58,7 @@ gitea --version | **1.26.0** | 2026-04-19 | AppURL cleanup; SanitizeHTML deprecated → use HTMLFormat | No | | **1.25.5** | 2026-03-10 | None — security + maintenance | No | | **1.25.0** | 2025 | **Directory restructure** — templates moved to `mail//.tmpl` (PR #35150); subject/body split with `---` separator; template preview support added | **Yes** (structural) | -| **≤ 1.24.x** | — | Flat directory structure under `custom/templates/mail/` | ❌ Unsupported | +| **≤ 1.24.x** | — | Flat directory structure under `custom/templates/mail/` | [UNSUPPORTED] | ## Template Variable Reference @@ -100,7 +102,7 @@ All 11 template types use only Gitea built-in variables and functions. Verified | `repo/issue/assigned` | `Doer`, `Issue`, `Link`, `IsPull` | | `repo/issue/default` | `Doer`, `Issue`, `Link`, `Body`, `ActionName`, `Comment`, `IsPull`, `IsMention`, `ReviewComments`, `CanReply` | -> ⚠️ **`.DisplayName`** is not available in collaborator, transfer, release, workflow_run, assigned, and default templates — do not reference it. +> [WARN] **`.DisplayName`** is not available in collaborator, transfer, release, workflow_run, assigned, and default templates — do not reference it. ### Translation Keys @@ -108,17 +110,18 @@ All templates use Gitea's official `mail.*` translation namespace. Every referen ## How Compatibility Is Verified -1. **Automated lint** — CI renders all templates via `go run . preview all` on every push; the preview function map mirrors Gitea 28.0.0 +1. **Local validation** — run `go test ./...` and `go run . preview all` from `tools/`; the preview function map mirrors Gitea 28.0.0 2. **Source audit** — Template data contexts are cross-referenced against Gitea's `services/mailer/` package 3. **Regression tests** — Go tests render push notifications and release attachments in all 10 themes 4. **Release checklist** — Each release confirms the max-tested Gitea version in this file -## Automated Tracking +## Version Tracking -A [workflow](.github/workflows/gitea-tracker.yml) runs daily (UTC 08:00) to detect new Gitea releases. When a new version is found, it automatically creates a PR updating the matrix and badges with a **⏳ Pending Verification** status. Manual trigger is also available via `workflow_dispatch`. Once verification passes, update the compatibility matrix; publish a new template tag when template changes require a release (see [Versioning](#versioning)). +The [tracker workflow](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/src/branch/main/.github/workflows/gitea-tracker.yml) scans root-level and `docs/` Markdown for `TRACKER:` markers. `VERSION-MAP` and `HISTORY` add pending rows; `UPSTREAM` updates the latest upstream version and status in English/Chinese README and AGENTS; `BADGE` shows the new version as pending while retaining the last tested version. `LATEST-TESTED` and `LATEST-VERIFIED` are manual-only markers and must change only after compatibility verification. The script rejects unknown or missing required markers and is idempotent. Its GitHub Actions schedule and PR creation have not been verified on this Gitea host; until then, check [upstream Gitea releases](https://github.com/go-gitea/gitea/releases) manually. Publish a new template tag only when template content changes (see [Versioning](#versioning)). ## Reporting Issues If you find a compatibility problem with a specific Gitea version: + 1. Check the [Gitea changelog](https://github.com/go-gitea/gitea/blob/main/CHANGELOG.md) for recent mail template changes 2. Open an issue with: your Gitea version, which template, and the error diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ba7dc79..77137a4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,7 +40,7 @@ Documentation updates, preview screenshots, installation guides, and translation ## Development Setup -- **Go 1.21+** for template rendering and the CLI tool +- **Go 1.21+** for template rendering and the CLI tool; Go modules download `github.com/urfave/cli/v2` and its dependencies ### Previewing Locally (Static) @@ -56,7 +56,7 @@ cd tools && go run . dev ``` - Watches `themes/**/*.tmpl` — auto-rebuilds on save -- Pure Go HTTP server with SSE push — no external dependencies +- HTTP server and SSE live reload use Go's standard library; the CLI has a Go module dependency - Re-renders templates in-process and pushes reload events to the browser - Terminal output: `themes/aurora/mail/repo/release.tmpl changed` → `[Builder] Rebuild done in 45ms` diff --git a/README.md b/README.md index 2a53eeb..95a4670 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # Gitea Mail Templates -A curated collection of professionally designed, audience-driven email templates for self-hosted [Gitea](https://about.gitea.com) instances. +Polished, drop-in email template themes for self-hosted [Gitea](https://about.gitea.com). -[![Gitea](https://img.shields.io/badge/Gitea-28.0.0%20%7C%2028.0.0%20tested-blue)](COMPATIBILITY.md) +[![Gitea](https://img.shields.io/badge/Gitea-28.1.0%20%5BPENDING%5D%20%7C%2028.0.0%20tested-yellow)](COMPATIBILITY.md) -> **Release v28.0.0 — verified with Gitea 28.0.0 — 110 template files, 10 visual styles, 11 email types each.** +> Latest Release: [v28.0.0](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v28.0.0) --- @@ -31,9 +31,9 @@ The templates on `main` can replace Gitea's built-in mail templates without patc | ![Ink](docs/images/ink.png) | **Ink** | Publishing / News / Literature | Editorial print, navy & gold, newspaper layout, drop caps | | ![Aurora](docs/images/aurora.png) | **Aurora** | Premium SaaS / Mindfulness | Ethereal light gradients, deep purple & teal, atmospheric glow | -> Images are screenshots from the [live preview](preview/index.html). See [docs/images/README.md](docs/images/README.md) for capture instructions. +> Images are screenshots from the [local preview](preview/index.html). See [docs/images/README.md](docs/images/README.md) for capture instructions. -[**Live preview gallery**](preview/index.html) — open in a browser for an interactive style switcher with desktop/mobile viewports and view mode (Modern, Source). +[**Local preview gallery**](preview/index.html) — generate the preview data as described below, then open it in a browser for an interactive style switcher with desktop/mobile viewports and view mode (Modern, Source). --- @@ -82,15 +82,17 @@ reset email — it will render with your custom styles. ## Preview -Two modes are available — a zero-dependency static preview for quick checks, and a live-reload dev server for design work. +Two modes are available — a static preview that needs no server after generation, and a live-reload dev server for design work. A source clone must generate preview data first. Existing v28.0.0 and earlier archives do not contain the preview or gallery screenshots; the updated packaging workflow includes both for future builds. ### Static Preview -Generate the preview data once (Go only), then open in a browser: +From a source clone, generate the preview data once, then open the HTML file in a browser: ```bash -cd tools && go run . preview all -open preview/index.html # no server needed +cd tools +go run . preview all +cd .. +# Open preview/index.html in a browser; no server is needed. ``` ### Dev Server (Live Reload) @@ -98,15 +100,16 @@ open preview/index.html # no server needed Start a pure Go development server that watches for `.tmpl` changes, auto-rebuilds, and pushes live updates to the browser via SSE: ```bash -cd tools && go run . dev -open http://localhost:3456 +cd tools +go run . dev +# Open http://localhost:3456 in a browser. ``` | Capability | Static | Dev | |-----------|--------|-----| -| Go template rendering | ✅ | ✅ | -| Theme/template switching | ✅ | ✅ | -| Live reload on save | — | ✅ | +| Go template rendering | [YES] | [YES] | +| Theme/template switching | [YES] | [YES] | +| Live reload on save | [NO] | [YES] | ### Features @@ -127,7 +130,7 @@ gitea-mail-templates/ │ ├── ... # Custom styles are added here as separate directories ├── preview/ # Live preview SPA │ ├── index.html # Style/template/client/viewport switcher -│ └── rendered.js # Pre-rendered templates (generated by tools/) +│ └── rendered.js # Generated by tools/; ignored in source clones ├── tools/ # Modular CLI tooling │ ├── tools.go # Main entry point │ ├── cli/ # CLI subcommands (list, create, delete, preview) @@ -165,7 +168,8 @@ gitea-mail-templates/ - **Gitea 28.0.0** — use template release v28.0.0; earlier Gitea versions require an older package - **Latest tested:** Gitea 28.0.0 -- **Latest release:** [v28.0.0](https://github.com/KenanZhu/GiteaMailTemplates/releases/tag/v28.0.0) updates all 10 release-notification templates for Gitea's `FormatByteSize` function. Use [v1.27.3](https://github.com/KenanZhu/GiteaMailTemplates/releases/tag/v1.27.3) for Gitea 1.25.0–1.27.3 (see [COMPATIBILITY.md](COMPATIBILITY.md)) +- **Latest release:** [v28.0.0](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v28.0.0) updates all 10 release-notification templates for Gitea's `FormatByteSize` function. Use [v1.27.3](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v1.27.3) for Gitea 1.25.0–1.27.3 (see [COMPATIBILITY.md](COMPATIBILITY.md)) +- **Upstream Gitea 28.1.0:** [PENDING] - The current source uses Gitea's official template data paths — see [COMPATIBILITY.md](COMPATIBILITY.md) for release-specific limitations - Uses only built-in Gitea template functions and official translation keys - No custom template functions or locale patches required @@ -178,7 +182,7 @@ gitea-mail-templates/ 2. **Accessible** — 4.5:1 contrast ratios; semantic HTML 3. **Graceful degradation** — Fallback link visible when buttons fail to render 4. **Logo support** — References `{{AppUrl}}assets/img/favicon.png` by default -5. **i18n-ready** — All user-facing strings use Gitea's `{{.locale.Tr}}` system +5. **Locale-aware** — Notification text uses Gitea's `{{.locale.Tr}}` system; some decorative labels remain theme-specific English text --- diff --git a/docs/CONTRIBUTING.zh-CN.md b/docs/CONTRIBUTING.zh-CN.md index 1f82043..0f385dc 100644 --- a/docs/CONTRIBUTING.zh-CN.md +++ b/docs/CONTRIBUTING.zh-CN.md @@ -34,14 +34,13 @@ ## 开发环境 -- **Go 1.21+** 用于模板渲染和 CLI 工具 +- **Go 1.21+** 用于模板渲染和 CLI 工具;Go 模块需下载 `github.com/urfave/cli/v2` 及其依赖 ### 本地预览(静态) 1. 先生成预览数据:`cd tools && go run . preview all` 2. 在浏览器中打开 `preview/index.html` — 无需服务器 - ### 开发服务器(实时重载) ```bash @@ -49,8 +48,7 @@ cd tools && go run . dev # → http://localhost:3456 ``` -修改 `.tmpl` 文件后自动重建并推送至浏览器。 - +修改 `.tmpl` 文件后自动重建并推送至浏览器。HTTP 服务与 SSE 实时重载使用 Go 标准库实现,CLI 本身依赖 Go 模块。 ### 集成测试 @@ -75,6 +73,11 @@ cd tools && go run . dev - `fix:` — Bug 修复 - `project:` — README、LICENSE、元文件 +## 翻译 + +- [English CONTRIBUTING](../CONTRIBUTING.md) +- 简体中文(本文) + ## 许可协议 参与贡献即表示您同意将您的贡献以 MIT 许可证授权。 diff --git a/docs/README.zh-CN.md b/docs/README.zh-CN.md index 5ffb5a1..dac2450 100644 --- a/docs/README.zh-CN.md +++ b/docs/README.zh-CN.md @@ -1,8 +1,8 @@ # Gitea 邮件模板 -为自托管 [Gitea](https://about.gitea.com) 实例精心设计、面向不同受众的邮件模板集合。 +为自托管 [Gitea](https://about.gitea.com) 提供精心设计、可直接部署的多风格邮件模板。 -> **发布版 v28.0.0 — 已通过 Gitea 28.0.0 兼容性检查 — 110 个模板文件、10 种视觉风格、每种 11 种邮件类型** +> 最新发布版:[v28.0.0](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v28.0.0) --- @@ -29,9 +29,9 @@ | ![Ink](images/ink.png) | **Ink** | 出版/新闻/文学 | 编辑印刷、深蓝与金色、报纸排版 | | ![Aurora](images/aurora.png) | **Aurora** | 高端SaaS/正念 | 空灵光效渐变、深紫与青绿、大气光晕 | -> 图片为 600px 宽截图,来自[在线预览](../preview/index.html)。截图方法参见 [images/README.md](images/README.md)。 +> 图片为 600px 宽截图,来自[本地预览](../preview/index.html)。截图方法参见 [images/README.md](images/README.md)。 -[**在线预览画廊**](../preview/index.html) +[**本地预览画廊**](../preview/index.html) — 先按下文生成预览数据,再在浏览器中打开。 --- @@ -46,6 +46,8 @@ systemctl restart gitea 切换风格只需覆盖文件,无需更改配置。 +`GITEA_CUSTOM` 决定自定义目录;常见路径为 Linux 二进制部署的 `/var/lib/gitea/custom`、Docker 的 `/data/gitea`,以及 Windows 的 `C:\gitea\custom`。 + ### 确认生效 管理后台的测试邮件不会使用自定义模板。要验证模板是否生效,请触发一次真实的 @@ -56,33 +58,59 @@ systemctl restart gitea ## 预览 +从源码克隆时,需先生成被忽略的 `preview/rendered.js`。现有 v28.0.0 及更早的压缩包未包含预览文件和画廊截图;更新后的打包流程会在后续构建中加入两者。 + **静态模式:** + ```bash -cd tools && go run . preview all +cd tools +go run . preview all +cd .. ``` -然后打开 `preview/index.html`。 + +然后在浏览器中打开 `preview/index.html`,无需启动服务器。 **开发服务器(实时重载):** + ```bash -cd tools && go run . dev -# → http://localhost:3456 +cd tools +go run . dev +# 在浏览器中打开 http://localhost:3456 ``` | 功能 | 静态 | Dev | |-----------|--------|-----| -| Go 模板渲染 | ✅ | ✅ | -| 主题/模板切换 | ✅ | ✅ | -| 实时重载 | — | ✅ | +| Go 模板渲染 | [YES] | [YES] | +| 主题/模板切换 | [YES] | [YES] | +| 实时重载 | [NO] | [YES] | + +预览支持主题与模板切换、Modern/Source 视图、桌面/移动端尺寸以及模板参数面板。`←→` 可切换选择框,`↑↓` 可切换选项,`d`/`m` 可切换视口。 --- ## 兼容性 - **Gitea 28.0.0** — 使用模板发布版 v28.0.0;旧版 Gitea 请选用对应的旧版模板 -- **最新测试:** Gitea 28.0.0 -- **最新发布版:** [v28.0.0](https://github.com/KenanZhu/GiteaMailTemplates/releases/tag/v28.0.0) 已将全部 10 个主题的发布通知适配为 `FormatByteSize`;Gitea 1.25.0–1.27.3 请使用 [v1.27.3](https://github.com/KenanZhu/GiteaMailTemplates/releases/tag/v1.27.3)(详见[兼容性说明](../COMPATIBILITY.md)) +- **最新测试:** Gitea 28.0.0 +- **最新发布版:** [v28.0.0](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v28.0.0) 已将全部 10 个主题的发布通知适配为 `FormatByteSize`;Gitea 1.25.0–1.27.3 请使用 [v1.27.3](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v1.27.3)(详见[兼容性说明](../COMPATIBILITY.md)) +- **上游 Gitea 28.1.0:** [PENDING] - 当前源码使用 Gitea 官方模板的数据路径;各发布版的限制详见 [兼容性说明](../COMPATIBILITY.md) +## 模板类型 + +每种风格都提供 11 种邮件类型:账户激活、邮箱验证、注册通知、密码重置、团队邀请、仓库协作者、仓库转移、新版发布、Actions 工作流、议题/合并请求指派及议题/合并请求更新。具体文件路径参见[英文 README](../README.md#template-types)。 + +## 设计与翻译 + +模板采用适合邮件客户端的响应式布局,并提供按钮失效时可用的备用链接。通知正文使用 Gitea 的翻译系统;部分主题装饰性标签仍为英文,并非所有可见文字都已本地化。 + +## 文档与贡献 + +- [English README](../README.md) +- [简体中文 README](README.zh-CN.md) +- [English CONTRIBUTING](../CONTRIBUTING.md) +- [简体中文贡献指南](CONTRIBUTING.zh-CN.md) + ## 许可证 MIT — 详见 [LICENSE](../LICENSE)。 diff --git a/docs/images/README.md b/docs/images/README.md index 0786413..31ab932 100644 --- a/docs/images/README.md +++ b/docs/images/README.md @@ -1,6 +1,6 @@ # Style Preview Images -Place theme style screenshots of **Desktop** in PNG format here, captured from the [live preview](../preview/index.html). +Place theme style screenshots of **Desktop** in PNG format here, captured from the [local preview](../../preview/index.html). ## Naming Convention @@ -14,7 +14,7 @@ neon.png mono.png terra.png ink.png aurora.png To ensure screenshots can be displayed in the README, please follow these size requirements: - **Maximum:** 50 KiB per image -- **Recommended:** 30–40 KiB +- **Recommended:** 10–20 KiB - **Format:** PNG, optimised — run through `pngquant` or `optipng` before committing ## How to Capture @@ -24,4 +24,4 @@ To ensure screenshots can be displayed in the README, please follow these size r 3. Take a screenshot of the rendered email (600px width recommended) 4. Save as `.png` in this directory -> For static preview, run `cd tools && go run . preview all` then open `preview/index.html` directly. +> For a source clone, run `cd tools && go run . preview all`, return to the repository root, then open `preview/index.html` directly. diff --git a/docs/superpowers/specs/2026-06-23-remove-juice-simplify-preview-design.md b/docs/superpowers/specs/2026-06-23-remove-juice-simplify-preview-design.md deleted file mode 100644 index c5a2b7e..0000000 --- a/docs/superpowers/specs/2026-06-23-remove-juice-simplify-preview-design.md +++ /dev/null @@ -1,92 +0,0 @@ -# Spec: Remove Juice & Simplify Preview Module - -**Date:** 2026-06-23 -**Status:** approved - -## Goal - -Remove multi-email-client simulation (Gmail/Outlook) from the preview module, eliminate the `juice` CSS-inlining dependency, and rewrite the dev server in pure Go with SSE-based live reload. - -## Motivation - -- Multi-client CSS simulation has diminishing value — modern email clients render consistently -- `juice` + Node.js adds complexity (npm install, node_modules, separate runtime) -- The project already uses Go for all build tooling; a Node.js dev server is an outlier -- Simplifying to "Modern" and "Source" views covers the real use cases - -## Changes - -### 1. Delete `tools/server/` (entire directory) - -Remove all Node.js artifacts: -- `inliner.mjs` — juice CSS inlining + Gmail/Outlook CSS stripping -- `server.mjs` — Express + WebSocket dev server -- `package.json` / `package-lock.json` -- `node_modules/` — all npm dependencies - -### 2. Rewrite `tools/cli/dev.go` - -- Remove Node.js dependency check -- Remove `exec.Command("node", "server.mjs")` subprocess -- Instead: create and start a pure Go HTTP server (calling into `tools/preview/server.go`) -- Keep the same CLI interface: `go run . dev [--port ]` - -### 3. New file: `tools/preview/server.go` - -Pure Go dev server with: -- **Static file serving** — serve `preview/` directory (index.html, rendered.js) -- **SSE endpoint** (`GET /events`) — Server-Sent Events for browser reload notifications -- **File watcher** — poll `themes/` every 500ms, compare mod times of `.tmpl` files -- **On change detected** — call `preview.RenderAll()` directly (in-process, no subprocess), write `rendered.js`, broadcast SSE `reload` event -- No external Go dependencies — uses `net/http` stdlib only - -### 4. Modify `preview/index.html` - -Frontend changes: -- **Client selector** — reduce from 4 options (Modern/Gmail/Outlook/Raw Source) to 2 (Modern/Source) -- **Remove** `renderedGmail` / `renderedOutlook` variables and all Gmail/Outlook JS logic -- **Simplify** `getRendered()` — always returns `rendered` -- **Simplify** `transform()` — only handles `source` mode (HTML escaping) -- **Remove** Client Simulation indicators panel (HTML section + JS in `updatePanel()`) -- **Remove** static-mode warning message ("Gmail / Outlook simulation is approximate...") -- **Replace** WebSocket with SSE (`new EventSource('/events')`) -- **SSE reload handler** — dynamically reload `rendered.js` on `reload` event, re-render iframe without losing current theme/template/viewport selection -- **Simplify** `setDevMode()` — remove multi-client disclaimer - -### 5. Update `AGENTS.md` - -- Remove references to Juice, Node.js, Gmail/Outlook variants -- Update dev server description to reflect pure Go implementation - -## Non-Changes - -- `tools/preview/engine.go` — template rendering engine unchanged -- `tools/preview/funcs.go` — template functions unchanged -- `tools/preview/locale.go` — locale data unchanged -- `tools/config/` — config loading unchanged -- `tools/data/templates_config.json` — unchanged -- `tools/cli/preview.go` — preview command unchanged -- `tools/cli/commands.go` — command registration unchanged -- All theme `.tmpl` files — unchanged - -## Behavior - -### Static preview (open `preview/index.html` directly) -- Works via `file://` protocol as before -- Two view modes: Modern (rendered HTML in iframe) and Source (escaped HTML source) -- No dev-mode warnings needed - -### Dev mode (`go run . dev`) -- `go run . dev` starts the Go HTTP server on port 3456 (configurable) -- On startup: runs preview engine once, writes `rendered.js` -- Serves `preview/` as static files -- Watches `themes/` for `.tmpl` changes (500ms polling) -- On change: re-renders affected themes, updates `rendered.js`, pushes SSE event -- Browser auto-reloads preview content without page refresh (preserves UI state) - -## Constraints - -- No new Go dependencies — SSE and file polling use stdlib only -- No Node.js requirement -- `preview/rendered.js` format stays compatible: `window.__RENDERED__`, `window.__REGISTRY__`, `window.__PARAMS__` -- Preview still works with `file://` protocol (no server needed for basic use)