Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b06a795e4 | ||
|
|
c93f2bf8b2 | ||
|
|
f4d96de79e | ||
|
|
fec3ace600 | ||
|
|
5c0589f6f6 | ||
|
|
14dc37046d | ||
|
|
23f0456622 | ||
|
|
92d2ba9889 |
No files matched your search
@@ -0,0 +1,8 @@
|
||||
* text=auto
|
||||
# Generated lock and framework sources use stable line endings.
|
||||
gitea.lock.json text eol=lf
|
||||
framework/** text eol=lf
|
||||
themes/** text eol=lf
|
||||
tools/** text eol=lf
|
||||
preview/** text eol=lf
|
||||
.github/** text eol=lf
|
||||
@@ -1,22 +1,16 @@
|
||||
> **Correction (2026-09-23):** v1.27.2 did not fix every push notification template. Bloom, Ember, and Heritage still used the old commit ID path and can fail to render push notifications on Gitea 1.27.1+. Use v1.27.3 for the fix.
|
||||
# v1.27.2
|
||||
|
||||
FEATURES
|
||||
> **Correction (2026-09-23):** The push-notification fix in this release is incomplete. Bloom, Ember and Heritage still use the old commit ID path and can fail to render push notifications on Gitea 1.27.1+. Use v1.27.3 for the complete fix; see the [compatibility matrix](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/src/branch/main/COMPATIBILITY.md#compatibility-matrix).
|
||||
|
||||
- THEMES (Aurora)
|
||||
- Add `email-text` class to release template paragraphs for consistent typography with the rest of the theme
|
||||
## Changes
|
||||
|
||||
BUGFIXES
|
||||
- Add the `email-text` class to Aurora release paragraphs for consistent typography.
|
||||
- Update push commit paths in seven themes for Gitea 1.27.1 compatibility (`.ID` → `.UserCommit.GitCommit.ID`). The three remaining themes are covered by the correction above.
|
||||
- Replace `rgba()` colors with `#RRGGBBAA` notation across all ten themes.
|
||||
|
||||
- THEMES
|
||||
- Update push commit data paths in seven themes for Gitea 1.27.1 compatibility (`.ID` → `.UserCommit.GitCommit.ID`); see the correction above for the three remaining themes
|
||||
- Replace `rgba()` colors with `#RRGGBBAA` hex notation across all 10 themes
|
||||
## Documentation
|
||||
|
||||
DOCS
|
||||
- Record compatibility for Gitea 1.27.0, 1.27.1 and 1.27.2.
|
||||
- Maintain English and Simplified Chinese README and contributor guides.
|
||||
|
||||
- Track Gitea 1.27.0, 1.27.1, and 1.27.2 compatibility
|
||||
- Keep English and Simplified Chinese README and CONTRIBUTING files
|
||||
|
||||
|
||||
---
|
||||
|
||||
**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)
|
||||
@@ -1,22 +1,20 @@
|
||||
FEATURES
|
||||
# v1.27.3
|
||||
|
||||
- RELEASE
|
||||
- Include English and Simplified Chinese README and CONTRIBUTING files in the release archives
|
||||
- Use reviewed release notes in the publishing workflow
|
||||
This release completes the push-notification fix for Gitea 1.27.1–1.27.3. See the [compatibility matrix](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/src/branch/main/COMPATIBILITY.md#compatibility-matrix) for recommended release combinations.
|
||||
|
||||
BUGFIXES
|
||||
## Fixes
|
||||
|
||||
- THEMES (Bloom, Ember, Heritage)
|
||||
- Fix pull request push notification commit links and abbreviated hashes for Gitea 1.27.1–1.27.3 by reading `.UserCommit.GitCommit.ID`
|
||||
- CI
|
||||
- Add a push notification regression test covering all 10 themes and run Go tests before packaging a release
|
||||
- Fix pull request push-notification links and abbreviated hashes in Bloom, Ember and Heritage by reading `.UserCommit.GitCommit.ID`.
|
||||
- Add a push-notification regression test covering all ten themes and run Go tests before packaging.
|
||||
|
||||
DOCS
|
||||
## Packaging
|
||||
|
||||
- Verify compatibility with Gitea 1.27.3; its mail templates, mailer, and locale files are unchanged from 1.27.2
|
||||
- Correct the compatibility matrix to distinguish the affected v1.27.2 archive from this fixed release
|
||||
- Include English and Simplified Chinese README and contributor guides in release archives.
|
||||
- Use reviewed release notes in the publishing workflow.
|
||||
|
||||
## Documentation
|
||||
|
||||
---
|
||||
- Record compatibility with Gitea 1.27.3, whose mail templates, mailer and locale files are unchanged from 1.27.2.
|
||||
- Distinguish the affected v1.27.2 archive from the complete fix in this release.
|
||||
|
||||
**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)
|
||||
@@ -0,0 +1,18 @@
|
||||
# v28.0.0
|
||||
|
||||
This release targets Gitea 28.0.0 and includes ten themes with eleven mail types each. For earlier Gitea versions, use the packages listed in the [compatibility matrix](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/src/branch/main/COMPATIBILITY.md#compatibility-matrix).
|
||||
|
||||
## Fixes
|
||||
|
||||
- Replace the removed `FileSize` function with `FormatByteSize` in release-attachment emails across all ten themes.
|
||||
- Update the preview's file-size function for Gitea 28.0.0 and include release attachments in the example data.
|
||||
- Add an all-theme regression test for release attachments and the removed function.
|
||||
|
||||
## Documentation
|
||||
|
||||
- Update both READMEs and the compatibility matrix for Gitea 28.0.0.
|
||||
- Document the Gitea 28.0.0 requirement and direct users of earlier versions to the compatibility matrix.
|
||||
|
||||
The snapshot-based source architecture now on `main` is separate, unreleased work. It is not included in the existing v28.0.0 archives.
|
||||
|
||||
[Full changelog: v1.27.3…v28.0.0](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/compare/v1.27.3...v28.0.0)
|
||||
@@ -0,0 +1,128 @@
|
||||
"""Gitea API operations for the release workflow (Python stdlib only)."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
from urllib.error import HTTPError
|
||||
from urllib.parse import quote, urlencode, urlsplit
|
||||
from urllib.request import HTTPRedirectHandler, Request, build_opener
|
||||
|
||||
|
||||
class NoRedirects(HTTPRedirectHandler):
|
||||
def redirect_request(self, req, fp, code, msg, headers, newurl):
|
||||
# Never forward an instance token to a redirect destination.
|
||||
return None
|
||||
|
||||
|
||||
class GiteaAPI:
|
||||
def __init__(self, server, repository, token):
|
||||
parsed = urlsplit(server)
|
||||
if parsed.scheme != "https" or not parsed.netloc or parsed.username or parsed.password or parsed.query or parsed.fragment:
|
||||
raise ValueError("GITEA_SERVER_URL must be an HTTPS instance URL")
|
||||
if not re.fullmatch(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+", repository):
|
||||
raise ValueError("GITEA_REPOSITORY must be owner/repository")
|
||||
if not token:
|
||||
raise ValueError("GITEA_TOKEN is required")
|
||||
self.base = server.rstrip("/") + "/api/v1/repos/" + repository
|
||||
self.token = token
|
||||
self.opener = build_opener(NoRedirects())
|
||||
|
||||
def request(self, method, path, data=None, content_type="application/json", allow_missing=False):
|
||||
if data is not None and not isinstance(data, bytes):
|
||||
data = json.dumps(data).encode("utf-8")
|
||||
req = Request(self.base + path, data=data, method=method, headers={
|
||||
"Authorization": "token " + self.token,
|
||||
"Accept": "application/json",
|
||||
"Content-Type": content_type,
|
||||
"User-Agent": "GiteaMailTemplates-actions",
|
||||
})
|
||||
try:
|
||||
with self.opener.open(req, timeout=120) as response:
|
||||
return json.load(response)
|
||||
except HTTPError as error:
|
||||
status = error.code
|
||||
error.close()
|
||||
if allow_missing and status == 404:
|
||||
return None
|
||||
raise RuntimeError(f"Gitea API {method} {path}: HTTP {status}") from None
|
||||
|
||||
|
||||
def create_issue(api, version):
|
||||
if not re.fullmatch(r"v\d+\.\d+\.\d+", version):
|
||||
raise ValueError("Expected a stable vX.Y.Z release tag")
|
||||
marker = f"<!-- gitea-mail-templates:template-release:{version} -->"
|
||||
title = f"Update documentation for template release {version}"
|
||||
page = 1
|
||||
while True:
|
||||
issues = api.request("GET", f"/issues?state=all&type=issues&limit=50&page={page}")
|
||||
for issue in issues:
|
||||
if issue.get("pull_request") is None and (marker in (issue.get("body") or "") or issue.get("title") == title):
|
||||
print(f"[PASS] Reminder issue already exists: {issue['html_url']}")
|
||||
return issue
|
||||
if not issues:
|
||||
break
|
||||
page += 1
|
||||
body = f"""{marker}
|
||||
Template release **{version}** has been published. Documentation is maintained manually.
|
||||
|
||||
- [ ] Update the release version in the English and Chinese READMEs.
|
||||
- [ ] Review compatibility records and mark versions verified only according to test results.
|
||||
- [ ] Check release links, release notes and remaining version references.
|
||||
|
||||
This reminder does not change repository files, branches or existing release assets.
|
||||
"""
|
||||
issue = api.request("POST", "/issues", {"title": title, "body": body})
|
||||
print(f"[PASS] Created reminder issue: {issue['html_url']}")
|
||||
return issue
|
||||
|
||||
|
||||
def publish_release(api, version, root=Path(".")):
|
||||
if not re.fullmatch(r"v\d+\.\d+\.\d+", version):
|
||||
raise ValueError("Expected a stable vX.Y.Z release tag")
|
||||
root = Path(root)
|
||||
lock = json.loads((root / "gitea.lock.json").read_text(encoding="utf-8"))
|
||||
if lock["tag"] != version:
|
||||
raise ValueError("Release tag must match gitea.lock.json")
|
||||
notes = (root / ".github" / "release-notes" / (version + ".md")).read_text(encoding="utf-8")
|
||||
if not notes.strip():
|
||||
raise ValueError("Release notes are empty")
|
||||
assets = [root / "dist" / ("gitea-mail-templates-" + version + ext) for ext in (".zip", ".tar.gz")]
|
||||
for asset in assets:
|
||||
if asset.is_symlink() or not asset.is_file() or not asset.stat().st_size:
|
||||
raise ValueError(f"Missing or invalid release archive: {asset}")
|
||||
existing = api.request("GET", "/releases/tags/" + quote(version, safe=""), allow_missing=True)
|
||||
if existing is not None:
|
||||
raise ValueError("Release already exists; refusing to replace its notes or assets")
|
||||
|
||||
release = api.request("POST", "/releases", {
|
||||
"tag_name": version, "name": version, "body": notes, "draft": True, "prerelease": False,
|
||||
})
|
||||
release_id = int(release["id"])
|
||||
# Gitea accepts raw attachment data with the filename in the query string.
|
||||
# Publish only after both uploads succeed; failures leave a draft for review.
|
||||
for asset in assets:
|
||||
api.request("POST", f"/releases/{release_id}/assets?" + urlencode({"name": asset.name}),
|
||||
asset.read_bytes(), content_type="application/octet-stream")
|
||||
release = api.request("PATCH", f"/releases/{release_id}", {"draft": False})
|
||||
print(f"[PASS] Published {release['html_url']}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("operation", choices=("create-issue", "publish-release"))
|
||||
parser.add_argument("--version", required=True)
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
api = GiteaAPI(os.environ.get("GITEA_SERVER_URL", ""), os.environ.get("GITEA_REPOSITORY", ""), os.environ.get("GITEA_TOKEN", ""))
|
||||
if args.operation == "create-issue":
|
||||
create_issue(api, args.version)
|
||||
else:
|
||||
publish_release(api, args.version)
|
||||
except (ValueError, KeyError, OSError, RuntimeError) as error:
|
||||
parser.exit(1, f"[FAIL] {error}\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,137 @@
|
||||
"""Package verified generated mail overrides; never copy stale build directories."""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
from pathlib import Path, PurePosixPath
|
||||
import re
|
||||
import tarfile
|
||||
import zipfile
|
||||
|
||||
|
||||
def read_file(path):
|
||||
path = Path(path)
|
||||
if path.is_symlink() or not path.is_file():
|
||||
raise ValueError(f"Expected regular file: {path}")
|
||||
return path.read_bytes()
|
||||
|
||||
|
||||
def checked_path(base, name):
|
||||
parts = PurePosixPath(name)
|
||||
if parts.is_absolute() or ".." in parts.parts or "\\" in name or ":" in name:
|
||||
raise ValueError(f"Unsafe package path: {name}")
|
||||
path = base.joinpath(*parts.parts)
|
||||
if not path.resolve().is_relative_to(base.resolve()):
|
||||
raise ValueError(f"Path escapes package root: {name}")
|
||||
return path
|
||||
|
||||
|
||||
def digest(data):
|
||||
return hashlib.sha256(data).hexdigest()
|
||||
|
||||
|
||||
def collect(root, version):
|
||||
root = Path(root)
|
||||
if not re.fullmatch(r"v[0-9]+\.[0-9]+\.[0-9]+", version):
|
||||
raise ValueError("Expected a stable vX.Y.Z package version")
|
||||
lock = json.loads(read_file(root / "gitea.lock.json"))
|
||||
if version != lock["tag"]:
|
||||
raise ValueError("Package version must match the locked Gitea tag")
|
||||
files = {}
|
||||
names = sorted(p.name for p in (root / "themes").iterdir() if p.is_dir() and not p.is_symlink())
|
||||
if not names:
|
||||
raise ValueError("No source theme manifests")
|
||||
for name in names:
|
||||
if not re.fullmatch(r"[a-z][a-z0-9-]*", name):
|
||||
raise ValueError(f"Invalid theme name: {name}")
|
||||
source = root / "themes" / name
|
||||
theme = json.loads(read_file(source / "theme.json"))
|
||||
if theme["name"] != name or theme["mode"] not in ("shared", "framed"):
|
||||
raise ValueError(f"Invalid theme metadata: {name}")
|
||||
built = root / "build" / "themes" / name
|
||||
meta = json.loads(read_file(built / "build.json"))
|
||||
if (meta["theme"], meta["mode"], meta["gitea_tag"], meta["gitea_commit"]) != (name, theme["mode"], version, lock["commit"]):
|
||||
raise ValueError(f"Stale build identity: {name}")
|
||||
if not {"theme.json", "theme.css"}.issubset(meta.get("sources") or {}) or not meta.get("files"):
|
||||
raise ValueError(f"Missing source/file provenance: {name}")
|
||||
expected_files = {"mail/base/head.tmpl", "mail/base/footer.tmpl"}
|
||||
if theme["mode"] == "framed":
|
||||
expected_files.update(path.removeprefix("templates/") for path in lock["files"] if path.startswith("templates/mail/") and path.endswith(".tmpl"))
|
||||
expected_files.update(path.relative_to(root / "framework").as_posix() for path in (root / "framework" / "mail").rglob("*.tmpl"))
|
||||
if set(meta["files"]) != expected_files:
|
||||
raise ValueError(f"Incomplete generated install package: {name}")
|
||||
for path, expected in meta["sources"].items():
|
||||
base = root if path.startswith(("framework/", "tools/")) else source
|
||||
if digest(read_file(checked_path(base, path))) != expected:
|
||||
raise ValueError(f"Source changed since build: {name}/{path}")
|
||||
for path, expected in meta["files"].items():
|
||||
if not path.startswith("mail/") or not path.endswith(".tmpl"):
|
||||
raise ValueError(f"Unexpected generated file: {path}")
|
||||
data = read_file(checked_path(built, path))
|
||||
if digest(data) != expected:
|
||||
raise ValueError(f"Generated checksum mismatch: {name}/{path}")
|
||||
files[f"themes/{name}/{path}"] = data
|
||||
files[f"themes/{name}/build.json"] = read_file(built / "build.json")
|
||||
manifest_data = read_file(root / "preview" / "rendered.js")
|
||||
prefix = b"window.__MAIL_PREVIEW__ = "
|
||||
manifest = json.loads(manifest_data.split(prefix, 1)[1].strip().removesuffix(b";"))
|
||||
languages = sorted(path.removeprefix("options/locale/locale_").removesuffix(".json") for path in lock["files"] if path.startswith("options/locale/locale_") and path.endswith(".json"))
|
||||
if manifest["upstream"] != version or manifest.get("upstream_commit") != lock["commit"] or sorted(manifest["themes"]) != names or sorted(manifest["languages"]) != languages:
|
||||
raise ValueError("Preview does not match lock/all source themes and languages; run preview all")
|
||||
files["preview/rendered.js"] = manifest_data
|
||||
for lang in languages:
|
||||
data = read_file(root / "preview" / "rendered" / (lang + ".js"))
|
||||
marker = ('window.__MAIL_PREVIEW_DATA__[' + json.dumps(lang) + '] = ').encode()
|
||||
payload = json.loads(data.split(marker, 1)[1].strip().removesuffix(b";"))
|
||||
if sorted(payload) != names or any(set(payload[name]) != set(manifest["registry"]) for name in names):
|
||||
raise ValueError(f"Incomplete preview language bundle: {lang}")
|
||||
files[f"preview/rendered/{lang}.js"] = data
|
||||
for name in ["LICENSE", "README.md", "CONTRIBUTING.md", "COMPATIBILITY.md", "docs/README.zh-CN.md", "docs/CONTRIBUTING.zh-CN.md", "preview/index.html"]:
|
||||
files[name] = read_file(root / name)
|
||||
for name in ["AGENTS.md", "THIRD_PARTY_NOTICES.md"]:
|
||||
if (root / name).exists():
|
||||
files[name] = read_file(root / name)
|
||||
for directory in [root / "docs", root / ".github" / "release-notes"]:
|
||||
for path in sorted(directory.rglob("*.md")):
|
||||
name = path.relative_to(root).as_posix()
|
||||
files[name] = read_file(checked_path(root, name))
|
||||
for path in sorted((root / "docs" / "images").iterdir()):
|
||||
if path.is_file() and (path.suffix == ".png" or path.name == "README.md"):
|
||||
files["docs/images/" + path.name] = read_file(path)
|
||||
license_data = read_file(root / "build" / "upstream" / "LICENSE")
|
||||
if digest(license_data) != lock["files"]["LICENSE"]:
|
||||
raise ValueError("Upstream license checksum mismatch")
|
||||
files["GITEA-LICENSE"] = license_data
|
||||
files["upstream-lock.json"] = read_file(root / "gitea.lock.json")
|
||||
return files
|
||||
|
||||
|
||||
def package(root, version, output):
|
||||
files = collect(root, version)
|
||||
archive = f"gitea-mail-templates-{version}"
|
||||
output = Path(output)
|
||||
targets = [output / (archive + ext) for ext in (".zip", ".tar.gz")]
|
||||
if any(path.exists() for path in targets):
|
||||
raise ValueError("Refusing to overwrite existing release archives; use a new output directory")
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
with zipfile.ZipFile(targets[0], "w", zipfile.ZIP_DEFLATED, compresslevel=9) as zipped, tarfile.open(targets[1], "w:gz") as tar:
|
||||
for name, data in sorted(files.items()):
|
||||
path = archive + "/" + name
|
||||
zipped.writestr(path, data)
|
||||
info = tarfile.TarInfo(path)
|
||||
info.size, info.mode = len(data), 0o644
|
||||
tar.addfile(info, io.BytesIO(data))
|
||||
print(f"[PASS] Packaged {len(files)} verified files: {targets[0]}, {targets[1]}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--version", required=True)
|
||||
parser.add_argument("--root", type=Path, default=Path(__file__).resolve().parents[2])
|
||||
parser.add_argument("--output", type=Path, default=Path("dist"))
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
package(args.root, args.version, args.output)
|
||||
except (ValueError, KeyError, IndexError, OSError) as error:
|
||||
parser.exit(1, f"[FAIL] {error}\n")
|
||||
@@ -0,0 +1,142 @@
|
||||
"""Offline Gitea API and issue-reminder regression tests."""
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import Mock
|
||||
from urllib.error import HTTPError
|
||||
|
||||
|
||||
SPEC = importlib.util.spec_from_file_location("gitea_actions", Path(__file__).with_name("gitea_actions.py"))
|
||||
ACTIONS = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(ACTIONS)
|
||||
|
||||
|
||||
class APITests(unittest.TestCase):
|
||||
def test_instance_url_token_and_raw_asset_request(self):
|
||||
api = ACTIONS.GiteaAPI("https://git.example/subpath/", "owner/repo", "test-token")
|
||||
response = Mock()
|
||||
response.__enter__ = Mock(return_value=response)
|
||||
response.__exit__ = Mock(return_value=False)
|
||||
response.read.return_value = b'{"id": 1}'
|
||||
api.opener = Mock()
|
||||
api.opener.open.return_value = response
|
||||
self.assertEqual({"id": 1}, api.request("POST", "/releases/1/assets?name=test.zip", b"archive", "application/octet-stream"))
|
||||
request = api.opener.open.call_args.args[0]
|
||||
self.assertEqual("https://git.example/subpath/api/v1/repos/owner/repo/releases/1/assets?name=test.zip", request.full_url)
|
||||
self.assertEqual("token test-token", request.get_header("Authorization"))
|
||||
self.assertEqual(b"archive", request.data)
|
||||
self.assertEqual("application/octet-stream", request.get_header("Content-type"))
|
||||
|
||||
def test_only_explicit_404_is_missing_and_redirects_are_refused(self):
|
||||
api = ACTIONS.GiteaAPI("https://git.example", "owner/repo", "test-token")
|
||||
api.opener = Mock()
|
||||
for status in (401, 403, 500, 302):
|
||||
api.opener.open.side_effect = HTTPError(api.base, status, "error", {}, None)
|
||||
with self.assertRaisesRegex(RuntimeError, f"HTTP {status}"):
|
||||
api.request("GET", "/releases/tags/v28.0.0", allow_missing=True)
|
||||
api.opener.open.side_effect = HTTPError(api.base, 404, "missing", {}, None)
|
||||
self.assertIsNone(api.request("GET", "/releases/tags/v28.0.0", allow_missing=True))
|
||||
self.assertIsNone(ACTIONS.NoRedirects().redirect_request(None, None, 302, "", {}, "https://elsewhere.example"))
|
||||
|
||||
def test_rejects_invalid_configuration(self):
|
||||
for server, repository, token in [("http://git.example", "owner/repo", "x"),
|
||||
("https://user:password@git.example", "owner/repo", "x"),
|
||||
("https://git.example", "../owner/repo", "x"), ("https://git.example", "owner/repo", "")]:
|
||||
with self.assertRaises(ValueError):
|
||||
ACTIONS.GiteaAPI(server, repository, token)
|
||||
|
||||
|
||||
class ReleaseTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.temp.cleanup)
|
||||
self.root = Path(self.temp.name)
|
||||
(self.root / "gitea.lock.json").write_text(json.dumps({"tag": "v28.0.0"}))
|
||||
notes = self.root / ".github/release-notes/v28.0.0.md"
|
||||
notes.parent.mkdir(parents=True)
|
||||
notes.write_text("Reviewed notes", encoding="utf-8")
|
||||
(self.root / "dist").mkdir()
|
||||
for ext in (".zip", ".tar.gz"):
|
||||
(self.root / "dist" / ("gitea-mail-templates-v28.0.0" + ext)).write_bytes(b"archive")
|
||||
|
||||
def test_existing_release_is_not_modified(self):
|
||||
api = Mock()
|
||||
api.request.return_value = {"id": 5}
|
||||
with self.assertRaisesRegex(ValueError, "already exists"):
|
||||
ACTIONS.publish_release(api, "v28.0.0", self.root)
|
||||
self.assertEqual(["GET"], [call.args[0] for call in api.request.call_args_list])
|
||||
|
||||
def test_publish_only_after_both_uploads_succeed(self):
|
||||
api = Mock()
|
||||
api.request.side_effect = [None, {"id": 5}, {"id": 6}, {"id": 7}, {"html_url": "https://git.example/release"}]
|
||||
ACTIONS.publish_release(api, "v28.0.0", self.root)
|
||||
calls = api.request.call_args_list
|
||||
self.assertEqual(["GET", "POST", "POST", "POST", "PATCH"], [c.args[0] for c in calls])
|
||||
self.assertTrue(calls[1].args[2]["draft"])
|
||||
self.assertEqual("Reviewed notes", calls[1].args[2]["body"])
|
||||
self.assertEqual("/releases/5/assets?name=gitea-mail-templates-v28.0.0.zip", calls[2].args[1])
|
||||
self.assertEqual("/releases/5/assets?name=gitea-mail-templates-v28.0.0.tar.gz", calls[3].args[1])
|
||||
self.assertEqual({"draft": False}, calls[4].args[2])
|
||||
|
||||
def test_failed_upload_leaves_draft_unpublished(self):
|
||||
api = Mock()
|
||||
api.request.side_effect = [None, {"id": 5}, RuntimeError("upload failed")]
|
||||
with self.assertRaisesRegex(RuntimeError, "upload failed"):
|
||||
ACTIONS.publish_release(api, "v28.0.0", self.root)
|
||||
self.assertNotIn("PATCH", [c.args[0] for c in api.request.call_args_list])
|
||||
|
||||
def test_bad_version_or_missing_archive_fails_before_api_call(self):
|
||||
api = Mock()
|
||||
for version in ("v28.0.1", "v28.0.0-rc1", "../../x"):
|
||||
with self.assertRaises(ValueError):
|
||||
ACTIONS.publish_release(api, version, self.root)
|
||||
(self.root / "dist/gitea-mail-templates-v28.0.0.zip").unlink()
|
||||
with self.assertRaisesRegex(ValueError, "archive"):
|
||||
ACTIONS.publish_release(api, "v28.0.0", self.root)
|
||||
api.request.assert_not_called()
|
||||
|
||||
|
||||
class IssueTests(unittest.TestCase):
|
||||
def test_creates_release_documentation_issue(self):
|
||||
api = Mock()
|
||||
api.request.side_effect = [[], {"html_url": "https://git.example/issues/1"}]
|
||||
ACTIONS.create_issue(api, "v28.1.0")
|
||||
calls = api.request.call_args_list
|
||||
self.assertEqual(["GET", "POST"], [c.args[0] for c in calls])
|
||||
self.assertEqual("/issues", calls[-1].args[1])
|
||||
payload = calls[-1].args[2]
|
||||
self.assertIn("<!-- gitea-mail-templates:template-release:v28.1.0 -->", payload["body"])
|
||||
self.assertIn("Documentation is maintained manually", payload["body"])
|
||||
|
||||
def test_reuses_closed_issue_on_later_page_and_ignores_pull_requests(self):
|
||||
api = Mock()
|
||||
marker = "<!-- gitea-mail-templates:template-release:v28.1.0 -->"
|
||||
api.request.side_effect = [[{"body": marker, "pull_request": {"url": "pr"}}],
|
||||
[{"body": marker, "state": "closed", "title": "Renamed", "html_url": "https://git.example/issues/1"}]]
|
||||
ACTIONS.create_issue(api, "v28.1.0")
|
||||
self.assertEqual(["GET", "GET"], [c.args[0] for c in api.request.call_args_list])
|
||||
self.assertIn("state=all", api.request.call_args_list[0].args[1])
|
||||
self.assertIn("page=2", api.request.call_args_list[1].args[1])
|
||||
|
||||
def test_other_release_versions_do_not_suppress_reminder(self):
|
||||
api = Mock()
|
||||
api.request.side_effect = [[{"body": "<!-- gitea-mail-templates:template-release:v28.0.0 -->"}], [],
|
||||
{"html_url": "https://git.example/issues/2"}]
|
||||
ACTIONS.create_issue(api, "v28.1.0")
|
||||
payload = api.request.call_args.args[2]
|
||||
self.assertIn("template-release:v28.1.0", payload["body"])
|
||||
self.assertIn("READMEs", payload["body"])
|
||||
|
||||
def test_invalid_versions_fail_before_api_call(self):
|
||||
api = Mock()
|
||||
for version in ("28.1.0", "v28.1.0-rc1", "../../x"):
|
||||
with self.assertRaises(ValueError):
|
||||
ACTIONS.create_issue(api, version)
|
||||
api.request.assert_not_called()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,101 @@
|
||||
"""Offline artifact/provenance tests independent of generated repository output."""
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
import zipfile
|
||||
|
||||
SPEC = importlib.util.spec_from_file_location("package_release", Path(__file__).with_name("package_release.py"))
|
||||
PACKER = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(PACKER)
|
||||
|
||||
|
||||
class PackagingTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.temp.cleanup)
|
||||
self.root = Path(self.temp.name)
|
||||
self.tag = "v28.0.0"
|
||||
self.commit = "a" * 40
|
||||
theme = json.dumps({"name": "demo", "mode": "shared"}).encode()
|
||||
css = b"a { color:blue; }"
|
||||
self.write("themes/demo/theme.json", theme)
|
||||
self.write("themes/demo/theme.css", css)
|
||||
files = {"mail/base/head.tmpl": b"<title>test</title>", "mail/base/footer.tmpl": b"<p>footer</p>"}
|
||||
meta = {"theme": "demo", "mode": "shared", "gitea_tag": self.tag, "gitea_commit": self.commit, "sources": {"theme.json": PACKER.digest(theme), "theme.css": PACKER.digest(css)}, "files": {p: PACKER.digest(data) for p, data in files.items()}}
|
||||
for path, data in files.items():
|
||||
self.write("build/themes/demo/" + path, data)
|
||||
self.write_json("build/themes/demo/build.json", meta)
|
||||
self.write_json("gitea.lock.json", {"tag": self.tag, "commit": self.commit, "files": {"LICENSE": PACKER.digest(b"MIT"), "options/locale/locale_en-US.json": PACKER.digest(b"{}")}})
|
||||
self.write("build/upstream/LICENSE", b"MIT")
|
||||
manifest = {"upstream": self.tag, "upstream_commit": self.commit, "themes": ["demo"], "languages": ["en-US"], "registry": {"activate": {}}}
|
||||
self.write("preview/rendered.js", ("window.__MAIL_PREVIEW__ = " + json.dumps(manifest) + ";").encode())
|
||||
self.write("preview/rendered/en-US.js", ('window.__MAIL_PREVIEW_DATA__["en-US"] = ' + json.dumps({"demo": {"activate": "<html></html>"}}) + ";").encode())
|
||||
for name in ["LICENSE", "AGENTS.md", "README.md", "CONTRIBUTING.md", "COMPATIBILITY.md", "THIRD_PARTY_NOTICES.md", "docs/README.zh-CN.md", "docs/CONTRIBUTING.zh-CN.md", "preview/index.html", "docs/images/README.md"]:
|
||||
self.write(name, b"test")
|
||||
|
||||
def write(self, name, data):
|
||||
path = self.root / name
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_bytes(data)
|
||||
|
||||
def write_json(self, name, data):
|
||||
self.write(name, json.dumps(data).encode())
|
||||
|
||||
def test_packages_declared_outputs_only_and_never_overwrites(self):
|
||||
self.write("build/themes/stale/mail/stale.tmpl", b"stale")
|
||||
out = self.root / "artifacts"
|
||||
PACKER.package(self.root, self.tag, out)
|
||||
with zipfile.ZipFile(next(out.glob("*.zip"))) as archive:
|
||||
names = archive.namelist()
|
||||
self.assertTrue(any(name.endswith("themes/demo/mail/base/head.tmpl") for name in names))
|
||||
self.assertFalse(any("stale" in name or "/build/" in name for name in names))
|
||||
with self.assertRaises(ValueError):
|
||||
PACKER.package(self.root, self.tag, out)
|
||||
|
||||
def test_refuses_stale_source_tampered_output_and_wrong_version(self):
|
||||
with self.assertRaises(ValueError):
|
||||
PACKER.collect(self.root, "v28.1.0")
|
||||
self.write("themes/demo/theme.css", b"changed")
|
||||
with self.assertRaisesRegex(ValueError, "Source changed"):
|
||||
PACKER.collect(self.root, self.tag)
|
||||
|
||||
def test_refuses_missing_language_bundle(self):
|
||||
(self.root / "preview/rendered/en-US.js").unlink()
|
||||
with self.assertRaises(ValueError):
|
||||
PACKER.collect(self.root, self.tag)
|
||||
|
||||
def test_refuses_modified_generated_files(self):
|
||||
self.write("build/themes/demo/mail/base/head.tmpl", b"modified")
|
||||
with self.assertRaisesRegex(ValueError, "checksum mismatch"):
|
||||
PACKER.collect(self.root, self.tag)
|
||||
|
||||
def test_includes_nested_documentation_and_release_note_sources(self):
|
||||
documents = {
|
||||
"docs/INDEX.zh-CN.md": "中文索引".encode(),
|
||||
"docs/images/README.zh-CN.md": "截图指南".encode(),
|
||||
"docs/release-notes/v28.0.0.zh-CN.md": "发布说明".encode(),
|
||||
".github/release-notes/v28.0.0.md": b"English release notes",
|
||||
}
|
||||
for name, data in documents.items():
|
||||
self.write(name, data)
|
||||
self.write("docs/private.tmp", b"not documentation")
|
||||
files = PACKER.collect(self.root, self.tag)
|
||||
for name, data in documents.items():
|
||||
self.assertEqual(files[name], data)
|
||||
self.assertIn("AGENTS.md", files)
|
||||
self.assertNotIn("docs/private.tmp", files)
|
||||
|
||||
def test_packages_without_removed_optional_root_documents(self):
|
||||
for name in ["AGENTS.md", "THIRD_PARTY_NOTICES.md"]:
|
||||
(self.root / name).unlink()
|
||||
files = PACKER.collect(self.root, self.tag)
|
||||
self.assertNotIn("AGENTS.md", files)
|
||||
self.assertNotIn("THIRD_PARTY_NOTICES.md", files)
|
||||
self.assertIn("GITEA-LICENSE", files)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,122 +0,0 @@
|
||||
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'
|
||||
required: false
|
||||
|
||||
jobs:
|
||||
track:
|
||||
name: Track Gitea Release
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Determine latest Gitea version
|
||||
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
|
||||
|
||||
- name: Create Pull Request
|
||||
if: steps.version.outputs.skip != '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"
|
||||
body: |
|
||||
Gitea **${{ steps.version.outputs.new_version }}** has been released.
|
||||
|
||||
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 }}
|
||||
@@ -8,19 +8,32 @@ on:
|
||||
jobs:
|
||||
validate:
|
||||
name: Validate Templates
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: linux-amd64-docker-small
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.21'
|
||||
go-version: '1.25.x'
|
||||
|
||||
- name: Test — mail template data contexts
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Test workflow scripts
|
||||
run: python -B -m unittest discover -s .github/scripts -p 'test_*.py'
|
||||
|
||||
- name: Verify pinned official snapshot
|
||||
working-directory: tools
|
||||
run: |
|
||||
go run . upstream prepare
|
||||
go run . upstream verify
|
||||
|
||||
- name: Test — framework alignment and multilingual notification parity
|
||||
working-directory: tools
|
||||
run: go test ./...
|
||||
|
||||
- name: Lint — render all templates
|
||||
- name: Build and render all themes and official languages
|
||||
working-directory: tools
|
||||
run: go run . preview all
|
||||
|
||||
@@ -28,31 +41,66 @@ jobs:
|
||||
name: Package & Release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
needs: validate
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: linux-amd64-docker-small
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GITEA_TOKEN }}
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.25.x'
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Verify release tag matches the official snapshot
|
||||
run: |
|
||||
python -c 'import json, os; lock=json.load(open("gitea.lock.json")); assert lock["tag"] == os.environ["GITHUB_REF_NAME"], "Release tag must match the pinned Gitea tag"'
|
||||
|
||||
- name: Verify release notes
|
||||
run: test -s ".github/release-notes/${GITHUB_REF_NAME}.md"
|
||||
|
||||
- name: Package themes/
|
||||
run: |
|
||||
VERSION=${GITHUB_REF#refs/tags/}
|
||||
ARCHIVE="gitea-mail-templates-${VERSION}"
|
||||
mkdir -p "dist/${ARCHIVE}/docs"
|
||||
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/"
|
||||
cd dist
|
||||
zip -r "${ARCHIVE}.zip" "${ARCHIVE}"
|
||||
tar -czf "${ARCHIVE}.tar.gz" "${ARCHIVE}"
|
||||
- name: Generate static preview for the archive
|
||||
working-directory: tools
|
||||
run: go run . preview all
|
||||
|
||||
- name: Package generated installable themes and multilingual preview
|
||||
run: python -B .github/scripts/package_release.py --version "$TEMPLATE_RELEASE"
|
||||
env:
|
||||
TEMPLATE_RELEASE: ${{ github.ref_name }}
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
files: dist/*.zip,dist/*.tar.gz
|
||||
fail_on_unmatched_files: true
|
||||
body_path: .github/release-notes/${{ github.ref_name }}.md
|
||||
run: python -B .github/scripts/gitea_actions.py publish-release --version "$TEMPLATE_RELEASE"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TEMPLATE_RELEASE: ${{ github.ref_name }}
|
||||
GITEA_SERVER_URL: ${{ github.server_url }}
|
||||
GITEA_REPOSITORY: ${{ github.repository }}
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
|
||||
notify-release-docs:
|
||||
name: Remind to Update Release Documentation
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
needs: package
|
||||
concurrency:
|
||||
group: release-documentation-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
runs-on: linux-amd64-docker-small
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Create documentation reminder Issue
|
||||
run: python -B .github/scripts/gitea_actions.py create-issue --version "$TEMPLATE_RELEASE"
|
||||
env:
|
||||
TEMPLATE_RELEASE: ${{ github.ref_name }}
|
||||
GITEA_SERVER_URL: ${{ github.server_url }}
|
||||
GITEA_REPOSITORY: ${{ github.repository }}
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
@@ -1,7 +1,10 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
# Go dependencies
|
||||
vendor/
|
||||
|
||||
# Python test cache
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -18,8 +21,10 @@ Desktop.ini
|
||||
*.zip
|
||||
*.tar.gz
|
||||
|
||||
# Package lock file
|
||||
package-lock.json
|
||||
|
||||
# Preview generated files
|
||||
preview/rendered.js
|
||||
preview/rendered/
|
||||
build/
|
||||
/upstream/
|
||||
dist/
|
||||
node_modules/
|
||||
@@ -1,90 +0,0 @@
|
||||
# AGENTS.md — Gitea Mail Templates
|
||||
|
||||
## Project Overview
|
||||
|
||||
A curated collection of email template themes (10 visual styles) for self-hosted Gitea instances. Each theme contains 11 Go `html/template` files covering all Gitea notification email types.
|
||||
|
||||
## Repository Layout
|
||||
|
||||
```
|
||||
themes/ # Template themes (10 styles, 11 .tmpl each = 110 source files)
|
||||
aurora/ # Ethereal / Dreamlike
|
||||
bloom/ # Creative / Startup (glassmorphism)
|
||||
ember/ # Community / Open Source
|
||||
heritage/ # Education / Research
|
||||
horizon/ # Enterprise / Corporate
|
||||
ink/ # Editorial / Publishing
|
||||
mono/ # Minimal / Swiss design
|
||||
neon/ # Cyberpunk / Gaming
|
||||
terminal/ # Developers / Tech
|
||||
terra/ # Nature / Sustainability
|
||||
tools/ # Go CLI tooling (modular, zero dependencies)
|
||||
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)
|
||||
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)
|
||||
docs/ # Bilingual documentation (English + Simplified Chinese)
|
||||
```
|
||||
|
||||
## Working With Templates
|
||||
|
||||
### Template Files
|
||||
- All `.tmpl` files use Go `html/template` syntax
|
||||
- Must use only Gitea's built-in template functions: `AppUrl`, `DotEscape`, `QueryEscape`, `ShortSha`, `HTMLFormat`, `PathEscapeSegments`, `FileSize`
|
||||
- Must use only official Gitea translation keys (`mail.*` namespace)
|
||||
- Never reference `.DisplayName` in templates where the data context lacks it (collaborator, transfer, release, workflow_run, assigned, default)
|
||||
- Each style must have all 11 template types
|
||||
|
||||
### Adding a New Theme
|
||||
1. Scaffold the new theme: `cd tools && go run . create <name>` — 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
|
||||
- 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
|
||||
- `tools/config/` handles config loading and data flattening
|
||||
- `tools/preview/` implements the rendering engine (template funcs, locale, engine, markSafeHTML)
|
||||
- `tools/cli/` implements CLI subcommands using `github.com/urfave/cli/v2`
|
||||
- `tools/preview/server.go` pure Go dev server with SSE live reload and in-process template re-rendering
|
||||
- Uses Go's native `html/template` package for template rendering
|
||||
|
||||
## Versioning
|
||||
|
||||
- Release tags identify actual downloadable packages; the compatibility matrix distinguishes released tags from source-only fixes
|
||||
- The current release is **v1.27.3**, verified against Gitea 1.27.3; it fixes the push-notification bug in Bloom, Ember, and Heritage that affects v1.27.2 on Gitea 1.27.1+. 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
|
||||
|
||||
## Commit Conventions
|
||||
- `style(name):` — template changes for a specific theme
|
||||
- `preview:` — preview tooling changes
|
||||
- `tools:` — Go CLI/build tooling changes
|
||||
- `docs:` — documentation and translations
|
||||
- `fix:` — bug fixes
|
||||
- `project:` — README, LICENSE, AGENTS.md, meta
|
||||
- `refactor:` — code restructuring (e.g. modularization)
|
||||
- `chore:` — maintenance (config updates, build scripts)
|
||||
|
||||
## Constraints
|
||||
- No JavaScript framework dependencies — preview is vanilla JS
|
||||
- No external Go dependencies — build uses stdlib only
|
||||
- Templates must remain compatible with Gitea's `html/template` execution environment
|
||||
- Preview works with `file://` protocol (no server needed)
|
||||
@@ -1,32 +1,61 @@
|
||||
# Gitea Compatibility
|
||||
<!-- DOC-TAGS: {"TRACKER":["LATEST-VERIFIED","VERSION-MAP","HISTORY"]} -->
|
||||
|
||||
This document tracks the compatibility between **Gitea Mail Templates** releases and **Gitea** versions.
|
||||
This document lists supported release combinations, known limitations and the checks used to assess compatibility.
|
||||
|
||||
## Quick Reference
|
||||
[Project overview](README.md) · [Contributor guide](CONTRIBUTING.md) · [简体中文使用说明](docs/README.zh-CN.md)
|
||||
|
||||
<!-- TRACKER:QUICK-REF-MAX OFFSET=3 -->
|
||||
| Template Release | Min Gitea | Max Tested Gitea | Status |
|
||||
|-----------------|-----------|-----------------|--------|
|
||||
| **v1.27.3** | **1.25.0** | **1.27.3** | ✅ Active |
|
||||
| **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 |
|
||||
## Compatibility Matrix
|
||||
|
||||
> **Latest verified:** Release v1.27.3 passes the Gitea 1.27.3 source audit and push-render test. The older v1.27.2 archive contains three broken push-notification templates. <!-- TRACKER:LATEST-VERIFIED -->
|
||||
Select a template release for the Gitea version you run. `[PASS]` records a compatible combination; `[PENDING]` means verification is incomplete. Legacy entries reflect the project's recorded compatibility assessments rather than a fresh test of every patch release.
|
||||
|
||||
## Versioning
|
||||
<!-- TRACKER:VERSION-MAP -->
|
||||
| Gitea version | Recommended template release | Status | Notes |
|
||||
|---------------|------------------------------|--------|-------|
|
||||
| 28.1.0 | — | [PENDING] | Compatibility verification pending |
|
||||
| 28.0.0 | **v28.0.0** | [PASS] | Earlier template releases use the removed `FileSize` mail function |
|
||||
| 1.27.3 | **v1.27.3** | [PASS] | Push-to-PR commit links fixed in every theme |
|
||||
| 1.27.2 | **v1.27.3** | [PASS] | Matching v1.27.2 has a push-notification defect in Bloom, Ember, and Heritage |
|
||||
| 1.27.1 | **v1.27.3** | [PASS] | Older v1.0.x templates use obsolete push-commit fields; v1.27.2 is only partially fixed |
|
||||
| 1.27.0 | — | [PENDING] | New commit data shape breaks older push templates; v1.27.3 is a plausible fix but was not release-tested here |
|
||||
| 1.26.4 | **v1.0.1** | [PASS] | — |
|
||||
| 1.26.3 | **v1.0.1** | [PASS] | — |
|
||||
| 1.26.2 | **v1.0.1** | [PASS] | — |
|
||||
| 1.26.1 | **v1.0.1** | [PASS] | — |
|
||||
| 1.26.0 | **v1.0.1** | [PASS] | — |
|
||||
| 1.25.5 | **v1.0.1** | [PASS] | — |
|
||||
| 1.25.0 | **v1.0.1** | [PASS] | — |
|
||||
<!-- /TRACKER:VERSION-MAP -->
|
||||
|
||||
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.
|
||||
v1.0.1 retains the pre-1.27 push-commit fields used by the listed Gitea 1.25/1.26 releases. Gitea 1.24.x and earlier use a different custom-mail-template layout and are [UNSUPPORTED]. The v1.0.0 template tag is superseded by v1.0.1; it is not a separate recommendation.
|
||||
|
||||
| Gitea version | Template release |
|
||||
|---------------|------------------|
|
||||
| 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 |
|
||||
<!-- TRACKER:LATEST-VERIFIED -->
|
||||
> **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. For other Gitea versions, follow the per-version matrix above.
|
||||
<!-- /TRACKER:LATEST-VERIFIED -->
|
||||
|
||||
- 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 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.
|
||||
## Snapshot-Driven Source Status
|
||||
|
||||
The source architecture on `main` is **unreleased**. Its lock pins Gitea **v28.0.0**, commit `15b8a5805adf57c5189602008d38cccfd3c795e0`, with 13 mail files (11 entrypoints and 2 shared partials) and 28 locale files. It targets Gitea 28 and later, with each new snapshot requiring review. Historical archives retain the compatibility recorded above.
|
||||
|
||||
Themes contain CSS and metadata. The shared framework adapts official templates into reusable headers, action buttons, fallback links and footers while preserving notification conditions, subjects and functional URLs. Official inputs are downloaded to `build/upstream/` and verified against the committed `gitea.lock.json`. Builds reject mismatched checksums, missing English keys, changed adapter references and unreviewed action anchors. New mail types require framework support and fixtures.
|
||||
|
||||
### Translation Behavior
|
||||
|
||||
Missing translations in other languages follow the official English fallback and are reported as `[FALLBACK]`. Gitea v28.0.0's Polish `mail.team_invite.text_1` starts with malformed `%[1]z…` instead of `%[1]s`; preview preserves this official defect and reports `[UPSTREAM-WARN]`. The exception matches the exact source text; other formatting defects fail rendering.
|
||||
|
||||
### Validation Scope
|
||||
|
||||
Source validation covers rendering and notification parity across all themes and languages. Optional browser checks cover static and HTTP previews, language switching and mobile layouts. An isolated Gitea smoke test checks template loading and captures a password-reset email. These checks do not establish rendering compatibility with Gmail, Outlook or Apple Mail; test those clients for your deployment. The release workflow does not automatically run the optional browser or real-Gitea suites.
|
||||
|
||||
Snapshot preparation, cache verification and version updates are separate operations. `upstream prepare` uses the root lock to create an absent cache; `upstream verify` checks an existing cache offline. `upstream sync --tag vX.Y.Z` explicitly replaces the snapshot and lock. A successful sync does not certify compatibility or update this matrix. See [command usage and cache recovery](CONTRIBUTING.md#official-snapshot-updates).
|
||||
|
||||
## Versioning and Known Exceptions
|
||||
|
||||
Release tags identify downloadable packages. Use the matrix to select a supported combination: v1.27.2 has a known defect despite its matching version number, and there is no v1.27.1 template tag. Changes on `main` become part of a release only when a new tag and archive are published.
|
||||
|
||||
- Gitea 1.27.0 changed the push-to-PR commit data shape. Gitea 1.27.1 fixed its **bundled** mail template, but custom overrides still need the new `.UserCommit.GitCommit` path. v1.27.2 updated seven themes; v1.27.3 completed the remaining three. Older v1.0.x templates use the pre-1.27 path. See the [upstream regression report](https://github.com/go-gitea/gitea/issues/38469), [upstream fix](https://github.com/go-gitea/gitea/pull/38467), and [v1.27.2 correction](.github/release-notes/v1.27.2.md).
|
||||
- Gitea 28.0.0 removed the mail-template `FileSize` function in favor of `FormatByteSize` ([mail function map](https://raw.githubusercontent.com/go-gitea/gitea/v28.0.0/modules/templates/mail.go)). v28.0.0 uses the new function; older template releases can fail when rendering release attachments. Conversely, v28.0.0 is not a drop-in replacement for earlier Gitea mail contexts.
|
||||
- A source-only fix is **unreleased** until its tag and downloadable archive exist. New upstream versions remain [PENDING] until their compatibility is checked.
|
||||
|
||||
## Check Your Gitea Version
|
||||
|
||||
@@ -38,30 +67,35 @@ gitea --version
|
||||
|
||||
## Gitea Version History — Mail Template Impact
|
||||
|
||||
<!-- TRACKER:VERSION-INSERT OFFSET=2 -->
|
||||
| Gitea | Release Date | Mail Template Changes | Breaking? |
|
||||
|-------|-------------|----------------------|-----------|
|
||||
| **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 |
|
||||
| **1.27.1** | 2026-07-27 | Push commit data paths changed: .ID → .UserCommit.GitCommit.ID (#38467); older custom templates can fail on push notifications | Yes (old .ID paths) |
|
||||
| **1.27.0** | 2026-07-13 | None — no mail template changes | No |
|
||||
| **1.26.4** | 2026-06-21 | None — hotfix release | No |
|
||||
| **1.26.3** | 2026-06-20 | None — security release | No |
|
||||
| **1.26.2** | 2026-05-20 | None — security + bug fixes | No |
|
||||
| **1.26.1** | 2026-04-22 | None — bug fixes | No |
|
||||
| **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/<category>/<type>.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 |
|
||||
This table describes changes in **Gitea**, not fixes in this template repository. `None` means no relevant new mail-template impact for that Gitea release; the recommended archive is listed in the matrix above.
|
||||
|
||||
<!-- TRACKER:HISTORY -->
|
||||
| Gitea | Release Date | Mail Template Changes | Impact |
|
||||
|-------|-------------|-----------------------|--------|
|
||||
| **28.1.0** | 2026-10-06 | [PENDING] Review of mail-template changes | TBD |
|
||||
| **28.0.0** | 2026-09-29 | `FileSize` replaced by `FormatByteSize`; mail template internals reorganized | **Breaking:** legacy release-attachment templates can fail |
|
||||
| **1.27.3** | 2026-08-29 | None | None |
|
||||
| **1.27.2** | 2026-08-14 | None | None |
|
||||
| **1.27.1** | 2026-07-27 | Bundled push template updated to `.UserCommit.GitCommit` ([fix](https://github.com/go-gitea/gitea/pull/38467)) | **Existing incompatibility persists:** old custom push-to-PR templates can fail |
|
||||
| **1.27.0** | 2026-07-13 | Push commit data moved under `.UserCommit.GitCommit` ([report](https://github.com/go-gitea/gitea/issues/38469)) | **Breaking:** old push-to-PR templates can fail |
|
||||
| **1.26.4** | 2026-06-21 | None | None |
|
||||
| **1.26.3** | 2026-06-20 | None | None |
|
||||
| **1.26.2** | 2026-05-20 | None | None |
|
||||
| **1.26.1** | 2026-04-22 | None | None |
|
||||
| **1.26.0** | 2026-04-19 | None | None |
|
||||
| **1.25.5** | 2026-03-10 | None | None |
|
||||
| **1.25.0** | 2025 | Custom-mail paths and subject/body format changed ([refactor](https://github.com/go-gitea/gitea/pull/35150)) | **Breaking:** older template layout is not supported |
|
||||
| **≤ 1.24.x** | — | Legacy custom-mail layout | [UNSUPPORTED] |
|
||||
<!-- /TRACKER:HISTORY -->
|
||||
|
||||
## Template Variable Reference
|
||||
|
||||
All 11 template types use only Gitea built-in variables and functions. Verified against Gitea source (`services/mailer/` + `modules/templates/mail.go`).
|
||||
Official templates define each mail context. The tables below summarize functions and example data relevant to development; they are not a complete API reference. Check the locked official sources and framework adapter when changing a template or fixture.
|
||||
|
||||
### Template Functions (available in all templates)
|
||||
### Relevant Template Functions
|
||||
|
||||
| Function | Since Gitea | Notes |
|
||||
|----------|------------|-------|
|
||||
| Function | Gitea version | Notes |
|
||||
|----------|---------------|-------|
|
||||
| `AppName` | ≤ 1.21 | Application name |
|
||||
| `AppUrl` | ≤ 1.21 | Application base URL |
|
||||
| `AppDomain` | ≤ 1.21 | Server domain |
|
||||
@@ -69,7 +103,8 @@ All 11 template types use only Gitea built-in variables and functions. Verified
|
||||
| `QueryEscape` | ≤ 1.21 | URL query encoding |
|
||||
| `PathEscapeSegments` | ≤ 1.21 | Per-segment path encoding |
|
||||
| `ShortSha` | ≤ 1.21 | Truncated commit hash |
|
||||
| `FileSize` | ≤ 1.21 | Human-readable file size |
|
||||
| `FileSize` | Before 28.0.0 | Used by older template releases for attachment sizes; unavailable in Gitea 28 mail templates |
|
||||
| `FormatByteSize` | 28.0.0 | Used by v28.0.0 for attachment sizes; unavailable in earlier Gitea mail templates |
|
||||
| `HTMLFormat` | ≤ 1.21 | Render string as safe HTML |
|
||||
| `Iif` | ≤ 1.21 | Inline conditional |
|
||||
| `dict` | ≤ 1.21 | Build maps from key-value pairs |
|
||||
@@ -78,7 +113,6 @@ All 11 template types use only Gitea built-in variables and functions. Verified
|
||||
| `SliceUtils` | ≤ 1.21 | Slice manipulation helpers |
|
||||
| `JsonUtils` | ≤ 1.21 | JSON helpers |
|
||||
| `DumpVar` | ≤ 1.21 | Debug variable dump |
|
||||
| `SanitizeHTML` | ≤ 1.21 | **Deprecated in 1.26** — use `HTMLFormat` |
|
||||
|
||||
### Data Contexts by Template
|
||||
|
||||
@@ -90,30 +124,42 @@ All 11 template types use only Gitea built-in variables and functions. Verified
|
||||
| `user/auth/reset_passwd` | `DisplayName`, `Code`, `ResetPwdCodeLives` |
|
||||
| `org/team_invite` | `Inviter`, `Team`, `Organization`, `InviteURL`, `Invite` |
|
||||
| `repo/collaborator` | `Subject`, `RepoName`, `Link` |
|
||||
| `repo/transfer` | `Doer`, `User`, `Repo`, `Link`, `Destination` |
|
||||
| `repo/transfer` | `Subject`, `Repo`, `Link` |
|
||||
| `repo/release` | `Release` (with `Publisher`, `TagName`, `Title`, `RenderedNote`, `Attachments`), `Link` |
|
||||
| `repo/actions/workflow_run` | `Run` (with `WorkflowID`, `HTMLURL`), `Jobs`, `RunStatusText` |
|
||||
| `repo/issue/assigned` | `Doer`, `Issue`, `Link`, `IsPull` |
|
||||
| `repo/actions/workflow_run` | `Subject`, `Run` (with `WorkflowID`, `HTMLURL`), `Jobs` (with status class, icon CID/alt, attempt, URL and duration) |
|
||||
| `repo/issue/assigned` | `Subject`, `Doer`, `Issue`, `Link`, `IsPull`, `CanReply` |
|
||||
| `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.
|
||||
`.DisplayName` is available in the authentication mail contexts. Collaborator, transfer, release, workflow, assignment and issue-update templates use the context-specific fields listed above.
|
||||
|
||||
### Translation Keys
|
||||
|
||||
All templates use Gitea's official `mail.*` translation namespace. Keys are stable across Gitea 1.25+.
|
||||
Official templates reference `mail.*` keys and `actions.runs.attempt`. AST-based checks inspect `.locale.Tr`, `$.locale.Tr` and both plural keys in `TrN`, including nested pipelines. Every referenced key must exist in the locked English catalog. Preview loads full official locale JSON files rather than a copied Go dictionary; formatting, escaping and plural selection follow the reviewed Gitea adapter.
|
||||
|
||||
## How Compatibility Is Verified
|
||||
|
||||
1. **Automated lint** — CI renders all templates via `go run . preview all` on every push
|
||||
2. **Source audit** — Template data contexts are cross-referenced against Gitea's `services/mailer/` package
|
||||
3. **Release checklist** — Each release confirms the max-tested Gitea version in this file
|
||||
1. **Locked input verification** — run `go run . upstream prepare` then `go run . upstream verify` from `tools/`; first preparation downloads missing cache files, verification checks hashes, adapter references and English keys offline.
|
||||
2. **Framework alignment** — `go test ./...` checks deterministic shared adaptation/generation and fails on changed primary-action anchors; theme sources cannot own business templates.
|
||||
3. **Notification semantics and controls** — tests compare subjects, notification text and functional links for every theme/language and critical branches, accounting explicitly for added controls/branding. Browser QA checks translated button labels, identical fallback targets, logos and mobile layout. `go run . preview all` builds all language bundles and reports fallback/upstream defects.
|
||||
4. **Matching-instance smoke test** — before publication, load generated overrides in an isolated Gitea matching the snapshot and capture a real notification or password-reset mail. The admin test email does not exercise custom templates.
|
||||
5. **Release identity** — the release tag must match the snapshot tag. Only mark the new release verified after its tests and smoke check; published tags/assets remain unchanged.
|
||||
|
||||
## 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) records upstream releases as pending. Snapshot updates and compatibility verification require a separate review.
|
||||
|
||||
Participating Markdown files declare managed subject/content pairs in a `DOC-TAGS` JSON comment. Each body is enclosed by `<!-- TRACKER:CONTENT -->` and `<!-- /TRACKER:CONTENT -->`, or the corresponding `RELEASE` pair. Only the first occurrence of a repeated pair in a document is processed. Undeclared, unknown or unclosed blocks, and missing declared blocks, cause validation to fail.
|
||||
|
||||
| Managed blocks | Update policy |
|
||||
|---|---|
|
||||
| `TRACKER:VERSION-MAP`, `TRACKER:HISTORY` | Add pending rows for new upstream releases |
|
||||
| `TRACKER:UPSTREAM` | Update the upstream version and pending status |
|
||||
| `TRACKER:BADGE` | Update the upstream version while retaining the last tested version |
|
||||
| `TRACKER:LATEST-TESTED`, `TRACKER:LATEST-VERIFIED` | Updated manually after verification |
|
||||
| `RELEASE:HEADER`, `RELEASE:SUMMARY`, `RELEASE:CURRENT` | Updated by the release workflow after publication; archive labels are prepared during packaging |
|
||||
|
||||
The [release workflow](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/src/branch/main/.github/workflows/release.yml) keeps release links at `/releases/latest` and leaves published tags unchanged. Actions execution and write permissions have not been verified on the configured Gitea host. Until confirmed, check [upstream releases](https://github.com/go-gitea/gitea/releases) and published assets manually. New template tags are published when template content changes; see [versioning and known exceptions](#versioning-and-known-exceptions).
|
||||
|
||||
## 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
|
||||
Open an issue with the Gitea version, template release or source commit, affected theme and mail type, language, reproduction steps and error output. Include the mail client for display problems. See the [reporting guide](CONTRIBUTING.md#reporting-problems) for source-build diagnostics.
|
||||
@@ -1,100 +1,219 @@
|
||||
# Contributing to Gitea Mail Templates
|
||||
|
||||
Thanks for your interest in contributing! This project aims to provide a diverse, well-maintained collection of email templates for the Gitea ecosystem.
|
||||
[简体中文](docs/CONTRIBUTING.zh-CN.md) · [Project overview](README.md) · [Compatibility](COMPATIBILITY.md)
|
||||
|
||||
---
|
||||
Contributions to themes, tooling, tests, documentation and translations are welcome. This guide covers the current source architecture; installation and release selection are described in the [README](README.md#installation).
|
||||
|
||||
## Ways to Contribute
|
||||
## Local Development
|
||||
|
||||
### Adding a New Style
|
||||
|
||||
1. Scaffold the new style: `cd tools && go run . create <your-style-name>` — this creates the directory structure with placeholder `.tmpl` files for all 11 email types
|
||||
2. Edit each `.tmpl` file in `themes/<your-style-name>/` with your unique visual design
|
||||
3. Regenerate the preview: `cd tools && go run . preview all` — the build script auto-discovers all theme directories under `themes/` and generates the theme selector dynamically
|
||||
4. Submit a PR with screenshots of rendered emails (≤ 50 KiB each, 10–20 KiB recommended)
|
||||
|
||||
### Style Guidelines
|
||||
|
||||
- Each style must include **all 11 template types** listed in the README
|
||||
- Use only Gitea's built-in template functions — check the [Gitea source](https://github.com/go-gitea/gitea) for reference
|
||||
- Translation keys must come from Gitea's official locale files (`mail.*` namespace)
|
||||
- **Never reference `.DisplayName`** in templates where the data context lacks it (collaborator, transfer, release, workflow_run, assigned, default)
|
||||
- Design for 600px max-width email clients
|
||||
- Test against major email clients (Apple Mail, Gmail, Outlook) when possible
|
||||
|
||||
### Bug Reports
|
||||
|
||||
If a template doesn't render correctly:
|
||||
|
||||
1. Check that all referenced Go template variables exist — compare against the Gitea source mail templates
|
||||
2. Verify translation keys match Gitea's locale files
|
||||
3. Confirm `.DisplayName` isn't used in templates that lack it
|
||||
4. Regenerate the preview: `cd tools && go run . preview all`
|
||||
5. Open an issue with: the style name, which email type, and the error or unexpected output
|
||||
|
||||
### Documentation Improvements
|
||||
|
||||
Documentation updates, preview screenshots, installation guides, and translations are always welcome.
|
||||
|
||||
---
|
||||
|
||||
## Development Setup
|
||||
|
||||
- **Go 1.21+** for template rendering and the CLI tool
|
||||
|
||||
### Previewing Locally (Static)
|
||||
|
||||
1. Run `cd tools && go run . preview all` to generate rendered data
|
||||
2. Open `preview/index.html` directly in a browser — no server needed
|
||||
3. Use the theme switcher, template selector, and view mode toggles
|
||||
|
||||
### Dev Server (Live Reload)
|
||||
Use **Go 1.24 or later**. From the repository root, prepare the dependencies and locked official inputs, then run the checks and generate the preview:
|
||||
|
||||
```bash
|
||||
cd tools && go run . dev
|
||||
# → http://localhost:3456
|
||||
cd tools
|
||||
go mod download
|
||||
go run . upstream prepare
|
||||
go test ./...
|
||||
go run . preview all
|
||||
```
|
||||
|
||||
- Watches `themes/**/*.tmpl` — auto-rebuilds on save
|
||||
- Pure Go HTTP server with SSE push — no external dependencies
|
||||
- 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`
|
||||
Open `preview/index.html` in a browser, or run `go run . dev` from `tools/` and visit [http://127.0.0.1:3456](http://127.0.0.1:3456) for live reload. The development server watches themes, the framework, the lock/cache and preview fixtures.
|
||||
|
||||
### Integration Testing
|
||||
The CLI uses urfave/cli and the x/net HTML parser. Python 3.11 or later is used by the documentation and packaging checks. Node.js and the dependencies in `tools/qa` are needed only for browser QA.
|
||||
|
||||
Deploy the templates to a Gitea instance and verify with real transactional emails.
|
||||
The admin test email (**Site Administration > Configuration > Mailer > Send Test Email**)
|
||||
does not use custom mail templates — it follows a built-in code path.
|
||||
### Common Commands
|
||||
|
||||
The most reliable method is to trigger a real notification. For example, the password
|
||||
reset flow:
|
||||
Run these commands from `tools/`. Place flags before positional arguments.
|
||||
|
||||
1. Log out and click **"Forgot password"** on the login page
|
||||
2. Enter your account email and submit
|
||||
3. Check the password reset email — it will render with your custom mail templates
|
||||
| Command | Result |
|
||||
|---|---|
|
||||
| `go run . list` | List available themes |
|
||||
| `go run . create my-theme` | Create theme metadata and CSS |
|
||||
| `go run . build my-theme` | Generate one theme's installable templates |
|
||||
| `go run . build all` | Generate every theme |
|
||||
| `go run . preview all` | Build every theme and render all official languages |
|
||||
| `go run . dev` | Start the development server on port 3456 |
|
||||
| `go run . dev --port 3457` | Use a different local port |
|
||||
| `go run . delete my-theme` | Remove the named theme's source directory |
|
||||
|
||||
---
|
||||
`build` writes to `build/themes/<name>/mail/` and records file and source hashes in `build.json`. `preview` also builds the templates, then writes `preview/rendered.js` and `preview/rendered/<locale>.js`. These generated files and the downloaded inputs are ignored by Git. Keep them out of contributions.
|
||||
|
||||
## Adding a Theme
|
||||
|
||||
1. Run `go run . create my-theme` from `tools/`.
|
||||
2. Edit `themes/my-theme/theme.json` and `theme.css`.
|
||||
3. Run `go test ./...` and `go run . preview all` from `tools/`.
|
||||
4. Check desktop and mobile layouts in English, Simplified Chinese and at least one other official language. Include screenshots with the pull request.
|
||||
|
||||
Theme names start with a lowercase letter and may contain lowercase letters, digits and hyphens. A theme directory contains only `theme.json` and `theme.css`.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-theme",
|
||||
"description": "A short description of the theme",
|
||||
"mode": "framed",
|
||||
"layout": "standard"
|
||||
}
|
||||
```
|
||||
|
||||
| Mode | Generated presentation |
|
||||
|---|---|
|
||||
| `framed` | Shared controls and a framework layout, styled with the theme's CSS. New themes use this mode with the `standard` layout. |
|
||||
| `shared` | CSS in the official head partial and the official footer partial; no additional framework controls or per-mail overrides. |
|
||||
|
||||
### Shared Framework
|
||||
|
||||
Headers, action buttons, fallback links, sidebars and footers live in `framework/mail/base/`. Layout presets live in `framework/layouts/`; the optional `layout` field selects a preset for framed themes. Colors, fonts and spacing belong in theme CSS.
|
||||
|
||||
`tools/builder/framework.go` adapts official action values and translation keys for the shared controls. Official templates retain responsibility for notification conditions, subjects, attachments and contextual links. Changes to controls or layout structure belong in the framework so all themes use the same adaptation logic.
|
||||
|
||||
Layout fragments must form balanced presentation markup. `__HEADER__` inserts the shared header, `__MAIL_TYPE__` expands to the discovered mail ID, and `__SIDEBAR__` inserts the shared sidebar in footer fragments. Deployment logos use `{{AppUrl}}assets/img/favicon.png`; generated preview data embeds the downloaded icon for offline display.
|
||||
|
||||
## Design Guidelines
|
||||
|
||||
- Use email-compatible CSS. Theme CSS must not load external resources, generate text or hide official content.
|
||||
- Use presentation markup for layout fragments. The validator accepts `table`, `tbody`, `tr`, `td`, `div` and `span` with supported presentation attributes.
|
||||
- When editing an existing theme, preserve its visual identity: palette, typography, borders, header, buttons and layout.
|
||||
- Check desktop and 390px mobile previews, including long text and URLs. Test Gmail, Outlook and Apple Mail where available; the browser preview does not emulate mail clients.
|
||||
- Keep gallery images in PNG format, at most 50 KiB each; 10–20 KiB is preferred. Follow the [capture guide](docs/images/README.md) for consistent images.
|
||||
|
||||
## Official Snapshot Updates
|
||||
|
||||
`gitea.lock.json` records a stable Gitea 28+ tag, its commit and per-file SHA-256 hashes. Official mail templates, locale catalogs, the favicon, license and reviewed adapter references are downloaded into `build/upstream/`. Only the generated root lock is committed; official files are not maintained in the source tree.
|
||||
|
||||
### Command Reference
|
||||
|
||||
Run from `tools/`. Each upstream subcommand accepts `--root <repository-root>`, defaulting to `..` relative to the working directory. Place the flag after the subcommand and quote paths containing spaces:
|
||||
|
||||
```powershell
|
||||
go run . upstream verify --root "C:\Projects\GiteaMailTemplates"
|
||||
```
|
||||
|
||||
| Command | Behavior |
|
||||
|---|---|
|
||||
| `go run . upstream prepare` | Read the root lock. Download and validate its exact inputs if the cache is absent; otherwise verify the existing cache offline. Leaves the root lock unchanged. |
|
||||
| `go run . upstream verify` | Check the existing cache against the root lock, offline and read-only. Reports `[PASS]` and lists non-English missing keys as `[FALLBACK]`. |
|
||||
| `go run . upstream sync --tag vX.Y.Z` | Resolve an explicit stable Gitea 28+ tag through the GitHub API, download and validate its files, then replace the cache and generate the root lock. |
|
||||
|
||||
`sync` uses `api.github.com`; file downloads use `raw.githubusercontent.com`. These commands do not accept a local Gitea checkout, authentication tokens or an implicit `latest` version. Network errors and API rate limits stop the operation. Go may separately need network access to download its toolchain or modules.
|
||||
|
||||
### Existing Clone and Offline Use
|
||||
|
||||
`build`, `preview` and `dev` prepare inputs automatically. They require the committed root lock and validate cached files before use. Once the cache, Go toolchain and dependencies are available, builds can run offline.
|
||||
|
||||
Use `upstream verify` to check the cache independently. It validates official file hashes, reviewed adapter references and official template keys in the English catalog. Build and rendering checks also cover framework translation keys and primary-action anchors, so cache verification is only one part of compatibility testing.
|
||||
|
||||
### Missing Lock and Explicit Version Updates
|
||||
|
||||
For an existing clone, restore the tracked `gitea.lock.json` if it is missing. The copy at `build/upstream/lock.json` cannot replace it. When intentionally initializing the lock, run:
|
||||
|
||||
```bash
|
||||
cd tools
|
||||
go run . upstream sync --tag v28.0.0
|
||||
go run . upstream verify
|
||||
go test ./...
|
||||
go run . preview all
|
||||
```
|
||||
|
||||
To review a different version, specify its tag explicitly. Review the lock diff, update the framework adapter and fixtures as needed, and complete the checks and matching-instance smoke test before updating compatibility records. `sync` changes the cache and lock only; documentation and release publication are separate steps. Use `prepare` for a missing cache.
|
||||
|
||||
Changes to the reviewed translation or mail-renderer sources require an adapter review before their reference hashes can be updated. Official English must contain all referenced keys; other languages use English fallback. New mail types require framework alignment and fixtures in `tools/data/templates_config.json`, which supplies preview metadata and example contexts. Keep integer values as integers in JSON fixtures for Go formatting.
|
||||
|
||||
Network or validation failures before cache replacement leave existing inputs unchanged. Cache replacement and root-lock writing are separate operations; if a filesystem error interrupts them, inspect both before retrying.
|
||||
|
||||
### Cache Recovery
|
||||
|
||||
`prepare` reports corrupt, incomplete or lock-mismatched caches without repairing them. `sync` also refuses to replace a non-empty invalid snapshot. Inspect the error and move the generated cache aside before preparing it again from the reviewed root lock.
|
||||
|
||||
For example, in PowerShell **from the repository root**, with an unused backup name:
|
||||
|
||||
```powershell
|
||||
Move-Item -LiteralPath .\build\upstream -Destination .\build\upstream.backup
|
||||
Set-Location tools
|
||||
go run . upstream prepare
|
||||
go run . upstream verify
|
||||
```
|
||||
|
||||
Preserve the rest of `build/` and leave official cached files unedited. To return to a previous snapshot, restore its reviewed root lock and rebuild the cache in the same way. Stop `dev` before replacing inputs, and avoid concurrent processes writing to the same cache.
|
||||
|
||||
### Known Upstream Formatting Issue
|
||||
|
||||
Gitea v28.0.0 contains a Polish `mail.team_invite.text_1` placeholder defect. Preview preserves the official output and reports `[UPSTREAM-WARN]`. The exception matches the exact reviewed source text; other formatting errors fail rendering. Report upstream defects without modifying the cached locale files.
|
||||
|
||||
## Verification and Release
|
||||
|
||||
### Checks for a Pull Request
|
||||
|
||||
From `tools/`, run `go test ./...` and `go run . preview all`. Tests cover deterministic generation, changes to action anchors, translation keys, and notification subjects, text and links across all themes and languages. Fixtures include push, review, reply, workflow and attachment branches. Shared controls and branding are accounted for separately from official notification content.
|
||||
|
||||
For documentation, tracker or packaging changes, run from the repository root:
|
||||
|
||||
```bash
|
||||
python -B -m unittest discover -s .github/scripts -p 'test_*.py'
|
||||
```
|
||||
|
||||
Keep English and Simplified Chinese guides aligned when changing shared instructions. Include a description of the change, relevant checks and screenshots for visual changes in the pull request.
|
||||
|
||||
### Browser and Gitea Checks
|
||||
|
||||
After generating preview data, run the optional browser suite:
|
||||
|
||||
```bash
|
||||
cd tools/qa
|
||||
npm install
|
||||
npx playwright install chromium
|
||||
npm test
|
||||
```
|
||||
|
||||
To use installed Chrome or Edge, set `BROWSER_EXECUTABLE_PATH` instead of installing Chromium. Set `PREVIEW_DEV_URL` to include a running HTTP preview in the checks. `npm test -- --update-gallery` also refreshes gallery screenshots.
|
||||
|
||||
Before a release, test generated overrides in an isolated Gitea instance matching the locked version and capture a real password-reset or notification email. The administration test-email button bypasses custom templates.
|
||||
|
||||
The optional `tools/integration` test uses disposable SQLite data, users, Git/SSH paths and loopback SMTP. Set `GITEA_SMOKE_BINARY` to a checksum-verified official binary matching the lock, then run `go test ./integration -v -count=1` from `tools/`. It captures mail locally without sending to real users. Without this variable, the test is skipped. The release workflow does not run the optional browser or real-Gitea checks automatically.
|
||||
|
||||
### Preparing a Release
|
||||
|
||||
Release tags must match the locked Gitea version. The current source refactor is unreleased; retain existing v28.0.0 and historical assets.
|
||||
|
||||
1. Complete automated checks, preview review and the matching-instance smoke test.
|
||||
2. Add reviewed notes at `.github/release-notes/vX.Y.Z.md` and update compatibility records with the verification results.
|
||||
3. Generate all themes and language bundles with `go run . preview all` from `tools/`.
|
||||
4. Package the release and verify its contents before publication.
|
||||
|
||||
For manual packaging, run from the repository root:
|
||||
|
||||
```bash
|
||||
python .github/scripts/package_release.py --version vX.Y.Z --output <new-output-directory>
|
||||
```
|
||||
|
||||
Replace the version and output placeholder with the reviewed tag and a new directory. The script checks source and generated hashes, includes all language bundles, excludes stale theme builds, and refuses to overwrite existing archives.
|
||||
|
||||
The release workflow uses the same script to package templates, previews, documentation, the upstream license and provenance. It also updates managed release labels. Confirm Actions support and write permissions on the configured Gitea host before relying on automated publication. See [version tracking](COMPATIBILITY.md#version-tracking) for the managed Markdown blocks.
|
||||
|
||||
### Gitea Workflow Configuration
|
||||
|
||||
Both workflows use the `linux-amd64-docker-small` runner label. The job image must support the Node.js actions used for checkout and toolchain setup, as well as Git and a POSIX shell. Go and Python are installed by the setup steps.
|
||||
|
||||
PR creation and release uploads use `.github/scripts/gitea_actions.py` and the instance's `/api/v1` API. The workflows pass the instance URL, repository and built-in `GITEA_TOKEN`; repository settings must allow the requested code, release and pull-request writes. The optional `UPSTREAM_GITHUB_TOKEN` secret is used only to query official releases on GitHub. Without it, those queries use GitHub's unauthenticated API limit.
|
||||
|
||||
The tracker reuses an existing branch when its content matches and reuses an open PR for that branch. A differing branch requires manual review; the workflow does not force-push. Release publication refuses any existing release, including a draft. New releases remain drafts until both archives upload successfully. If an upload fails, inspect the draft before retrying; published releases and assets must remain unchanged.
|
||||
|
||||
## Reporting Problems
|
||||
|
||||
Include the Gitea version, template release or source commit, theme, mail type, language and steps to reproduce. For source-build problems, include the snapshot tag/commit and output from `go run . upstream verify` or `go run . preview all`. For display problems, include the mail client and a screenshot with personal data removed.
|
||||
|
||||
## Commit Conventions
|
||||
|
||||
Any readable commit message in semantic format is welcome. Such as:
|
||||
| Prefix | Scope |
|
||||
|---|---|
|
||||
| `style(<name>):` | Theme presentation |
|
||||
| `preview:` | Browser preview |
|
||||
| `tools:` | CLI, build and snapshot tools |
|
||||
| `docs:` | Documentation and translations |
|
||||
| `fix:` | Bug fixes |
|
||||
| `project:` | Repository configuration and project structure |
|
||||
| `refactor:` | Code restructuring |
|
||||
| `chore:` | Maintenance |
|
||||
|
||||
- `style(horizon|terminal|ember|bloom|heritage|neon|mono|terra|ink|aurora):` — template changes
|
||||
- `preview(*):` — preview tooling changes
|
||||
- `tools(*):` — Go build script changes
|
||||
- `docs(*):` — documentation and translations
|
||||
- `fix(*):` — bug fixes
|
||||
- `project(*):` — README, LICENSE, AGENTS.md, meta
|
||||
## Translations and License
|
||||
|
||||
---
|
||||
|
||||
## Translations
|
||||
|
||||
- English
|
||||
- [简体中文](docs/CONTRIBUTING.zh-CN.md)
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
By contributing, you agree that your contributions will be licensed under the MIT License.
|
||||
The [Simplified Chinese guide](docs/CONTRIBUTING.zh-CN.md) covers the same workflow. Contributions are licensed under MIT. Retain Gitea copyright and the upstream license when distributing derived templates; see [third-party notices](THIRD_PARTY_NOTICES.md).
|
||||
@@ -1,204 +1,211 @@
|
||||
# Gitea Mail Templates
|
||||
|
||||
A curated collection of professionally designed, audience-driven email templates for self-hosted [Gitea](https://about.gitea.com) instances.
|
||||
|
||||
[](COMPATIBILITY.md) <!-- TRACKER:BADGE -->
|
||||
|
||||
> **Release v1.27.3 — verified with Gitea 1.27.3 — 110 template files, 10 visual styles, 11 email types each.**
|
||||
|
||||
# Gitea Mail Template
|
||||
---
|
||||
|
||||
## Philosophy
|
||||
[简体中文](docs/README.zh-CN.md)
|
||||
|
||||
Most self-hosted Gitea instances use the default plain email templates. This project provides **ready-to-deploy, visually polished alternatives** — each designed for a specific community or audience, so you can pick the one that feels right for your users.
|
||||
Gitea Mail Template offers a range of email template styles for self-hosted [Gitea](https://about.gitea.com) instances.
|
||||
|
||||
The templates on `main` can replace Gitea's built-in mail templates without patches, plugins, or forks. Check the [compatibility matrix](COMPATIBILITY.md) before using a published archive; older releases may have version-specific limitations.
|
||||
> Latest release: [v28.0.0](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/latest)
|
||||
|
||||
---
|
||||
The repository includes several themes for Gitea emails, suitable for most deployment scenarios and use cases. Before installing, consult the [compatibility matrix](COMPATIBILITY.md#compatibility-matrix) for your Gitea version and choose the corresponding release archive.
|
||||
|
||||
## Style Gallery
|
||||
|
||||
| Preview | Style | Audience | Character |
|
||||
|---|---|---|---|
|
||||
|  | **Horizon** | Enterprise / Corporate | Blue accent, slate typography, centered cards |
|
||||
|  | **Terminal** | Developers / Tech | Dark mode, monospace, green CLI accents |
|
||||
|  | **Ember** | Community / Open Source | Warm amber, rounded, humanist, inclusive |
|
||||
|  | **Bloom** | Creative / Startup | Frosted glass, soft blue light, iridescent accents |
|
||||
|  | **Heritage** | Education / Research | Navy and gold, serif, classic, authoritative |
|
||||
|  | **Neon** | Gaming / Web3 / Creative Tech | Cyberpunk neon glow, hot pink & cyan, synthwave energy |
|
||||
|  | **Mono** | Design Studios / Editorial | Swiss brutalist, black & white, red accent, zero radius |
|
||||
|  | **Terra** | Sustainability / Wellness | Warm earth tones, organic textures, humanist serif |
|
||||
|  | **Ink** | Publishing / News / Literature | Editorial print, navy & gold, newspaper layout, drop caps |
|
||||
|  | **Aurora** | Premium SaaS / Mindfulness | Ethereal light gradients, deep purple & teal, atmospheric glow |
|
||||
The gallery shows how the email templates render in the preview tool.
|
||||
|
||||
> Images are screenshots from the [live preview](preview/index.html). See [docs/images/README.md](docs/images/README.md) for capture instructions.
|
||||
| Preview | Theme | Style Features |
|
||||
|---|---|---|
|
||||
|  | **Aurora** | Dark purple background, teal accents and a soft glow |
|
||||
|  | **Bloom** | Light blue gradients, rounded cards and buttons |
|
||||
|  | **Ember** | Warm orange tones, serif headings and rounded buttons |
|
||||
|  | **Heritage** | Navy and gold, double borders and serif fonts |
|
||||
|  | **Horizon** | Blue accents, gray text and a centered white card |
|
||||
|  | **Ink** | Newspaper layout, a sidebar, serif fonts and drop caps |
|
||||
|  | **Mono** | Black and white, red accents and square borders |
|
||||
|  | **Neon** | Dark background, pink and cyan accents and glow effects |
|
||||
|  | **Terminal** | Dark background, monospace fonts and green accents |
|
||||
|  | **Terra** | Earth tones, terracotta buttons and serif fonts |
|
||||
|
||||
[**Live preview gallery**](preview/index.html) — open in a browser for an interactive style switcher with desktop/mobile viewports and view mode (Modern, Source).
|
||||
|
||||
---
|
||||
Screenshots show only the current default build. Use the [local preview](#preview) to view other email types and languages. See the [screenshot guide](docs/images/README.md) when updating screenshots.
|
||||
|
||||
## Installation
|
||||
|
||||
### Quick Start
|
||||
### Choose an Installation Source
|
||||
|
||||
Choose a style, then copy the `mail/` directory into your Gitea custom templates path:
|
||||
Run `gitea --version` to confirm the version of your deployed Gitea instance, then choose a template version from the [compatibility matrix](COMPATIBILITY.md#compatibility-matrix).
|
||||
|
||||
> [!WARNING]
|
||||
> We do not recommend building directly from the source repository, as these versions have not yet been fully tested for release readiness. Outdated or missing template parameters that have not been identified may prevent your Gitea instance from starting correctly.
|
||||
|
||||
> [!TIP]
|
||||
> If you still need to build the email templates from source, follow the steps below.
|
||||
|
||||
| Source | Email Template Directory | Preparation |
|
||||
|---|---|---|
|
||||
| Release archive | `themes/<name>/mail/` | Download and extract the recommended release archive |
|
||||
| Source | `build/themes/<name>/mail/` | Build with Go 1.24 or later |
|
||||
|
||||
To build from source, run the following from the repository root:
|
||||
|
||||
```bash
|
||||
# Locate your Gitea custom directory
|
||||
# (set by GITEA_CUSTOM; defaults shown below)
|
||||
|
||||
# Copy templates (example: Horizon style)
|
||||
cp -r themes/horizon/mail/* /var/lib/gitea/custom/templates/mail/
|
||||
|
||||
# Restart Gitea
|
||||
systemctl restart gitea
|
||||
cd tools
|
||||
go run . build all
|
||||
cd ..
|
||||
```
|
||||
|
||||
### Custom Directory Location
|
||||
Building usable templates requires resources from the official Gitea repository to ensure consistent source inputs. The command automatically downloads the official files pinned by `gitea.lock.json`. Subsequent builds use the cached resources without downloading them again. For missing files or a damaged cache, see [official snapshot updates](CONTRIBUTING.md#official-snapshot-updates).
|
||||
|
||||
| Platform | Default Path |
|
||||
### Install a Theme
|
||||
|
||||
Copy the contents of the selected theme's `mail/` directory into `<GITEA_CUSTOM>/templates/mail/`, then restart Gitea to apply the theme. Before copying, confirm the custom directory your instance actually uses. Common deployment paths include:
|
||||
|
||||
| Deployment | Example Custom Directory |
|
||||
|---|---|
|
||||
| Linux (binary) | `/var/lib/gitea/custom` |
|
||||
| Linux (Docker) | `/data/gitea` |
|
||||
| Linux binary | `/var/lib/gitea/custom` |
|
||||
| Docker | `/data/gitea` |
|
||||
| Windows | `C:\gitea\custom` |
|
||||
|
||||
### Switching Styles
|
||||
|
||||
Overwrite the files with a different style. All templates share the exact same variable structure — no configuration changes needed.
|
||||
For example, on a Linux host where systemd manages Gitea, run the following from the extracted release archive directory:
|
||||
|
||||
```bash
|
||||
cp -r themes/terminal/mail/* /var/lib/gitea/custom/templates/mail/
|
||||
systemctl stop gitea
|
||||
mkdir -p /var/lib/gitea/custom/templates/mail
|
||||
cp -r themes/horizon/mail/. /var/lib/gitea/custom/templates/mail/
|
||||
systemctl restart gitea
|
||||
```
|
||||
|
||||
### Confirming It Works
|
||||
> [!TIP]
|
||||
> For templates built from source, change the copy source to `build/themes/horizon/mail/.`. For Docker and Windows deployments, use the appropriate container or service controls to stop and restart Gitea.
|
||||
|
||||
The admin test email does not use custom mail templates. To verify your templates
|
||||
are active, trigger a real email notification. The quickest way is the password
|
||||
reset flow: log out, click **"Forgot password"** on the login page, and check the
|
||||
reset email — it will render with your custom styles.
|
||||
### Switch Themes
|
||||
|
||||
---
|
||||
Back up or remove the files installed by the previous theme, choose a new email theme, and repeat the installation steps above.
|
||||
|
||||
### Confirm the Theme Is Applied
|
||||
|
||||
Use a test account to trigger an email notification, such as a password reset, and check its appearance and links. The administration panel's test-email button does not use custom email templates, so it cannot confirm whether the theme has been applied.
|
||||
|
||||
## Preview
|
||||
|
||||
Two modes are available — a zero-dependency static preview for quick checks, and a live-reload dev server for design work.
|
||||
The preview tool included in the repository lets you view how emails render across themes, template types and languages before deployment.
|
||||
|
||||
### Static Preview
|
||||
|
||||
Generate the preview data once (Go only), then open in a browser:
|
||||
First, generate the preview data in the source repository:
|
||||
|
||||
```bash
|
||||
cd tools && go run . preview all
|
||||
open preview/index.html # no server needed
|
||||
cd tools
|
||||
go run . preview all
|
||||
cd ..
|
||||
```
|
||||
|
||||
### Dev Server (Live Reload)
|
||||
Open [preview/index.html](preview/index.html) in a browser. No server is required.
|
||||
|
||||
Start a pure Go development server that watches for `.tmpl` changes, auto-rebuilds, and pushes live updates to the browser via SSE:
|
||||
### Development with Live Reload
|
||||
|
||||
```bash
|
||||
cd tools && go run . dev
|
||||
open http://localhost:3456
|
||||
cd tools
|
||||
go run . dev
|
||||
# Press Ctrl+C in the terminal to stop the server.
|
||||
cd ..
|
||||
```
|
||||
|
||||
| Capability | Static | Dev |
|
||||
|-----------|--------|-----|
|
||||
| Go template rendering | ✅ | ✅ |
|
||||
| Theme/template switching | ✅ | ✅ |
|
||||
| Live reload on save | — | ✅ |
|
||||
Open [http://127.0.0.1:3456](http://127.0.0.1:3456). The Go server watches theme files, the shared framework, the lock file and cache, and preview test data. When a file changes, it rebuilds the email templates and refreshes the page through server-sent events (SSE) to provide live reload.
|
||||
|
||||
### Features
|
||||
### Controls and Keyboard Shortcuts
|
||||
|
||||
- Theme switcher — browse all 10 visual styles
|
||||
- Template switcher — all 11 email types
|
||||
- View mode — Modern (rendered preview), Source (raw HTML)
|
||||
- Viewport toggle — Desktop 1386×780 / Mobile 390×780
|
||||
- Parameter panel — mock data per email type
|
||||
- Keyboard shortcuts — `←→` tab between Theme/Template/View, `↑↓` select within, `d`/`m` viewport
|
||||
| Control | Options or Shortcuts |
|
||||
|---|---|
|
||||
| Theme, template, language and view | `←` / `→` moves between selectors; `↑` / `↓` selects an option |
|
||||
| View | **Modern** displays the rendered result; **Source** displays the generated HTML text |
|
||||
| Viewport | **Desktop** (1386 × 780), **Mobile** (390 × 780); shortcuts `d` / `m` |
|
||||
| Information panel | `p` expands or collapses the panel |
|
||||
|
||||
---
|
||||
> [!WARNING]
|
||||
> The preview renders templates using example data. Browsers and email clients differ in their CSS support, so check the appearance in your target email clients before deployment.
|
||||
|
||||
## Directory Structure
|
||||
## Compatibility
|
||||
|
||||
- **Latest tested:** Gitea 28.0.0
|
||||
- **Latest release:** [v28.0.0](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/latest)
|
||||
- **Upstream Gitea 28.1.0:** [PENDING]
|
||||
|
||||
## Project Directory Structure
|
||||
|
||||
The project is organized around a shared framework, theme styles and build tools. Source code and documentation are tracked in version control; downloaded official files, generated templates and preview data are not.
|
||||
|
||||
### Tracked in Version Control
|
||||
|
||||
```text
|
||||
.github/ # Workflows and maintenance scripts
|
||||
release-notes/ # Release notes for each version
|
||||
scripts/ # Documentation updates, packaging and Gitea release scripts
|
||||
workflows/ # Validation and publication
|
||||
docs/ # Chinese documentation, documentation index and gallery images
|
||||
framework/ # Shared email controls and layout presets
|
||||
layouts/ # Layout presets shared across email types
|
||||
mail/base/ # Header, action buttons, fallback links, sidebar and footer
|
||||
preview/index.html # Preview interface
|
||||
themes/<THEME_NAME>/ # Theme source files
|
||||
tools/ # Go CLI, build tools and tests
|
||||
builder/ # Official template adaptation and theme generation
|
||||
cli/ # Command definitions and argument handling
|
||||
config/ # Preview configuration loading and validation
|
||||
data/ # Email type descriptions and preview example data
|
||||
preview/ # Email rendering, locale adaptation and development server
|
||||
upstream/ # Official snapshot downloads, verification and translation key discovery
|
||||
gitea.lock.json # Official version tag, commit and file checksums
|
||||
README.md
|
||||
```
|
||||
gitea-mail-templates/
|
||||
├── themes/ # 10 visual styles, 11 .tmpl files each
|
||||
│ ├── ... # 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/)
|
||||
├── tools/ # Modular CLI tooling
|
||||
│ ├── 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
|
||||
│ ├── preview/ # Template rendering engine
|
||||
│ └── go.mod
|
||||
├── docs/ # Bilingual documentation (English + Simplified Chinese)
|
||||
├── AGENTS.md # AI agent guidance
|
||||
├── CONTRIBUTING.md
|
||||
├── LICENSE
|
||||
├── README.md
|
||||
└── .gitignore
|
||||
|
||||
Official templates provide notification content, conditions, subject lines and links. The shared framework handles email presentation, while themes use metadata and CSS to select layouts and define styles. Theme directories do not contain official email source files or translations.
|
||||
|
||||
### Not Tracked in Version Control
|
||||
|
||||
The following files are generated by the build and preview commands or downloaded according to the lock file. They do not need to be maintained manually or committed:
|
||||
|
||||
```text
|
||||
build/themes/<THEME_NAME>/ # Theme build output
|
||||
mail/ # Email templates ready to install in Gitea
|
||||
build.json # Build provenance and generated file checksums
|
||||
build/upstream/ # Official templates, locales, assets and licenses pinned by the lock file
|
||||
dist/ # Default output directory for release archives
|
||||
preview/rendered.js # Preview manifest
|
||||
preview/rendered/<LOCALE>.js # Preview data for each locale
|
||||
```
|
||||
|
||||
`build all` generates installable templates for all themes. `preview all` generates both templates and preview data for each locale. On first use, the tools prepare any missing official file cache according to `gitea.lock.json`; subsequent uses verify the cache before proceeding.
|
||||
|
||||
### Template Types
|
||||
|
||||
| File | Email Trigger |
|
||||
Gitea currently includes the following 11 email templates [1](#notes):
|
||||
|
||||
| File | Notification Type |
|
||||
|---|---|
|
||||
| `mail/user/auth/activate.tmpl` | Account activation |
|
||||
| `mail/user/auth/activate_email.tmpl` | Email address verification |
|
||||
| `mail/user/auth/register_notify.tmpl` | New registration notification |
|
||||
| `mail/user/auth/register_notify.tmpl` | Registration notification |
|
||||
| `mail/user/auth/reset_passwd.tmpl` | Password reset |
|
||||
| `mail/org/team_invite.tmpl` | Team invitation |
|
||||
| `mail/repo/collaborator.tmpl` | Repository collaborator added |
|
||||
| `mail/repo/transfer.tmpl` | Repository ownership transfer |
|
||||
| `mail/repo/release.tmpl` | New release published |
|
||||
| `mail/repo/actions/workflow_run.tmpl` | Actions workflow run |
|
||||
| `mail/repo/issue/assigned.tmpl` | Issue / Pull Request assigned |
|
||||
| `mail/repo/issue/default.tmpl` | Issue / Pull Request updates |
|
||||
|
||||
---
|
||||
|
||||
## Compatibility
|
||||
|
||||
- **Gitea 1.25+** — matches the refactored mail template directory structure (v1.25)
|
||||
- **Latest tested:** Gitea 1.27.3 <!-- TRACKER:LATEST-TESTED -->
|
||||
- **Latest release:** [v1.27.3](https://github.com/KenanZhu/GiteaMailTemplates/releases/tag/v1.27.3) fixes push notifications in Bloom, Ember, and Heritage on Gitea 1.27.1+. The older v1.27.2 archive is affected (see [COMPATIBILITY.md](COMPATIBILITY.md))
|
||||
- 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
|
||||
|
||||
---
|
||||
|
||||
## Design Principles
|
||||
|
||||
1. **Responsive** — Max-width 600px cards; works in all email clients
|
||||
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
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
- [English](README.md)
|
||||
- [简体中文](docs/README.zh-CN.md)
|
||||
|
||||
---
|
||||
| `mail/repo/issue/assigned.tmpl` | Issue or pull request assignment |
|
||||
| `mail/repo/issue/default.tmpl` | Issue or pull request activity |
|
||||
|
||||
## Contributing
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. A Simplified Chinese translation is available in [docs/](docs/).
|
||||
Contributions to themes, tools, documentation and translations are welcome. The [contributor guide](CONTRIBUTING.md) covers local environment setup, design guidelines, required checks and the release process.
|
||||
|
||||
## License
|
||||
|
||||
MIT — see [LICENSE](LICENSE). Free to use, modify, and distribute in any Gitea deployment.
|
||||
This project is licensed under [MIT](LICENSE). Generated release archives retain Gitea's license and snapshot provenance.
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<sub>Not affiliated with the Gitea project. Gitea is a community-managed lightweight code hosting solution written in Go.</sub>
|
||||
</p>
|
||||
### Notes
|
||||
|
||||
[1](#notes): [Mail templates | Gitea Documentation](https://docs.gitea.com/administration/mail-templates/)
|
||||
|
||||
This project is not affiliated with Gitea.
|
||||
@@ -1,80 +1,219 @@
|
||||
# 贡献指南 — Gitea 邮件模板
|
||||
# 贡献指南
|
||||
|
||||
## 参与方式
|
||||
[English](../CONTRIBUTING.md) · [项目概览](README.zh-CN.md) · [兼容性](../COMPATIBILITY.md)
|
||||
|
||||
### 添加新风格
|
||||
欢迎改进主题、工具、测试、文档和翻译。本指南介绍当前源码架构的开发流程;安装和版本选择请参阅 [README](README.zh-CN.md#安装)。
|
||||
|
||||
1. 使用工具脚手架:`cd tools && go run . create <风格名称>` — 这会创建完整的目录结构和全部 11 种邮件类型的占位 `.tmpl` 文件
|
||||
2. 在 `themes/<风格名称>/` 中编写每个 `.tmpl` 文件,应用独特的视觉设计
|
||||
3. 重新生成预览:`cd tools && go run . preview all`(构建脚本会自动发现 `themes/` 下的所有主题目录并动态生成主题选择器)
|
||||
4. 提交包含截图渲染效果的 PR(单张图片 ≤ 50 KiB,建议 10–20 KiB)
|
||||
## 本地开发
|
||||
|
||||
### 风格指南
|
||||
|
||||
- 每个风格必须包含全部 **11 种模板类型**
|
||||
- 只使用 Gitea 内置模板函数
|
||||
- 翻译键必须来自 Gitea 官方语言文件(`mail.*` 命名空间)
|
||||
- **不要在以下模板中使用 `.DisplayName`**:collaborator、transfer、release、workflow_run、assigned、default
|
||||
- 面向 600px 宽度的邮件客户端设计
|
||||
- 尽可能在 Gmail、Outlook、Apple Mail 中测试
|
||||
|
||||
### 报告 Bug
|
||||
|
||||
1. 检查引用的 Go 模板变量是否存在
|
||||
2. 验证翻译键是否与 Gitea 语言文件匹配
|
||||
3. 确认 `.DisplayName` 未在不支持的模板中使用
|
||||
4. 重新生成预览:`cd tools && go run . preview all`
|
||||
5. 提交 issue,注明风格名称、邮件类型及错误描述
|
||||
|
||||
### 改进文档
|
||||
|
||||
文档改进、预览截图、安装指南和翻译始终欢迎。
|
||||
|
||||
---
|
||||
|
||||
## 开发环境
|
||||
|
||||
- **Go 1.21+** 用于模板渲染和 CLI 工具
|
||||
|
||||
### 本地预览(静态)
|
||||
|
||||
1. 先生成预览数据:`cd tools && go run . preview all`
|
||||
2. 在浏览器中打开 `preview/index.html` — 无需服务器
|
||||
|
||||
|
||||
### 开发服务器(实时重载)
|
||||
使用 **Go 1.24 或更高版本**。从仓库根目录开始,准备依赖和锁定的官方文件,再运行检查并生成预览:
|
||||
|
||||
```bash
|
||||
cd tools && go run . dev
|
||||
# → http://localhost:3456
|
||||
cd tools
|
||||
go mod download
|
||||
go run . upstream prepare
|
||||
go test ./...
|
||||
go run . preview all
|
||||
```
|
||||
|
||||
修改 `.tmpl` 文件后自动重建并推送至浏览器。
|
||||
在浏览器中打开 `preview/index.html`,或在 `tools/` 中运行 `go run . dev`,访问 [http://127.0.0.1:3456](http://127.0.0.1:3456) 使用实时预览。开发服务器监听主题、框架、锁文件与缓存、预览测试数据的变化。
|
||||
|
||||
CLI 使用 urfave/cli 和 x/net HTML 解析器。文档与打包检查使用 Python 3.11 或更高版本;只有浏览器检查需要 Node.js 和 `tools/qa` 中的依赖。
|
||||
|
||||
### 集成测试
|
||||
### 常用命令
|
||||
|
||||
将模板部署到 Gitea 实例后,请通过真实的邮件通知进行验证。管理后台的测试邮件
|
||||
(**Site Administration > Configuration > Mailer > Send Test Email**)不会使用
|
||||
自定义邮件模板——它走的是内置代码路径。
|
||||
以下命令均在 `tools/` 中执行。带有位置参数时,将选项放在位置参数之前。
|
||||
|
||||
最可靠的验证方式是触发一次真实的邮件通知,推荐使用密码重置流程:
|
||||
| 命令 | 用途 |
|
||||
|---|---|
|
||||
| `go run . list` | 列出可用主题 |
|
||||
| `go run . create my-theme` | 创建主题元数据和 CSS |
|
||||
| `go run . build my-theme` | 生成指定主题的安装模板 |
|
||||
| `go run . build all` | 生成全部主题 |
|
||||
| `go run . preview all` | 构建全部主题并渲染所有官方语言 |
|
||||
| `go run . dev` | 在端口 3456 启动开发服务器 |
|
||||
| `go run . dev --port 3457` | 指定其他本地端口 |
|
||||
| `go run . delete my-theme` | 删除指定主题的源码目录 |
|
||||
|
||||
1. 退出登录,点击登录页的**"忘记密码"**
|
||||
2. 输入账户邮箱并提交
|
||||
3. 查看密码重置邮件——它将使用你的自定义邮件模板渲染
|
||||
`build` 将模板写入 `build/themes/<名称>/mail/`,并在 `build.json` 中记录文件和源码哈希。`preview` 同时完成构建,随后写入 `preview/rendered.js` 和 `preview/rendered/<语言>.js`。这些生成文件和下载的官方文件均被 Git 忽略,无需随贡献提交。
|
||||
|
||||
---
|
||||
## 添加主题
|
||||
|
||||
1. 在 `tools/` 中运行 `go run . create my-theme`。
|
||||
2. 编辑 `themes/my-theme/theme.json` 和 `theme.css`。
|
||||
3. 在 `tools/` 中运行 `go test ./...` 和 `go run . preview all`。
|
||||
4. 检查英文、简体中文及至少另一种官方语言的桌面与移动端布局,在拉取请求中附上截图。
|
||||
|
||||
主题名称以小写字母开头,可包含小写字母、数字和连字符。主题目录只包含 `theme.json` 和 `theme.css`。
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-theme",
|
||||
"description": "主题的简短说明",
|
||||
"mode": "framed",
|
||||
"layout": "standard"
|
||||
}
|
||||
```
|
||||
|
||||
| 模式 | 生成的展示结构 |
|
||||
|---|---|
|
||||
| `framed` | 使用共享控件和框架布局,由主题 CSS 定义样式。新主题默认使用此模式及 `standard` 布局。 |
|
||||
| `shared` | 在官方页头片段中加入 CSS,并保留官方页脚片段;不增加框架控件或各类邮件的覆盖模板。 |
|
||||
|
||||
### 共享框架
|
||||
|
||||
页头、操作按钮、备用链接、侧栏和页脚位于 `framework/mail/base/`。布局预设位于 `framework/layouts/`,`framed` 主题可通过可选的 `layout` 字段选择预设。配色、字体和间距由主题 CSS 定义。
|
||||
|
||||
`tools/builder/framework.go` 将官方操作值和翻译键适配到共享控件。通知条件、主题行、附件和上下文链接由官方模板定义。控件或布局结构的修改应放在框架中,使各主题使用同一套适配逻辑。
|
||||
|
||||
布局片段组合后必须形成标签配对完整的展示结构。`__HEADER__` 插入共享页头,`__MAIL_TYPE__` 展开为发现的邮件 ID,页脚片段中的 `__SIDEBAR__` 插入共享侧栏。部署模板的 Logo 使用 `{{AppUrl}}assets/img/favicon.png`;生成的预览数据嵌入下载的图标,供离线显示。
|
||||
|
||||
## 设计规范
|
||||
|
||||
- 使用兼容邮件客户端的 CSS。主题 CSS 不得加载外部资源、生成文字或隐藏官方内容。
|
||||
- 布局片段使用展示性标记。校验器接受 `table`、`tbody`、`tr`、`td`、`div`、`span` 及支持的展示属性。
|
||||
- 修改已有主题时,保留其配色、字体、边框、页头、按钮和布局特征。
|
||||
- 检查桌面和 390px 移动端预览,覆盖长文本和长 URL。条件允许时测试 Gmail、Outlook 和 Apple Mail;浏览器预览不能模拟邮件客户端。
|
||||
- 画廊图片使用 PNG,单张不超过 50 KiB,建议为 10–20 KiB。按[截图指南](images/README.md)保持截图一致。
|
||||
|
||||
## 更新官方快照
|
||||
|
||||
`gitea.lock.json` 记录稳定的 Gitea 28+ 标签、对应提交和每个文件的 SHA-256。官方邮件模板、语言文件、图标、许可证和已评审的适配参考源码下载到 `build/upstream/`。仓库仅提交生成的根锁文件,不维护官方文件副本。
|
||||
|
||||
### 命令参考
|
||||
|
||||
在 `tools/` 中执行。每个上游子命令均支持 `--root <仓库根目录>`,默认值为相对当前工作目录的 `..`。参数放在子命令之后,包含空格的路径需要加引号:
|
||||
|
||||
```powershell
|
||||
go run . upstream verify --root "C:\Projects\GiteaMailTemplates"
|
||||
```
|
||||
|
||||
| 命令 | 行为 |
|
||||
|---|---|
|
||||
| `go run . upstream prepare` | 读取根锁文件。缓存不存在时下载并校验对应文件;已有缓存则离线校验。不修改根锁文件。 |
|
||||
| `go run . upstream verify` | 对照根锁文件离线、只读检查已有缓存。输出 `[PASS]`,并以 `[FALLBACK]` 列出非英文语言缺少的键。 |
|
||||
| `go run . upstream sync --tag vX.Y.Z` | 通过 GitHub API 解析明确指定的稳定 Gitea 28+ 标签,下载并校验文件,然后替换缓存并生成根锁文件。 |
|
||||
|
||||
`sync` 使用 `api.github.com`,文件下载使用 `raw.githubusercontent.com`。这些命令不接受本地 Gitea 克隆、认证令牌或隐式的 `latest` 版本。网络错误和 API 限流会中止操作;Go 工具链或模块可能仍需单独联网下载。
|
||||
|
||||
### 已有克隆与离线使用
|
||||
|
||||
`build`、`preview` 和 `dev` 会自动准备输入,需要已提交的根锁文件,并在使用缓存前完成校验。缓存、Go 工具链和依赖齐备后,可以离线构建。
|
||||
|
||||
需要单独检查缓存时,使用 `upstream verify`。它校验官方文件哈希、已评审的适配参考源码,以及官方模板引用的英文翻译键。构建和渲染还会检查框架翻译键及主要操作链接的锚点,因此缓存校验只是兼容性测试的一部分。
|
||||
|
||||
### 锁文件缺失与显式更新版本
|
||||
|
||||
已有克隆缺少 `gitea.lock.json` 时,应恢复受版本控制的文件。`build/upstream/lock.json` 中的副本不能替代根锁文件。需要有意初始化锁文件时,执行:
|
||||
|
||||
```bash
|
||||
cd tools
|
||||
go run . upstream sync --tag v28.0.0
|
||||
go run . upstream verify
|
||||
go test ./...
|
||||
go run . preview all
|
||||
```
|
||||
|
||||
评审其他版本时,明确指定对应标签。检查锁文件差异,按需更新框架适配逻辑和测试数据,完成检查及同版本实例冒烟测试后,再更新兼容性记录。`sync` 只修改缓存和锁文件,文档更新与版本发布需单独完成。仅缺少缓存时使用 `prepare`。
|
||||
|
||||
已评审的翻译或邮件渲染参考源码发生变化时,须先评审适配逻辑,再更新参考哈希。官方英文必须包含所有引用的翻译键;其他语言缺少的翻译回退为英文。新增邮件类型需要补充框架适配和 `tools/data/templates_config.json` 中的测试数据,该文件提供预览元数据和示例上下文。JSON 测试数据中的整数应保持整数形式,以便 Go 正确格式化。
|
||||
|
||||
缓存替换前发生网络或校验错误时,原有文件保持不变。缓存替换和根锁文件写入是两个操作;文件系统错误中断操作后,重试前应检查二者是否一致。
|
||||
|
||||
### 缓存恢复
|
||||
|
||||
`prepare` 会报告损坏、不完整或与锁文件不匹配的缓存,不会自动修复。`sync` 同样拒绝替换非空的无效快照。检查错误后,将生成的缓存移至备份位置,再按已评审的根锁文件重新准备。
|
||||
|
||||
例如,在**仓库根目录的 PowerShell** 中,使用尚不存在的备份名称:
|
||||
|
||||
```powershell
|
||||
Move-Item -LiteralPath .\build\upstream -Destination .\build\upstream.backup
|
||||
Set-Location tools
|
||||
go run . upstream prepare
|
||||
go run . upstream verify
|
||||
```
|
||||
|
||||
保留 `build/` 中的其他内容,不直接编辑缓存中的官方文件。需要回退快照时,恢复此前已评审的根锁文件,再按同样方式重建缓存。替换输入前先停止 `dev`,避免多个进程同时写入同一缓存。
|
||||
|
||||
### 已知上游格式问题
|
||||
|
||||
Gitea v28.0.0 的波兰语 `mail.team_invite.text_1` 存在占位符缺陷。预览保留官方输出并报告 `[UPSTREAM-WARN]`。该例外仅匹配已评审的完整原文,其他格式错误会使渲染失败。发现上游问题时应报告问题,不修改缓存中的语言文件。
|
||||
|
||||
## 验证与发布
|
||||
|
||||
### 拉取请求检查
|
||||
|
||||
在 `tools/` 中运行 `go test ./...` 和 `go run . preview all`。测试覆盖生成结果的确定性、操作锚点变化、翻译键,以及全部主题和语言的通知主题行、正文与链接。测试数据包含推送、评审、回复、工作流和附件分支;共享控件与品牌展示作为附加内容单独处理。
|
||||
|
||||
修改文档、追踪脚本或打包流程时,在仓库根目录执行:
|
||||
|
||||
```bash
|
||||
python -B -m unittest discover -s .github/scripts -p 'test_*.py'
|
||||
```
|
||||
|
||||
调整通用说明时,同步更新英文和简体中文指南。拉取请求应说明改动内容及相关检查结果,涉及视觉变化时附上截图。
|
||||
|
||||
### 浏览器与 Gitea 实例检查
|
||||
|
||||
生成预览数据后,可运行浏览器检查:
|
||||
|
||||
```bash
|
||||
cd tools/qa
|
||||
npm install
|
||||
npx playwright install chromium
|
||||
npm test
|
||||
```
|
||||
|
||||
如需使用已安装的 Chrome 或 Edge,设置 `BROWSER_EXECUTABLE_PATH`,无需再安装 Chromium。设置 `PREVIEW_DEV_URL` 可同时检查运行中的 HTTP 预览。`npm test -- --update-gallery` 会一并更新画廊截图。
|
||||
|
||||
发布前,在与锁定版本一致的隔离 Gitea 实例中加载生成的模板,捕获真实的密码重置或通知邮件。管理后台的测试邮件按钮不使用自定义模板。
|
||||
|
||||
可选的 `tools/integration` 测试使用临时 SQLite 数据、用户、Git/SSH 路径和回环 SMTP。将 `GITEA_SMOKE_BINARY` 设为已校验官方校验值、版本与锁文件一致的 Gitea 可执行文件,再在 `tools/` 中运行 `go test ./integration -v -count=1`。邮件仅在本地捕获,不发送给真实用户。未设置该变量时会跳过此测试。发布工作流不会自动运行可选的浏览器和真实 Gitea 实例检查。
|
||||
|
||||
### 准备发行版
|
||||
|
||||
发行标签必须与锁定的 Gitea 版本一致。当前源码重构尚未发布,已有 v28.0.0 和历史版本的附件应保留。
|
||||
|
||||
1. 完成自动化检查、预览检查和同版本实例冒烟测试。
|
||||
2. 在 `.github/release-notes/vX.Y.Z.md` 添加已评审的发行说明,并按验证结果更新兼容性记录。
|
||||
3. 在 `tools/` 中运行 `go run . preview all`,生成全部主题和语言数据。
|
||||
4. 打包并检查发行包内容后再发布。
|
||||
|
||||
手动打包时,在仓库根目录执行:
|
||||
|
||||
```bash
|
||||
python .github/scripts/package_release.py --version vX.Y.Z --output <new-output-directory>
|
||||
```
|
||||
|
||||
将版本和输出目录占位符替换为已评审的标签及新的目录。脚本会检查源码与产物哈希,包含所有语言数据包,排除残留的旧主题构建,并拒绝覆盖已有压缩包。
|
||||
|
||||
发布工作流使用同一脚本打包模板、预览、文档、官方许可证和来源信息,并更新受管理的版本标记。依赖自动发布前,请确认所用 Gitea 主机的 Actions 支持和写入权限。Markdown 管理区块的说明见[版本追踪](../COMPATIBILITY.md#version-tracking)。
|
||||
|
||||
### Gitea 工作流配置
|
||||
|
||||
两个工作流均使用 `linux-amd64-docker-small` Runner 标签。任务镜像需要支持检出和工具链安装步骤使用的 Node.js Action,并提供 Git 和 POSIX Shell。Go 与 Python 由安装步骤准备。
|
||||
|
||||
PR 创建和发行附件上传通过 `.github/scripts/gitea_actions.py` 调用实例的 `/api/v1` API。工作流传入实例地址、仓库名称和内置 `GITEA_TOKEN`,仓库设置需要允许所请求的代码、发行版及拉取请求写入权限。可选的 `UPSTREAM_GITHUB_TOKEN` Secret 仅用于查询 GitHub 上的官方发行版;未配置时使用 GitHub 的匿名 API 限额。
|
||||
|
||||
追踪工作流会复用内容相同的已有分支及对应的未关闭 PR。分支内容不同时需要人工检查,工作流不会强制推送。发布流程拒绝修改任何已有发行版,包括草稿;新版本在两个压缩包上传成功后才从草稿转为发布状态。上传失败时,重试前先检查草稿;已发布版本及附件应保持不变。
|
||||
|
||||
## 报告问题
|
||||
|
||||
请提供 Gitea 版本、模板发行版或源码提交、主题、邮件类型、语言和复现步骤。源码构建问题还应包含快照标签与提交,以及 `go run . upstream verify` 或 `go run . preview all` 的输出。显示问题请注明邮件客户端,并附上移除个人信息后的截图。
|
||||
|
||||
## 提交规范
|
||||
|
||||
- `style(<名称>):` — 特定风格的模板变更
|
||||
- `preview:` — 预览工具变更
|
||||
- `tools:` — Go 构建脚本变更
|
||||
- `docs:` — 文档和翻译
|
||||
- `fix:` — Bug 修复
|
||||
- `project:` — README、LICENSE、元文件
|
||||
| 前缀 | 范围 |
|
||||
|---|---|
|
||||
| `style(<名称>):` | 主题展示样式 |
|
||||
| `preview:` | 浏览器预览 |
|
||||
| `tools:` | CLI、构建与快照工具 |
|
||||
| `docs:` | 文档和翻译 |
|
||||
| `fix:` | 问题修复 |
|
||||
| `project:` | 仓库配置和项目结构 |
|
||||
| `refactor:` | 代码重构 |
|
||||
| `chore:` | 日常维护 |
|
||||
|
||||
## 许可协议
|
||||
## 翻译与许可证
|
||||
|
||||
参与贡献即表示您同意将您的贡献以 MIT 许可证授权。
|
||||
[英文指南](../CONTRIBUTING.md)介绍相同的开发流程。贡献采用 MIT 许可证;分发派生模板时,请保留 Gitea 版权和官方许可证,详见[第三方声明](../THIRD_PARTY_NOTICES.md)。
|
||||
@@ -1,88 +1,209 @@
|
||||
# Gitea 邮件模板
|
||||
|
||||
为自托管 [Gitea](https://about.gitea.com) 实例精心设计、面向不同受众的邮件模板集合。
|
||||
|
||||
> **发布版 v1.27.3 — 已通过 Gitea 1.27.3 兼容性检查 — 110 个模板文件、10 种视觉风格、每种 11 种邮件类型**
|
||||
|
||||
# Gitea Mail Template
|
||||
---
|
||||
|
||||
## 设计理念
|
||||
[English](../README.md)
|
||||
|
||||
大多数自托管 Gitea 实例使用默认的纯文本邮件模板。本项目提供了**开箱即用、视觉精美的替代方案**——每种方案都针对特定社区或受众设计,您可以选择最适合您用户的风格。
|
||||
Gitea Mail Template 为自托管的 [Gitea](https://about.gitea.com) 提供多种可选、适合的邮件模板风格。
|
||||
|
||||
`main` 分支中的模板可直接替换 Gitea 内置邮件模板,**无需补丁、插件或 fork**。使用已发布的压缩包前,请先查看[兼容性说明](../COMPATIBILITY.md),了解旧版本的限制。
|
||||
> 最新发布版:[v28.0.0](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/latest)
|
||||
|
||||
---
|
||||
仓库包含数种主题的 Gitea 邮件模板,适合大多数的部署场景与应用范围。安装前,请根据 Gitea 版本查阅[兼容矩阵](../COMPATIBILITY.md#compatibility-matrix),选择对应的发行包。
|
||||
|
||||
## 风格画廊
|
||||
|
||||
| 预览 | 风格 | 受众 | 特点 |
|
||||
|---|---|---|---|
|
||||
|  | **Horizon** | 企业/公司 | 蓝色强调色、石板灰排版、居中卡片 |
|
||||
|  | **Terminal** | 开发者/技术 | 暗色模式、等宽字体、绿色命令行风格 |
|
||||
|  | **Ember** | 社区/开源 | 暖琥珀色、圆角、人文主义、包容 |
|
||||
|  | **Bloom** | 创意/初创 | 磨砂玻璃、柔和蓝色光线、虹彩点缀 |
|
||||
|  | **Heritage** | 教育/研究 | 海军蓝与金色、衬线字体、经典、权威 |
|
||||
|  | **Neon** | 游戏/Web3/创意科技 | 赛博朋克霓虹、粉红与青色、合成波能量 |
|
||||
|  | **Mono** | 设计工作室/编辑 | 瑞士粗野主义、黑白红强调、零圆角 |
|
||||
|  | **Terra** | 可持续/健康 | 温暖大地色调、有机质感、人文衬线 |
|
||||
|  | **Ink** | 出版/新闻/文学 | 编辑印刷、深蓝与金色、报纸排版 |
|
||||
|  | **Aurora** | 高端SaaS/正念 | 空灵光效渐变、深紫与青绿、大气光晕 |
|
||||
这里展示邮件模板通过预览工具呈现的实际渲染风格和效果。
|
||||
|
||||
> 图片为 600px 宽截图,来自[在线预览](../preview/index.html)。截图方法参见 [images/README.md](images/README.md)。
|
||||
| 预览 | 主题 | 样式特点 |
|
||||
|---|---|---|
|
||||
|  | **Aurora** | 深紫色背景、青绿色强调色、柔和光晕 |
|
||||
|  | **Bloom** | 浅蓝色渐变、圆角卡片与按钮 |
|
||||
|  | **Ember** | 暖橙色调、衬线标题、圆角按钮 |
|
||||
|  | **Heritage** | 藏蓝与金色、双线边框、衬线字体 |
|
||||
|  | **Horizon** | 蓝色强调色、灰色文字、居中白色卡片 |
|
||||
|  | **Ink** | 报刊式布局、侧栏、衬线字体与首字下沉 |
|
||||
|  | **Mono** | 黑白配色、红色强调色、直角边框 |
|
||||
|  | **Neon** | 深色背景、粉红与青色、发光效果 |
|
||||
|  | **Terminal** | 深色背景、等宽字体、绿色强调色 |
|
||||
|  | **Terra** | 大地色调、陶土色按钮、衬线字体 |
|
||||
|
||||
[**在线预览画廊**](../preview/index.html)
|
||||
|
||||
---
|
||||
截图仅展示当前默认构建效果,其他邮件类型和语言可通过[本地预览](#预览)查看;更新截图请参阅[截图指南](images/README.md)。
|
||||
|
||||
## 安装
|
||||
|
||||
选择一种风格,将 `mail/` 目录复制到 Gitea 自定义模板路径:
|
||||
### 选择安装来源
|
||||
|
||||
运行 `gitea --version` 确认部署的 Gitea 实例版本,再按照[兼容矩阵](../COMPATIBILITY.md#compatibility-matrix)选择模板版本。
|
||||
|
||||
> [!WARNING]
|
||||
> 我们不建议直接从源码仓库拉取并构建,因为这些版本还未经过完全测试达到发行可用的水平。如果存在未知的模板参数过时或遗漏可能导致 Gitea 实例启动异常。
|
||||
|
||||
> [!TIP]
|
||||
> 如果仍然需要从源码构建邮件模板,下方同样给出的相应步骤方法。
|
||||
|
||||
| 来源 | 邮件模板目录 | 准备步骤 |
|
||||
|---|---|---|
|
||||
| 发行版 | `themes/<名称>/mail/` | 下载并解压推荐版本的发行包 |
|
||||
| 源码 | `build/themes/<名称>/mail/` | 使用 Go 1.24 或更高版本构建 |
|
||||
|
||||
从源码构建时,请在仓库根目录执行:
|
||||
|
||||
```bash
|
||||
cp -r themes/horizon/mail/* /var/lib/gitea/custom/templates/mail/
|
||||
cd tools
|
||||
go run . build all
|
||||
cd ..
|
||||
```
|
||||
构建可用模板时需要依赖于 Gitea 官方仓库的资源文件,以确保来源的一致性。因此该命令会自动下载由`gitea.lock.json` 文件锁定的官方文件,后续构建无需再次下载,使用缓存目录资源即可运行。文件缺失或缓存损坏的处理方式见[更新官方快照](CONTRIBUTING.zh-CN.md#更新官方快照)。
|
||||
|
||||
### 安装主题
|
||||
|
||||
将所选主题 `mail/` 目录中的内容复制到 `<GITEA_CUSTOM>/templates/mail/`,然后重启 Gitea 即可生效。复制前请确认实例实际使用的自定义目录。以下是常见部署路径示例:
|
||||
|
||||
| 部署方式 | 自定义目录示例 |
|
||||
|---|---|
|
||||
| Linux Binary | `/var/lib/gitea/custom` |
|
||||
| Docker | `/data/gitea` |
|
||||
| Windows | `C:\gitea\custom` |
|
||||
|
||||
例如,在使用 systemd 管理 Gitea 的 Linux 主机上,从已解压的发行包目录执行:
|
||||
|
||||
```bash
|
||||
systemctl stop gitea
|
||||
mkdir -p /var/lib/gitea/custom/templates/mail
|
||||
cp -r themes/horizon/mail/. /var/lib/gitea/custom/templates/mail/
|
||||
systemctl restart gitea
|
||||
```
|
||||
|
||||
切换风格只需覆盖文件,无需更改配置。
|
||||
> [!TIP]
|
||||
> 使用源码构建产物时,将复制来源改为 `build/themes/horizon/mail/.`。Docker 和 Windows 部署请使> 用相应的容器或服务管理方式暂停和重启。
|
||||
|
||||
### 切换主题
|
||||
|
||||
先备份或移除上一主题安装的文件,选择新的邮件主题依照上述安装步骤重新操作即可。
|
||||
|
||||
### 确认生效
|
||||
|
||||
管理后台的测试邮件不会使用自定义模板。要验证模板是否生效,请触发一次真实的
|
||||
邮件通知。最快的方式是密码重置:退出登录,点击登录页的**"忘记密码"**,查看
|
||||
重置邮件即可——它将使用你的自定义样式渲染。
|
||||
|
||||
---
|
||||
使用测试账户触发密码重置等邮件通知,检查邮件样式和链接。管理后台的测试邮件按钮不使用自定义邮件模板,因此无法确认邮件模板是否生效。
|
||||
|
||||
## 预览
|
||||
|
||||
**静态模式:**
|
||||
```bash
|
||||
cd tools && go run . preview all
|
||||
```
|
||||
然后打开 `preview/index.html`。
|
||||
通过仓库中提供的预览工具,可以在部署前预览邮件不同主题,不同模板类型和语言的渲染效果
|
||||
|
||||
### 静态预览
|
||||
|
||||
首先需要在源码仓库中生成预览数据:
|
||||
|
||||
**开发服务器(实时重载):**
|
||||
```bash
|
||||
cd tools && go run . dev
|
||||
# → http://localhost:3456
|
||||
cd tools
|
||||
go run . preview all
|
||||
cd ..
|
||||
```
|
||||
|
||||
| 功能 | 静态 | Dev |
|
||||
|-----------|--------|-----|
|
||||
| Go 模板渲染 | ✅ | ✅ |
|
||||
| 主题/模板切换 | ✅ | ✅ |
|
||||
| 实时重载 | — | ✅ |
|
||||
在浏览器中打开 [preview/index.html](../preview/index.html) 即可。无需启动服务器。
|
||||
|
||||
---
|
||||
### 热重载开发
|
||||
|
||||
```bash
|
||||
cd tools
|
||||
go run . dev
|
||||
## 在终端中输入 Ctrl + C 即可终止服务器运行
|
||||
cd ..
|
||||
```
|
||||
|
||||
打开 [http://127.0.0.1:3456](http://127.0.0.1:3456)。Go 服务器监听主题文件、共享框架、锁文件与缓存、预览测试数据的变化。一旦监听到文件更改,服务器会将邮件模板重新构建后通过发送事件(SSE)刷新页面,以实现实时热重载。
|
||||
|
||||
### 控制和快捷键
|
||||
|
||||
| 控件 | 选项或快捷键 |
|
||||
|---|---|
|
||||
| 主题、模板、语言和视图 | `←` / `→` 切换选择框,`↑` / `↓` 选择选项 |
|
||||
| 视图 | **Modern** 显示渲染结果,**Source** 显示生成的 HTML 文本 |
|
||||
| 视口 | **Desktop**(1386 × 780)、**Mobile**(390 × 780);快捷键 `d` / `m` |
|
||||
| 信息面板 | `p` 展开或收起面板 |
|
||||
|
||||
> [!WARNING]
|
||||
> 预览使用示例数据渲染模板。浏览器与邮件客户端对 CSS 的支持不同,部署前还需在目标邮件客户端中检查效果。
|
||||
|
||||
## 兼容性
|
||||
|
||||
- **Gitea 1.25+** — v1.25 引入的邮件模板目录结构
|
||||
- **最新测试:** Gitea 1.27.3<!-- TRACKER:LATEST-TESTED -->
|
||||
- **最新发布版:** [v1.27.3](https://github.com/KenanZhu/GiteaMailTemplates/releases/tag/v1.27.3) 已修复 Bloom、Ember、Heritage 主题在 Gitea 1.27.1+ 上的推送通知问题;旧版 v1.27.2 压缩包仍受影响(详见 [兼容性说明](../COMPATIBILITY.md))
|
||||
- 当前源码使用 Gitea 官方模板的数据路径;各发布版的限制详见 [兼容性说明](../COMPATIBILITY.md)
|
||||
- **最新测试:** Gitea 28.0.0
|
||||
- **最新发布版:** [v28.0.0](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/latest)
|
||||
- **上游 Gitea 28.1.0:** [PENDING]
|
||||
|
||||
## 项目目录结构
|
||||
|
||||
项目按共享框架、主题样式和构建工具组织。源码与文档纳入版本控制,下载的官方文件和生成的模板、预览数据不纳入版本控制。
|
||||
|
||||
### 版本控制中
|
||||
|
||||
```text
|
||||
.github/ # 工作流及维护脚本
|
||||
release-notes/ # 各版本的发行说明
|
||||
scripts/ # 文档更新、打包和 Gitea 发布脚本
|
||||
workflows/ # 验证和发布
|
||||
docs/ # 中文文档、文档索引和画廊图片
|
||||
framework/ # 共享邮件控件和布局预设
|
||||
layouts/ # 各类邮件共用的布局预设
|
||||
mail/base/ # 页头、操作按钮、备用链接、侧栏和页脚
|
||||
preview/index.html # 预览界面
|
||||
themes/<THEME_NAME>/ # 主题源码
|
||||
tools/ # Go 命令行工具、构建工具和测试
|
||||
builder/ # 官方模板适配和主题生成
|
||||
cli/ # 命令定义和参数处理
|
||||
config/ # 预览配置读取和校验
|
||||
data/ # 邮件类型说明和预览示例数据
|
||||
preview/ # 邮件渲染、语言适配和开发服务器
|
||||
upstream/ # 官方快照下载、校验和翻译键发现
|
||||
gitea.lock.json # 官方版本标签、提交和文件校验值
|
||||
README.md
|
||||
```
|
||||
|
||||
官方模板提供通知内容、条件、主题行和链接;共享框架负责邮件呈现,主题通过元数据和 CSS 选择布局、定义样式。主题目录中不保存官方邮件源码或翻译文件。
|
||||
|
||||
### 不在版本控制中
|
||||
|
||||
以下文件由构建和预览命令生成,或按锁文件下载,无需手动维护或提交:
|
||||
|
||||
```text
|
||||
build/themes/<THEME_NAME>/ # 主题构建结果
|
||||
mail/ # 可安装到 Gitea 的邮件模板
|
||||
build.json # 构建来源和生成文件的校验记录
|
||||
build/upstream/ # 锁文件指定的官方模板、语言、资源和许可证
|
||||
dist/ # 默认输出的发行压缩包
|
||||
preview/rendered.js # 预览清单
|
||||
preview/rendered/<LOCALE>.js # 各语言的预览数据
|
||||
```
|
||||
|
||||
`build all` 生成全部主题的安装模板,`preview all` 同时生成模板和各语言的预览数据。首次使用时,工具按 `gitea.lock.json` 准备缺失的官方文件缓存;后续使用前会校验缓存。
|
||||
|
||||
### 模板类型
|
||||
|
||||
当前 Gitea 包含以下 11 种邮件模板 [1](#注释):
|
||||
|
||||
| 文件 | 通知类型 |
|
||||
|---|---|
|
||||
| `mail/user/auth/activate.tmpl` | 账户激活 |
|
||||
| `mail/user/auth/activate_email.tmpl` | 邮箱验证 |
|
||||
| `mail/user/auth/register_notify.tmpl` | 注册通知 |
|
||||
| `mail/user/auth/reset_passwd.tmpl` | 密码重置 |
|
||||
| `mail/org/team_invite.tmpl` | 团队邀请 |
|
||||
| `mail/repo/collaborator.tmpl` | 添加仓库协作者 |
|
||||
| `mail/repo/transfer.tmpl` | 仓库所有权转移 |
|
||||
| `mail/repo/release.tmpl` | 发布新版本 |
|
||||
| `mail/repo/actions/workflow_run.tmpl` | Actions 工作流运行 |
|
||||
| `mail/repo/issue/assigned.tmpl` | 议题或合并请求指派 |
|
||||
| `mail/repo/issue/default.tmpl` | 议题或合并请求动态 |
|
||||
|
||||
## 参与贡献
|
||||
|
||||
欢迎改进主题、工具、文档和翻译。[贡献指南](CONTRIBUTING.zh-CN.md)介绍了本地环境准备、设计规范、检查要求和发布流程。
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT — 详见 [LICENSE](../LICENSE)。
|
||||
本项目采用 [MIT 许可证](../LICENSE)。生成的发行包保留 Gitea 许可证和快照来源信息,详见[第三方声明](../THIRD_PARTY_NOTICES.md)。
|
||||
|
||||
---
|
||||
### 注释
|
||||
|
||||
[1](#注释):[Mail templates | Gitea Documentation](https://docs.gitea.com/administration/mail-templates/)
|
||||
|
||||
本项目与 Gitea 官方无隶属关系。
|
||||
@@ -1,27 +1,27 @@
|
||||
# Style Preview Images
|
||||
# Gallery Images
|
||||
|
||||
Place theme style screenshots of **Desktop** in PNG format here, captured from the [live preview](../preview/index.html).
|
||||
Gallery images show the current source build. Historical release archives retain their original templates.
|
||||
|
||||
## Naming Convention
|
||||
## Capture Settings
|
||||
|
||||
```
|
||||
horizon.png terminal.png ember.png bloom.png heritage.png
|
||||
neon.png mono.png terra.png ink.png aurora.png
|
||||
```
|
||||
Use the same settings for every theme so images remain comparable:
|
||||
|
||||
## Image Size Requirements
|
||||
| Setting | Value |
|
||||
|---|---|
|
||||
| Template | **Register Notify** |
|
||||
| Language | **en-US** |
|
||||
| View | **Modern** |
|
||||
| Viewport | **Desktop** |
|
||||
| Information panel | Collapsed |
|
||||
| Output | PNG, 600px wide recommended |
|
||||
|
||||
To ensure screenshots can be displayed in the README, please follow these size requirements:
|
||||
Save images as `<theme-name>.png` in this directory, matching the directory name under `themes/`. Each file must be at most **50 KiB**; **10–20 KiB** is preferred. Optimize larger PNGs with tools such as `pngquant` or `optipng`.
|
||||
|
||||
- **Maximum:** 50 KiB per image
|
||||
- **Recommended:** 30–40 KiB
|
||||
- **Format:** PNG, optimised — run through `pngquant` or `optipng` before committing
|
||||
## Manual Capture
|
||||
|
||||
## How to Capture
|
||||
1. Run `go run . dev` from `tools/` and open [http://127.0.0.1:3456](http://127.0.0.1:3456). Alternatively, run `go run . preview all` and open [preview/index.html](../../preview/index.html) directly.
|
||||
2. Select a theme and apply the capture settings above.
|
||||
3. Capture the rendered email, excluding the preview toolbar and information panel.
|
||||
4. Save the PNG as `<theme-name>.png`, check its size and repeat for the remaining themes.
|
||||
|
||||
1. Start the dev server: `cd tools && go run . dev` (opens http://localhost:3456 in your browser)
|
||||
2. For each style, select the **"Activate Account"** template and **"Modern"** client mode in **"Desktop"**
|
||||
3. Take a screenshot of the rendered email (600px width recommended)
|
||||
4. Save as `<style-name>.png` in this directory
|
||||
|
||||
> For static preview, run `cd tools && go run . preview all` then open `preview/index.html` directly.
|
||||
When adding a theme, update its gallery entry in both the [English README](../../README.md#style-gallery) and [Simplified Chinese README](../README.zh-CN.md#风格画廊).
|
||||
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 17 KiB |
@@ -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 <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)
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
</td></tr>
|
||||
</table>
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
</td></tr>
|
||||
<tr><td class="email-footer">
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
<table role="presentation" width="100%" align="center" cellpadding="0" cellspacing="0" border="0" class="email-wrap">
|
||||
<tr><td class="email-header">__HEADER__</td></tr>
|
||||
<tr><td class="email-card card-body mail-type-__MAIL_TYPE__">
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
</td></tr>
|
||||
</table>
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
</td></tr>
|
||||
<tr><td class="email-footer">
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
<table role="presentation" width="100%" align="center" cellpadding="0" cellspacing="0" border="0" class="email-wrap email-card">
|
||||
<tr><td class="email-header">__HEADER__</td></tr>
|
||||
<tr><td class="email-body card-body mail-type-__MAIL_TYPE__">
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
</td></tr>
|
||||
</table>
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
</td></tr>
|
||||
<tr><td class="email-footer">
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
<table role="presentation" width="100%" align="center" cellpadding="0" cellspacing="0" border="0" class="email-wrap email-card">
|
||||
<tr><td class="email-header">__HEADER__</td></tr>
|
||||
<tr><td class="email-body card-body mail-type-__MAIL_TYPE__">
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
</td></tr>
|
||||
</table>
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
</td></tr>
|
||||
<tr><td class="email-footer">
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
<table role="presentation" width="100%" align="center" cellpadding="0" cellspacing="0" border="0" class="email-wrap email-card">
|
||||
<tr><td class="email-header">__HEADER__</td></tr>
|
||||
<tr><td class="email-body card-body mail-type-__MAIL_TYPE__">
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
</td></tr>
|
||||
</table>
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
</td></tr>
|
||||
<tr><td class="email-footer">
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
<table role="presentation" width="100%" align="center" cellpadding="0" cellspacing="0" border="0" class="email-wrap">
|
||||
<tr><td class="email-header">__HEADER__</td></tr>
|
||||
<tr><td class="email-card card-body mail-type-__MAIL_TYPE__">
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
</td></tr>
|
||||
</table>
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
</td><td width="36%" class="email-sidebar resp-hide">__SIDEBAR__</td></tr>
|
||||
<tr><td colspan="2" class="email-footer">
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
<table role="presentation" width="100%" align="center" cellpadding="0" cellspacing="0" border="0" class="email-wrap">
|
||||
<tr><td colspan="2" class="email-header">__HEADER__</td></tr>
|
||||
<tr><td width="64%" class="email-card card-body mail-type-__MAIL_TYPE__">
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
</td></tr>
|
||||
</table>
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
</td></tr>
|
||||
<tr><td class="email-footer">
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
<table role="presentation" width="100%" align="center" cellpadding="0" cellspacing="0" border="0" class="email-wrap">
|
||||
<tr><td class="email-header">__HEADER__</td></tr>
|
||||
<tr><td class="email-card card-body mail-type-__MAIL_TYPE__">
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
</td></tr>
|
||||
</table>
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
</td></tr>
|
||||
<tr><td class="email-footer">
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
<table role="presentation" width="100%" align="center" cellpadding="0" cellspacing="0" border="0" class="email-wrap">
|
||||
<tr><td class="email-header">__HEADER__</td></tr>
|
||||
<tr><td class="email-card card-body mail-type-__MAIL_TYPE__">
|
||||
@@ -0,0 +1 @@
|
||||
</td></tr></table>
|
||||
@@ -0,0 +1 @@
|
||||
</td></tr><tr><td class="email-footer">
|
||||
@@ -0,0 +1 @@
|
||||
<table role="presentation" width="100%" align="center" cellpadding="0" cellspacing="0" border="0" class="email-wrap"><tr><td class="email-header">__HEADER__</td></tr><tr><td class="email-card card-body mail-type-__MAIL_TYPE__">
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
</td></tr>
|
||||
</table>
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
</td></tr>
|
||||
<tr><td class="email-footer">
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
<table role="presentation" width="100%" align="center" cellpadding="0" cellspacing="0" border="0" class="email-wrap">
|
||||
<tr><td class="email-header">__HEADER__</td></tr>
|
||||
<tr><td class="email-card card-body mail-type-__MAIL_TYPE__">
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
</td></tr>
|
||||
</table>
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
</td></tr>
|
||||
<tr><td class="email-footer">
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
<table role="presentation" width="100%" align="center" cellpadding="0" cellspacing="0" border="0" class="email-wrap">
|
||||
<tr><td class="email-header">__HEADER__</td></tr>
|
||||
<tr><td class="email-card card-body mail-type-__MAIL_TYPE__">
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="email-action">
|
||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-table framework-extra"><tr><td class="email-btn-row"><a href="{{.URL}}" class="email-btn">{{.Label}}</a></td></tr></table>
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" class="email-link-box"><tr><td class="email-link-cell">
|
||||
<p class="email-link-label framework-extra">{{.Hint}}</p>
|
||||
<p class="email-link-url"><a href="{{.URL}}">{{.URL}}</a></p>
|
||||
</td></tr></table>
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
<p class="email-footer-text"><span class="framework-extra">© </span><a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a></p>
|
||||
@@ -0,0 +1,11 @@
|
||||
<div class="framework-extra">
|
||||
{{if eq .Variant "terminal"}}<span class="email-brand">> {{AppName}}</span>
|
||||
{{else if eq .Variant "mono"}}<span class="email-brand">{{AppName}}<span class="email-brand-star">*</span></span>
|
||||
{{else if eq .Variant "neon"}}<span class="email-brand">{{AppName}}</span>
|
||||
{{else if eq .Variant "heritage"}}<img src="{{AppUrl}}assets/img/favicon.png" alt="Gitea" width="40" height="40" class="email-logo" /><p class="email-brand">{{AppName}}</p>
|
||||
{{else if eq .Variant "bloom"}}<img src="{{AppUrl}}assets/img/favicon.png" alt="Gitea" width="32" height="32" class="email-logo" /><p class="email-brand">{{AppName}}</p>
|
||||
{{else if eq .Variant "ink"}}<img src="{{AppUrl}}assets/img/favicon.png" alt="Gitea" width="24" height="24" class="email-logo" /><span class="email-brand">{{AppName}}</span><br /><span class="email-tagline">Git with a cup of tea</span>
|
||||
{{else if eq .Variant "terra"}}<img src="{{AppUrl}}assets/img/favicon.png" alt="Gitea" width="28" height="28" class="email-logo" /><span class="email-brand">{{AppName}}</span><div class="email-brand-rule"></div>
|
||||
{{else if eq .Variant "aurora"}}<img src="{{AppUrl}}assets/img/favicon.png" alt="Gitea" width="24" height="24" class="email-logo" /><span class="email-brand">{{AppName}}</span>
|
||||
{{else}}<img src="{{AppUrl}}assets/img/favicon.png" alt="Gitea" width="32" height="32" class="email-logo" /><span class="email-brand">{{AppName}}</span>{{end}}
|
||||
</div>
|
||||
@@ -0,0 +1,6 @@
|
||||
<div class="framework-extra sidebar-info">
|
||||
<p class="sidebar-label">{{AppName}}</p>
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" class="sidebar-info-table"><tr><td>
|
||||
<strong>{{AppDomain}}</strong><br /><a href="{{AppUrl}}">{{AppUrl}}</a>
|
||||
</td></tr></table>
|
||||
</div>
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"repository": "https://github.com/go-gitea/gitea",
|
||||
"tag": "v28.0.0",
|
||||
"commit": "15b8a5805adf57c5189602008d38cccfd3c795e0",
|
||||
"files": {
|
||||
"LICENSE": "ed2f10a9d78b8c6c9ef33f1420d0eb266981891caf2f15d630553f02dc60d3ae",
|
||||
"modules/templates/mail.go": "fb8204b79f700f88c7c3f0da16e50c3d678a2216e62e98f712805ed1d42ab3a9",
|
||||
"modules/translation/i18n/format.go": "f440236eaf1f73fe0a9aa36a2c036a4b34c6ab28c9e4cf971bef7fceee701cb4",
|
||||
"modules/translation/i18n/localestore.go": "e55a7832d01753622a7d47e904de95e5aed7ad3535c59bf6ac3fec4346e40e9d",
|
||||
"modules/translation/translation.go": "4217cbd9beb79c0e1be52dd9a7d7705b9ff4becc2d9515d49236db126da0f7ee",
|
||||
"options/locale/locale_cs-CZ.json": "816c4a0cfc6112c8cc9a63e63b3de0bc623819a23d2f2bbb800b09c45d23da66",
|
||||
"options/locale/locale_de-DE.json": "593ec4e3652c0faf9ecb5d5b27a649c1c0a933030ecdfbf4dc810ea4ad69bfb8",
|
||||
"options/locale/locale_el-GR.json": "dd03e6e083d6286d8eb2045a37bcf5c578fc339ab87a72a7397e01733078d89e",
|
||||
"options/locale/locale_en-US.json": "bb58779f10b2cd0ee94f71297b2231a621d4d30f2a56a2b223f3c586f635f592",
|
||||
"options/locale/locale_es-ES.json": "cbb1cd7bc4709a189b8b189d26a83a06fed0c32e0b5ac5602370fb0f03a39d04",
|
||||
"options/locale/locale_fa-IR.json": "ad85f5829335844883608ed5ed09dee5bc5f4800cda7ef9277200aa4a7f35f32",
|
||||
"options/locale/locale_fi-FI.json": "3b811b0ee794548399bcb64a887711a26cc0c26e04dfd0305b0846742d065952",
|
||||
"options/locale/locale_fr-FR.json": "a725246762b129e6323ff2e74baa75eb4c63e893ef0e3e226d3a020d60e1fd87",
|
||||
"options/locale/locale_ga-IE.json": "670c916a233c83eea5efd7e73971763dd2cd5886a832064724fccfdef87d7bf9",
|
||||
"options/locale/locale_hu-HU.json": "39fc4beb3a602bbb8d41d8ab6c2cbfa7220d0c52c7cf9d631e5d2304209618a5",
|
||||
"options/locale/locale_id-ID.json": "1916c9d9f600ddbeba53d2566cb3687c28a57920b2e66a5ef67ea7989535932b",
|
||||
"options/locale/locale_is-IS.json": "fc1a98822acc354d0915ff6e221722a4d6d26215d52a77d4b4b90259b22f7447",
|
||||
"options/locale/locale_it-IT.json": "c67559a29021731cb7ffab0fe25a4300025c7e45d9d61488f1a1c99752360854",
|
||||
"options/locale/locale_ja-JP.json": "e5e64b955a358630b43b6f4cb79b1790175f685a90d46f7071d93dafbdbce9cb",
|
||||
"options/locale/locale_ko-KR.json": "5f4ed6436333a25c1c638c8e22c8bdf1cf71e96162c43e61a9fa86d0c3bb2bab",
|
||||
"options/locale/locale_lv-LV.json": "eab8fa9711921b30166649050955c9ae661a0da15261c89be92687508469d863",
|
||||
"options/locale/locale_nl-NL.json": "b8e58a9832d5da637656f53c6288e35860402e458b3ffd552c0d3c980310caef",
|
||||
"options/locale/locale_pl-PL.json": "b8cec5b14ac491b16b0f11254df294ac6806be684d0d28e0758f42f48d6d993f",
|
||||
"options/locale/locale_pt-BR.json": "c9e7d762eb9a00733fcdd935b565372ec28a2cdc11d6d95c9ea06a870b1951a4",
|
||||
"options/locale/locale_pt-PT.json": "64ab9f016103379b9b5d18d71b03190bcb4fd38e9e4f1e1768a23b14edc21840",
|
||||
"options/locale/locale_ru-RU.json": "d607a77a12abe0ccb87d9392a096369005b48ac74b2d4767a19455bd608f2c42",
|
||||
"options/locale/locale_si-LK.json": "ed1f7fc6fd2153d541b124dc1051666fbc8750b9c1fd3f3befd5d976590ffc0a",
|
||||
"options/locale/locale_sk-SK.json": "91adc0dfe01783eb9c6b7f4a7c9941a215079a610cd1ecbeac1fe574ea67c95d",
|
||||
"options/locale/locale_sv-SE.json": "c4a6448c23dbed6e383d0b417a8a872abf187f76394780da6809182ba4343749",
|
||||
"options/locale/locale_tr-TR.json": "6d5774ff6293e60bee0961c072e0ddea272656a12afe8d6eb94e330bcc8dcdae",
|
||||
"options/locale/locale_uk-UA.json": "fabce931b5f7bc378494fe752f3a5148d6f37c6b9ae07674c09d3afa274378d5",
|
||||
"options/locale/locale_zh-CN.json": "682925d41494eab564ff1a13269def6e396676d13cfcefabb22aeff75b808a4d",
|
||||
"options/locale/locale_zh-TW.json": "9d242d16492e808889d7cdcc5db2ee5e53baabfc3d3dc13ea854c8852e963ec4",
|
||||
"public/assets/img/favicon.png": "cbe472b55c0b2f2e57db58e991fc457e136fbaf246f54aaf7631ff089c76344f",
|
||||
"templates/mail/base/footer.tmpl": "5accf2a39dffd952fd2d55a4fa867d33d91415792f11f90bc7e2fc8f8492117b",
|
||||
"templates/mail/base/head.tmpl": "239a2f805949ca7ebd27fa11a34a20bbb7a5d435a95f3c25c55a4139126d7476",
|
||||
"templates/mail/org/team_invite.tmpl": "646133e75c9efdce96eb7d58d8aea0111cae890c816a718ee8098346ab089a44",
|
||||
"templates/mail/repo/actions/workflow_run.tmpl": "dbd34f3c7fb4f10a7497ca2872a648104c098f425866cae50f44b98033000b45",
|
||||
"templates/mail/repo/collaborator.tmpl": "dd8a858a4fcdb37c71c88b9e51f7f5bbfa3c6afec572e2c9c7801fde4ececfb5",
|
||||
"templates/mail/repo/issue/assigned.tmpl": "347cbe856d748a8c36fb8c9e5db64b831b2a8faf86a266a2e926e2eb1ab9a24b",
|
||||
"templates/mail/repo/issue/default.tmpl": "98c36bb240f4946d9364fa4ebd386cff599f03bc2526c107881b8ce5a39a9ab8",
|
||||
"templates/mail/repo/release.tmpl": "5b6c37286eedff5502efaeeaefac52190b835af0711055f387115bb4dd7ed4e5",
|
||||
"templates/mail/repo/transfer.tmpl": "f394e8c0a75a16bbd19389b2bc286111326bcbcbb4e900e43b708ae74d0546a5",
|
||||
"templates/mail/user/auth/activate.tmpl": "cd360177635d49663f25d700ac1d7f15f551f8de152053a39402267f896cfdee",
|
||||
"templates/mail/user/auth/activate_email.tmpl": "3bfa50ce1801fc928d6359a8a52fd9578dac40f185aea4c1d792fc5ddd4bbf0c",
|
||||
"templates/mail/user/auth/register_notify.tmpl": "ee11808ccc63241cedc29c2038a8ee29583817f51743bb56adc379944de5c8af",
|
||||
"templates/mail/user/auth/reset_passwd.tmpl": "9efd3651c5e82a2d411c569f0736c6c55201a062d29747b985eb6af268f81b8e"
|
||||
}
|
||||
}
|
||||
@@ -239,6 +239,11 @@ html,body,#preview-area,#panel-body{
|
||||
<select id="sel-template" onchange="switchTemplate(this.value)"></select>
|
||||
</span>
|
||||
<span class="sep"></span>
|
||||
<span class="ctl-group">
|
||||
<label>Language</label>
|
||||
<select id="sel-locale" onchange="switchLocale(this.value)"></select>
|
||||
</span>
|
||||
<span class="sep"></span>
|
||||
<span class="ctl-group">
|
||||
<label>View</label>
|
||||
<select id="sel-client" onchange="switchClient(this.value)">
|
||||
@@ -323,10 +328,14 @@ html,body,#preview-area,#panel-body{
|
||||
//
|
||||
// State — REGISTRY and PARAMS are loaded from rendered.js (single source of truth)
|
||||
//
|
||||
const state = { theme:'', template:'', client:'modern', viewport:'desktop' };
|
||||
const state = { theme:'', template:'', client:'modern', viewport:'desktop', locale:'en-US' };
|
||||
let REGISTRY = {};
|
||||
let PARAMS = {};
|
||||
let rendered = null; // window.__RENDERED__
|
||||
let rendered = null;
|
||||
let manifest = null;
|
||||
let localeRequest = 0;
|
||||
let renderRequest = 0, renderTimer;
|
||||
const localeLoads = new Map();
|
||||
let devMode = false; // true once SSE connects
|
||||
|
||||
//
|
||||
@@ -338,20 +347,31 @@ function transform(html, mode) {
|
||||
const e = html.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||||
return '<!DOCTYPE html><html><head><style>body{background:#0d1117;color:#c9d1d9;font-family:Consolas,monospace;padding:16px;margin:0}pre{white-space:pre-wrap;word-break:break-all;font-size:11px;line-height:1.6;margin:0}</style></head><body><pre>'+e+'</pre></body></html>';
|
||||
}
|
||||
return html;
|
||||
// Gitea sends status icons as CID attachments. Preview has no MIME message,
|
||||
// so display equivalent local SVG icons without changing install templates.
|
||||
const doc = new DOMParser().parseFromString(html, 'text/html');
|
||||
// Static preview has no deployed instance; use the generated official asset.
|
||||
// Installable mail templates keep the instance's {{AppUrl}} image URL.
|
||||
doc.querySelectorAll('img.email-logo').forEach(img => { if(manifest?.logo) img.src=manifest.logo; });
|
||||
doc.querySelectorAll('img[src^="cid:"]').forEach(img => {
|
||||
const color = img.classList.contains('status-success') ? '#2da44e' : '#697077';
|
||||
const svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><circle cx="9" cy="9" r="7" fill="'+color+'"/><path d="M5 9l3 3 5-6" fill="none" stroke="white" stroke-width="2"/></svg>';
|
||||
img.src = 'data:image/svg+xml,' + encodeURIComponent(svg);
|
||||
});
|
||||
return '<!DOCTYPE html>' + doc.documentElement.outerHTML;
|
||||
}
|
||||
|
||||
//
|
||||
// Render preview
|
||||
//
|
||||
function render() {
|
||||
const request=++renderRequest;
|
||||
clearTimeout(renderTimer);
|
||||
const frame = document.getElementById('preview-frame');
|
||||
const loading = document.getElementById('loading');
|
||||
if(!rendered) return;
|
||||
|
||||
if (location.protocol !== 'file:') {
|
||||
frame.setAttribute('sandbox', '');
|
||||
}
|
||||
|
||||
frame.style.display = 'none';
|
||||
loading.style.display = 'flex';
|
||||
@@ -359,18 +379,19 @@ function render() {
|
||||
const src = rendered;
|
||||
const raw = src[state.theme]?.[state.template];
|
||||
let content = raw ? transform(raw, state.client) : '<p style=\"color:red;padding:20px\">Not found: '+state.theme+'/'+state.template+'</p>';
|
||||
content = content.replace('</head>', '<style>html{scrollbar-color:rgba(107,125,153,.25) transparent;scrollbar-width:thin;-ms-overflow-style:-ms-autohiding-scrollbar}html::-webkit-scrollbar{width:6px;height:6px}html::-webkit-scrollbar-track{background:transparent}html::-webkit-scrollbar-thumb{background-color:rgba(107,125,153,.25);border-radius:3px;border:1px solid rgba(255,255,255,.06)}html::-webkit-scrollbar-thumb:hover{background-color:rgba(107,125,153,.45)}html::-webkit-scrollbar-corner{background:transparent}</style></head>');
|
||||
content = content.replace('</head>', '<style>html,body{scrollbar-width:none;-ms-overflow-style:none}html::-webkit-scrollbar,body::-webkit-scrollbar{display:none;width:0;height:0}</style></head>');
|
||||
|
||||
let done = false;
|
||||
function finish() {
|
||||
if (done) return; done = true;
|
||||
if (done || request!==renderRequest) return; done = true;
|
||||
clearTimeout(renderTimer);
|
||||
loading.style.display = 'none';
|
||||
frame.style.display = 'block';
|
||||
frame.onload = null;
|
||||
}
|
||||
frame.onload = finish;
|
||||
frame.srcdoc = content;
|
||||
setTimeout(finish, 5000);
|
||||
renderTimer=setTimeout(finish, 5000);
|
||||
updatePanel();
|
||||
}
|
||||
|
||||
@@ -391,10 +412,11 @@ function toggleSection(id) {
|
||||
// Panel content updates
|
||||
//
|
||||
function updatePanel() {
|
||||
const escape = v => String(v).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
const r = REGISTRY[state.template];
|
||||
document.getElementById('panel-name').textContent = r ? r.name : state.template;
|
||||
document.getElementById('panel-desc').textContent = r ? r.desc : '';
|
||||
document.getElementById('panel-path').textContent = r ? 'themes/'+state.theme+'/'+r.path : '-';
|
||||
document.getElementById('panel-path').textContent = r ? 'build/themes/'+state.theme+'/'+r.path+' (official snapshot + theme overrides)' : '-';
|
||||
|
||||
// Template stats
|
||||
const themes = rendered ? Object.keys(rendered).length : 0;
|
||||
@@ -407,13 +429,13 @@ function updatePanel() {
|
||||
}
|
||||
}
|
||||
document.getElementById('panel-stats').textContent =
|
||||
themes + ' themes loaded · ' + totalTemplates + ' templates (' + (totalTemplates - totalFailed) + ' ok, ' + totalFailed + ' failed)';
|
||||
manifest.upstream + ' · ' + state.locale + ' · ' + themes + ' themes · ' + totalTemplates + ' templates (' + (totalTemplates - totalFailed) + ' ok, ' + totalFailed + ' failed)';
|
||||
|
||||
const p = PARAMS[state.template] || {};
|
||||
let rows = '';
|
||||
for(const [k,v] of Object.entries(p)){
|
||||
const isReq = !k.includes('RenderedNote') && !k.includes('Attachments') && !k.includes('DisableDownload') && !k.includes('IsMention') && !k.includes('IsPull') && !k.includes('IsForcePush') && !k.includes('ReviewComments') && !k.includes('Commits') && !k.includes('BaseBranch');
|
||||
rows += '<tr><td>.'+k+'</td><td>'+String(v)+'</td><td><span class="badge '+(isReq?'badge-req':'badge-opt')+'">'+(isReq?'required':'optional')+'</span></td></tr>';
|
||||
rows += '<tr><td>.'+escape(k)+'</td><td>'+escape(v)+'</td><td><span class="badge '+(isReq?'badge-req':'badge-opt')+'">'+(isReq?'required':'optional')+'</span></td></tr>';
|
||||
}
|
||||
document.getElementById('panel-params').innerHTML = rows || '<tr><td colspan="3" style="color:var(--muted)">-</td></tr>';
|
||||
}
|
||||
@@ -422,7 +444,7 @@ function updatePanel() {
|
||||
// Events
|
||||
//
|
||||
function switchTheme(v) { state.theme=v; render(); }
|
||||
function switchTemplate(v) { state.template=v; render(); }
|
||||
function switchTemplate(v) { state.template=v; switchLocale(state.locale); }
|
||||
function switchClient(v) { state.client=v; render(); }
|
||||
function setViewport(v) {
|
||||
state.viewport=v;
|
||||
@@ -443,7 +465,7 @@ document.addEventListener('keydown',e=>{
|
||||
if(e.key==='p'&&!e.ctrlKey&&!e.metaKey&&!e.altKey){ togglePanel(); return; }
|
||||
|
||||
// ← → cycle focus between Theme / Template / Client selects
|
||||
const selects=['sel-theme','sel-template','sel-client'];
|
||||
const selects=['sel-theme','sel-template','sel-locale','sel-client'];
|
||||
const activeId=document.activeElement?.id;
|
||||
const cur=selects.indexOf(activeId);
|
||||
|
||||
@@ -460,38 +482,97 @@ document.addEventListener('keydown',e=>{
|
||||
});
|
||||
|
||||
//
|
||||
// Init — load everything from rendered.js (single source of truth)
|
||||
//
|
||||
(function(){
|
||||
if(window.__REGISTRY__) REGISTRY = window.__REGISTRY__;
|
||||
if(window.__PARAMS__) PARAMS = window.__PARAMS__;
|
||||
|
||||
if(window.__RENDERED__){
|
||||
rendered = window.__RENDERED__;
|
||||
|
||||
const themeKeys = Object.keys(rendered).sort();
|
||||
const selTheme = document.getElementById('sel-theme');
|
||||
themeKeys.forEach(t => {
|
||||
const o = document.createElement('option');
|
||||
o.value = t;
|
||||
o.textContent = t.charAt(0).toUpperCase() + t.slice(1);
|
||||
selTheme.appendChild(o);
|
||||
});
|
||||
state.theme = themeKeys[0] || '';
|
||||
|
||||
const selTpl = document.getElementById('sel-template');
|
||||
for(const [id, t] of Object.entries(REGISTRY)){
|
||||
const o = document.createElement('option');
|
||||
o.value = id;
|
||||
o.textContent = t.name;
|
||||
selTpl.appendChild(o);
|
||||
// Manifest and language bundles
|
||||
function fillSelect(id, entries, selected) {
|
||||
const select = document.getElementById(id);
|
||||
select.replaceChildren();
|
||||
for (const [value,label] of entries) {
|
||||
const option = document.createElement('option');
|
||||
option.value=value; option.textContent=label; select.appendChild(option);
|
||||
}
|
||||
state.template = Object.keys(REGISTRY)[0] || '';
|
||||
} else {
|
||||
document.getElementById('loading').style.display='none';
|
||||
showMessage('rendered.js not found — run <code>cd tools && go run . preview all</code> to generate', 'error');
|
||||
select.value=selected;
|
||||
}
|
||||
|
||||
function applyManifest(data) {
|
||||
if (!data || data.schema !== 1 || !data.languages.length || !data.themes.length) {
|
||||
throw new Error('Invalid preview manifest; regenerate with go run . preview all');
|
||||
}
|
||||
manifest=data; REGISTRY=data.registry; PARAMS=data.params;
|
||||
if (!data.themes.includes(state.theme)) state.theme=data.themes[0];
|
||||
if (!REGISTRY[state.template]) state.template=Object.keys(REGISTRY)[0];
|
||||
if (!data.languages.includes(state.locale)) state.locale=data.languages.includes('en-US') ? 'en-US' : data.languages[0];
|
||||
fillSelect('sel-theme',data.themes.map(t=>[t,t.charAt(0).toUpperCase()+t.slice(1)]),state.theme);
|
||||
fillSelect('sel-template',Object.entries(REGISTRY).map(([id,t])=>[id,t.name]),state.template);
|
||||
fillSelect('sel-locale',data.languages.map(locale=>{
|
||||
let name;
|
||||
try {
|
||||
// Show each language in its own language, independent of the browser UI.
|
||||
if (Intl.DisplayNames.supportedLocalesOf([locale]).length) {
|
||||
name=new Intl.DisplayNames([locale],{type:'language',fallback:'none'}).of(locale);
|
||||
}
|
||||
} catch (_) { /* Older browsers can still select languages by locale code. */ }
|
||||
return [locale,name && name!==locale ? locale+' — '+name : locale];
|
||||
}),state.locale);
|
||||
document.querySelectorAll('#sel-locale option').forEach(option=>{
|
||||
option.lang=option.value;
|
||||
option.dir='auto';
|
||||
});
|
||||
}
|
||||
|
||||
function loadLocale(lang) {
|
||||
if (window.__MAIL_PREVIEW_DATA__?.[lang]) return Promise.resolve(window.__MAIL_PREVIEW_DATA__[lang]);
|
||||
if (localeLoads.has(lang)) return localeLoads.get(lang);
|
||||
const promise=new Promise((resolve,reject)=>{
|
||||
const script=document.createElement('script');
|
||||
script.src='rendered/'+encodeURIComponent(lang)+'.js'+(devMode ? '?v='+Date.now() : '');
|
||||
script.onload=()=>{
|
||||
script.remove();
|
||||
const data=window.__MAIL_PREVIEW_DATA__?.[lang];
|
||||
if (data) resolve(data); else reject(new Error('Invalid language bundle '+lang));
|
||||
};
|
||||
script.onerror=()=>{ script.remove(); reject(new Error('Language bundle missing: '+lang+'; regenerate preview data')); };
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
localeLoads.set(lang,promise);
|
||||
promise.catch(()=>localeLoads.delete(lang));
|
||||
return promise;
|
||||
}
|
||||
|
||||
async function switchLocale(lang) {
|
||||
if (!manifest?.languages.includes(lang)) return;
|
||||
state.locale=lang; rendered=null;
|
||||
const request=++localeRequest;
|
||||
document.getElementById('preview-frame').style.display='none';
|
||||
document.getElementById('loading').style.display='flex';
|
||||
try {
|
||||
const data=await loadLocale(lang);
|
||||
if (request!==localeRequest) return;
|
||||
rendered=data;
|
||||
try { localStorage.setItem('mail-preview-locale',lang); } catch (_) {}
|
||||
document.getElementById('locale-message')?.remove();
|
||||
const warnings=manifest.warnings?.[lang]?.[state.template] || [];
|
||||
const fallback=manifest.fallback?.[lang] || [];
|
||||
if (fallback.length || warnings.length) {
|
||||
const el=showMessage('',warnings.length ? 'warn' : 'info'); el.id='locale-message';
|
||||
el.textContent=warnings.join(' ') || '[FALLBACK] '+lang+': '+fallback.length+' referenced translation keys use en-US';
|
||||
}
|
||||
render();
|
||||
} catch (error) {
|
||||
if (request!==localeRequest) return;
|
||||
document.getElementById('loading').style.display='none';
|
||||
showMessage('', 'error').textContent = String(error);
|
||||
}
|
||||
}
|
||||
|
||||
(function(){
|
||||
try {
|
||||
try { state.locale=localStorage.getItem('mail-preview-locale') || 'en-US'; } catch (_) {}
|
||||
applyManifest(window.__MAIL_PREVIEW__);
|
||||
switchLocale(state.locale);
|
||||
} catch (error) {
|
||||
document.getElementById('loading').style.display='none';
|
||||
showMessage('Preview data unavailable — run <code>cd tools && go run . preview all</code> to generate','error');
|
||||
}
|
||||
})();
|
||||
|
||||
// Message helpers
|
||||
@@ -512,7 +593,7 @@ function setDevMode(on) {
|
||||
if (on) {
|
||||
clearMessages();
|
||||
showMessage(
|
||||
'Live reload active — changes to .tmpl files will auto-refresh the preview.',
|
||||
'Live reload active — theme CSS, shared controls, lock/cache and fixtures auto-refresh the preview.',
|
||||
'info'
|
||||
).id = 'msg-dev-disclaimer';
|
||||
}
|
||||
@@ -520,6 +601,7 @@ function setDevMode(on) {
|
||||
|
||||
// Dev-server live-reload (SSE)
|
||||
(function(){
|
||||
if (location.protocol === 'file:') return;
|
||||
let es;
|
||||
try {
|
||||
es = new EventSource('/events');
|
||||
@@ -533,14 +615,15 @@ function setDevMode(on) {
|
||||
const script = document.createElement('script');
|
||||
script.src = 'rendered.js?v=' + Date.now();
|
||||
script.onload = function() {
|
||||
if (window.__RENDERED__) rendered = window.__RENDERED__;
|
||||
if (window.__REGISTRY__) REGISTRY = window.__REGISTRY__;
|
||||
if (window.__PARAMS__) PARAMS = window.__PARAMS__;
|
||||
render();
|
||||
script.remove();
|
||||
window.__MAIL_PREVIEW_DATA__ = {};
|
||||
localeLoads.clear();
|
||||
applyManifest(window.__MAIL_PREVIEW__);
|
||||
switchLocale(state.locale);
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
} else if (msg.type === 'error') {
|
||||
showMessage(msg.message, 'error');
|
||||
showMessage('', 'error').textContent = msg.message;
|
||||
}
|
||||
} catch(_) {}
|
||||
};
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.locale.Tr "mail.team_invite.subject" (.Inviter.DisplayName|DotEscape) (.Organization.DisplayName|DotEscape)}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#0a0815 }
|
||||
.email-wrap { max-width:600px;margin:40px auto;font-family:system-ui,-apple-system,sans-serif }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:block;margin:0 auto 14px;border:0;outline:none;opacity:0.5 }
|
||||
.email-brand { font-size:16px;font-weight:300;color:#ffffff66;letter-spacing:4px;text-transform:uppercase }
|
||||
.email-header { padding:0 0 32px;text-align:center }
|
||||
|
||||
/* Card */
|
||||
.email-card { padding:48px 40px;background:#191432cc;border:1px solid #0d948833;box-shadow:0 0 60px #0d948814,0 0 120px #6366f10a }
|
||||
.email-heading { margin:0 0 20px;font-size:24px;font-weight:300;color:#ccfbf1;line-height:1.3;letter-spacing:-.5px }
|
||||
.email-text { margin:0 0 24px;font-size:14px;line-height:2;color:#d2fcf5e0;font-weight:300 }
|
||||
.email-subtext { margin:0;font-size:13px;line-height:1.8;color:#b4c3d7f2;font-weight:300 }
|
||||
|
||||
/* Divider */
|
||||
.email-divider { height:1px;background:linear-gradient(90deg,transparent,#0d94884d,transparent) }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { padding:40px 40px;text-align:center }
|
||||
.email-btn { display:inline-block;padding:16px 48px;font-size:14px;font-weight:400;color:#e0f5f3!important;text-decoration:none;background:#0d94882e;border:1px solid #0d94884d;letter-spacing:1px }
|
||||
.email-btn-table { margin:0 0 32px }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { border:1px solid #0d94882e;background:#0d948808 }
|
||||
.email-link-label { margin:0 0 8px;font-size:11px;color:#b4fceebf;font-weight:300 }
|
||||
.email-link-url { margin:0;font-size:11px;font-family:monospace;color:#99f6e4;background:#00000033;padding:10px 12px;border:1px solid #0d948826;word-break:break-all }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:32px 0 0;text-align:center }
|
||||
.email-footer-text { margin:0;font-size:11px;color:#a0afc88c;font-weight:300 }
|
||||
.email-footer-link { color:#5eead480;text-decoration:none;font-weight:400 }
|
||||
|
||||
/* Typography utilities */
|
||||
.card-body h1,.card-body h2,.card-body h3,.card-body h4,.card-body h5,.card-body h6 { color:#ccfbf1!important }
|
||||
.card-body a,.card-body a:link,.card-body a:visited { color:#5eead4!important;text-decoration:underline!important }
|
||||
.card-body a:hover,.card-body a:active { color:#99f6e4!important }
|
||||
.card-body li { color:#d2fcf5e0!important }
|
||||
|
||||
/* Responsive */
|
||||
@media only screen and (max-width:480px) {
|
||||
.email-card { padding:32px 20px!important }
|
||||
.email-btn-row { padding:24px 20px!important }
|
||||
.email-heading { font-size:20px!important }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background:#0a0815;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" class="email-wrap">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<tr>
|
||||
<td class="email-header">
|
||||
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="24" height="24" class="email-logo" />
|
||||
<span class="email-brand">{{AppName}}</span>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Card -->
|
||||
<tr>
|
||||
<td class="email-card card-body">
|
||||
|
||||
<p class="email-heading">
|
||||
{{.locale.Tr "mail.hi_user_x" .Invite.Email}}
|
||||
</p>
|
||||
|
||||
<p class="email-text">
|
||||
{{.locale.Tr "mail.team_invite.text_1" (DotEscape .Inviter.DisplayName) (DotEscape .Team.Name) (DotEscape .Organization.DisplayName)}}
|
||||
</p>
|
||||
|
||||
<p class="email-subtext">
|
||||
{{.locale.Tr "mail.team_invite.text_2"}}
|
||||
</p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Divider -->
|
||||
<tr>
|
||||
<td class="email-divider"></td>
|
||||
</tr>
|
||||
|
||||
<!-- Button & Link -->
|
||||
<tr>
|
||||
<td class="email-btn-row">
|
||||
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-table">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{.InviteURL}}" class="email-btn">{{.locale.Tr "mail.view_it_on" AppName}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" class="email-link-box">
|
||||
<tr>
|
||||
<td style="padding:16px 18px;">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{.InviteURL}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p class="email-subtext" style="margin-top:24px;font-size:12px;color:#a0afc880;">
|
||||
{{.locale.Tr "mail.team_invite.text_3" .Invite.Email}}
|
||||
</p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td class="email-footer">
|
||||
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,141 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.Subject}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#0a0815 }
|
||||
.email-wrap { max-width:600px;margin:40px auto;font-family:system-ui,-apple-system,sans-serif }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:block;margin:0 auto 14px;border:0;outline:none;opacity:0.5 }
|
||||
.email-brand { font-size:16px;font-weight:300;color:#ffffff66;letter-spacing:4px;text-transform:uppercase }
|
||||
.email-header { padding:0 0 32px;text-align:center }
|
||||
|
||||
/* Card */
|
||||
.email-card { padding:48px 40px;background:#191432cc;border:1px solid #0d948833;box-shadow:0 0 60px #0d948814,0 0 120px #6366f10a }
|
||||
.email-heading { margin:0 0 20px;font-size:24px;font-weight:300;color:#ccfbf1;line-height:1.3;letter-spacing:-.5px }
|
||||
.email-text { margin:0 0 24px;font-size:14px;line-height:2;color:#d2fcf5e0;font-weight:300 }
|
||||
.email-subtext { margin:0;font-size:13px;line-height:1.8;color:#b4c3d7f2;font-weight:300 }
|
||||
|
||||
/* Divider */
|
||||
.email-divider { height:1px;background:linear-gradient(90deg,transparent,#0d94884d,transparent) }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { padding:40px 40px;text-align:center }
|
||||
.email-btn { display:inline-block;padding:16px 48px;font-size:14px;font-weight:400;color:#e0f5f3!important;text-decoration:none;background:#0d94882e;border:1px solid #0d94884d;letter-spacing:1px }
|
||||
.email-btn-table { margin:0 0 32px }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { border:1px solid #0d94882e;background:#0d948808 }
|
||||
.email-link-label { margin:0 0 8px;font-size:11px;color:#b4fceebf;font-weight:300 }
|
||||
.email-link-url { margin:0;font-size:11px;font-family:monospace;color:#99f6e4;background:#00000033;padding:10px 12px;border:1px solid #0d948826;word-break:break-all }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:32px 0 0;text-align:center }
|
||||
.email-footer-text { margin:0;font-size:11px;color:#a0afc88c;font-weight:300 }
|
||||
.email-footer-link { color:#5eead480;text-decoration:none;font-weight:400 }
|
||||
|
||||
/* Typography utilities */
|
||||
.card-body h1,.card-body h2,.card-body h3,.card-body h4,.card-body h5,.card-body h6 { color:#ccfbf1!important }
|
||||
.card-body a,.card-body a:link,.card-body a:visited { color:#5eead4!important;text-decoration:underline!important }
|
||||
.card-body a:hover,.card-body a:active { color:#99f6e4!important }
|
||||
.card-body li { color:#d2fcf5e0!important }
|
||||
|
||||
/* Responsive */
|
||||
@media only screen and (max-width:480px) {
|
||||
.email-card { padding:32px 20px!important }
|
||||
.email-btn-row { padding:24px 20px!important }
|
||||
.email-heading { font-size:20px!important }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background:#0a0815;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" class="email-wrap">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<tr>
|
||||
<td class="email-header">
|
||||
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="24" height="24" class="email-logo" />
|
||||
<span class="email-brand">{{AppName}}</span>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Card -->
|
||||
<tr>
|
||||
<td class="email-card card-body">
|
||||
|
||||
<p class="email-heading"></p>
|
||||
|
||||
<p class="email-text">
|
||||
<h2>{{.Repo.FullName}} - {{.Run.WorkflowID}}: {{.RunStatusText}}</h2>
|
||||
<ul>
|
||||
{{range $job := .Jobs}}
|
||||
<li>
|
||||
<a href="{{$job.HTMLURL}}">{{$job.Status}}: {{$job.Name}}{{if gt $job.Attempt 1}} (retry #{{$job.Attempt}}){{end}} - {{$job.Duration}}</a>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<p class="email-subtext"></p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Divider -->
|
||||
<tr>
|
||||
<td class="email-divider"></td>
|
||||
</tr>
|
||||
|
||||
<!-- Button & Link -->
|
||||
<tr>
|
||||
<td class="email-btn-row">
|
||||
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-table">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{.Run.HTMLURL}}" class="email-btn">{{.locale.Tr "mail.view_it_on" AppName}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" class="email-link-box">
|
||||
<tr>
|
||||
<td style="padding:16px 18px;">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{.Run.HTMLURL}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td class="email-footer">
|
||||
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,134 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.Subject}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#0a0815 }
|
||||
.email-wrap { max-width:600px;margin:40px auto;font-family:system-ui,-apple-system,sans-serif }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:block;margin:0 auto 14px;border:0;outline:none;opacity:0.5 }
|
||||
.email-brand { font-size:16px;font-weight:300;color:#ffffff66;letter-spacing:4px;text-transform:uppercase }
|
||||
.email-header { padding:0 0 32px;text-align:center }
|
||||
|
||||
/* Card */
|
||||
.email-card { padding:48px 40px;background:#191432cc;border:1px solid #0d948833;box-shadow:0 0 60px #0d948814,0 0 120px #6366f10a }
|
||||
.email-heading { margin:0 0 20px;font-size:24px;font-weight:300;color:#ccfbf1;line-height:1.3;letter-spacing:-.5px }
|
||||
.email-text { margin:0 0 24px;font-size:14px;line-height:2;color:#d2fcf5e0;font-weight:300 }
|
||||
.email-subtext { margin:0;font-size:13px;line-height:1.8;color:#b4c3d7f2;font-weight:300 }
|
||||
|
||||
/* Divider */
|
||||
.email-divider { height:1px;background:linear-gradient(90deg,transparent,#0d94884d,transparent) }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { padding:40px 40px;text-align:center }
|
||||
.email-btn { display:inline-block;padding:16px 48px;font-size:14px;font-weight:400;color:#e0f5f3!important;text-decoration:none;background:#0d94882e;border:1px solid #0d94884d;letter-spacing:1px }
|
||||
.email-btn-table { margin:0 0 32px }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { border:1px solid #0d94882e;background:#0d948808 }
|
||||
.email-link-label { margin:0 0 8px;font-size:11px;color:#b4fceebf;font-weight:300 }
|
||||
.email-link-url { margin:0;font-size:11px;font-family:monospace;color:#99f6e4;background:#00000033;padding:10px 12px;border:1px solid #0d948826;word-break:break-all }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:32px 0 0;text-align:center }
|
||||
.email-footer-text { margin:0;font-size:11px;color:#a0afc88c;font-weight:300 }
|
||||
.email-footer-link { color:#5eead480;text-decoration:none;font-weight:400 }
|
||||
|
||||
/* Typography utilities */
|
||||
.card-body h1,.card-body h2,.card-body h3,.card-body h4,.card-body h5,.card-body h6 { color:#ccfbf1!important }
|
||||
.card-body a,.card-body a:link,.card-body a:visited { color:#5eead4!important;text-decoration:underline!important }
|
||||
.card-body a:hover,.card-body a:active { color:#99f6e4!important }
|
||||
.card-body li { color:#d2fcf5e0!important }
|
||||
|
||||
/* Responsive */
|
||||
@media only screen and (max-width:480px) {
|
||||
.email-card { padding:32px 20px!important }
|
||||
.email-btn-row { padding:24px 20px!important }
|
||||
.email-heading { font-size:20px!important }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background:#0a0815;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" class="email-wrap">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<tr>
|
||||
<td class="email-header">
|
||||
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="24" height="24" class="email-logo" />
|
||||
<span class="email-brand">{{AppName}}</span>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Card -->
|
||||
<tr>
|
||||
<td class="email-card card-body">
|
||||
|
||||
<p class="email-heading"></p>
|
||||
|
||||
<p class="email-text">
|
||||
{{.locale.Tr "mail.repo.collaborator.added.text"}} <code>{{.RepoName}}</code>.
|
||||
</p>
|
||||
|
||||
<p class="email-subtext"></p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Divider -->
|
||||
<tr>
|
||||
<td class="email-divider"></td>
|
||||
</tr>
|
||||
|
||||
<!-- Button & Link -->
|
||||
<tr>
|
||||
<td class="email-btn-row">
|
||||
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-table">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{.Link}}" class="email-btn">{{.locale.Tr "mail.view_it_on" AppName}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" class="email-link-box">
|
||||
<tr>
|
||||
<td style="padding:16px 18px;">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{.Link}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td class="email-footer">
|
||||
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,140 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.Subject}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#0a0815 }
|
||||
.email-wrap { max-width:600px;margin:40px auto;font-family:system-ui,-apple-system,sans-serif }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:block;margin:0 auto 14px;border:0;outline:none;opacity:0.5 }
|
||||
.email-brand { font-size:16px;font-weight:300;color:#ffffff66;letter-spacing:4px;text-transform:uppercase }
|
||||
.email-header { padding:0 0 32px;text-align:center }
|
||||
|
||||
/* Card */
|
||||
.email-card { padding:48px 40px;background:#191432cc;border:1px solid #0d948833;box-shadow:0 0 60px #0d948814,0 0 120px #6366f10a }
|
||||
.email-heading { margin:0 0 20px;font-size:24px;font-weight:300;color:#ccfbf1;line-height:1.3;letter-spacing:-.5px }
|
||||
.email-text { margin:0 0 24px;font-size:14px;line-height:2;color:#d2fcf5e0;font-weight:300 }
|
||||
.email-subtext { margin:0;font-size:13px;line-height:1.8;color:#b4c3d7f2;font-weight:300 }
|
||||
|
||||
/* Divider */
|
||||
.email-divider { height:1px;background:linear-gradient(90deg,transparent,#0d94884d,transparent) }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { padding:40px 40px;text-align:center }
|
||||
.email-btn { display:inline-block;padding:16px 48px;font-size:14px;font-weight:400;color:#e0f5f3!important;text-decoration:none;background:#0d94882e;border:1px solid #0d94884d;letter-spacing:1px }
|
||||
.email-btn-table { margin:0 0 32px }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { border:1px solid #0d94882e;background:#0d948808 }
|
||||
.email-link-label { margin:0 0 8px;font-size:11px;color:#b4fceebf;font-weight:300 }
|
||||
.email-link-url { margin:0;font-size:11px;font-family:monospace;color:#99f6e4;background:#00000033;padding:10px 12px;border:1px solid #0d948826;word-break:break-all }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:32px 0 0;text-align:center }
|
||||
.email-footer-text { margin:0;font-size:11px;color:#a0afc88c;font-weight:300 }
|
||||
.email-footer-link { color:#5eead480;text-decoration:none;font-weight:400 }
|
||||
|
||||
/* Typography utilities */
|
||||
.card-body h1,.card-body h2,.card-body h3,.card-body h4,.card-body h5,.card-body h6 { color:#ccfbf1!important }
|
||||
.card-body a,.card-body a:link,.card-body a:visited { color:#5eead4!important;text-decoration:underline!important }
|
||||
.card-body a:hover,.card-body a:active { color:#99f6e4!important }
|
||||
.card-body li { color:#d2fcf5e0!important }
|
||||
|
||||
/* Responsive */
|
||||
@media only screen and (max-width:480px) {
|
||||
.email-card { padding:32px 20px!important }
|
||||
.email-btn-row { padding:24px 20px!important }
|
||||
.email-heading { font-size:20px!important }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background:#0a0815;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" class="email-wrap">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<tr>
|
||||
<td class="email-header">
|
||||
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="24" height="24" class="email-logo" />
|
||||
<span class="email-brand">{{AppName}}</span>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Card -->
|
||||
<tr>
|
||||
<td class="email-card card-body">
|
||||
|
||||
<p class="email-heading"></p>
|
||||
|
||||
<p class="email-text">
|
||||
{{$repo_url := HTMLFormat "<a href=\047%s\047>%s</a>" .Issue.Repo.HTMLURL .Issue.Repo.FullName}}
|
||||
{{$link := HTMLFormat "<a href=\047%s\047>#%d</a>" .Link .Issue.Index}}
|
||||
{{if .IsPull}}
|
||||
{{.locale.Tr "mail.issue_assigned.pull" .Doer.Name $link $repo_url}}
|
||||
{{else}}
|
||||
{{.locale.Tr "mail.issue_assigned.issue" .Doer.Name $link $repo_url}}
|
||||
{{end}}
|
||||
</p>
|
||||
|
||||
<p class="email-subtext"></p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Divider -->
|
||||
<tr>
|
||||
<td class="email-divider"></td>
|
||||
</tr>
|
||||
|
||||
<!-- Button & Link -->
|
||||
<tr>
|
||||
<td class="email-btn-row">
|
||||
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-table">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{.Link}}" class="email-btn">{{.locale.Tr "mail.view_it_on" AppName}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" class="email-link-box">
|
||||
<tr>
|
||||
<td style="padding:16px 18px;">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{.Link}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td class="email-footer">
|
||||
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,209 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.Subject}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#0a0815 }
|
||||
.email-wrap { max-width:600px;margin:40px auto;font-family:system-ui,-apple-system,sans-serif }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:block;margin:0 auto 14px;border:0;outline:none;opacity:0.5 }
|
||||
.email-brand { font-size:16px;font-weight:300;color:#ffffff66;letter-spacing:4px;text-transform:uppercase }
|
||||
.email-header { padding:0 0 32px;text-align:center }
|
||||
|
||||
/* Card */
|
||||
.email-card { padding:48px 40px;background:#191432cc;border:1px solid #0d948833;box-shadow:0 0 60px #0d948814,0 0 120px #6366f10a }
|
||||
.email-heading { margin:0 0 20px;font-size:24px;font-weight:300;color:#ccfbf1;line-height:1.3;letter-spacing:-.5px }
|
||||
.email-text { margin:0 0 24px;font-size:14px;line-height:2;color:#d2fcf5e0;font-weight:300 }
|
||||
.email-subtext { margin:0;font-size:13px;line-height:1.8;color:#b4c3d7f2;font-weight:300 }
|
||||
|
||||
/* Divider */
|
||||
.email-divider { height:1px;background:linear-gradient(90deg,transparent,#0d94884d,transparent) }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { padding:40px 40px;text-align:center }
|
||||
.email-btn { display:inline-block;padding:16px 48px;font-size:14px;font-weight:400;color:#e0f5f3!important;text-decoration:none;background:#0d94882e;border:1px solid #0d94884d;letter-spacing:1px }
|
||||
.email-btn-table { margin:0 0 32px }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { border:1px solid #0d94882e;background:#0d948808 }
|
||||
.email-link-label { margin:0 0 8px;font-size:11px;color:#b4fceebf;font-weight:300 }
|
||||
.email-link-url { margin:0;font-size:11px;font-family:monospace;color:#99f6e4;background:#00000033;padding:10px 12px;border:1px solid #0d948826;word-break:break-all }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:32px 0 0;text-align:center }
|
||||
.email-footer-text { margin:0;font-size:11px;color:#a0afc88c;font-weight:300 }
|
||||
.email-footer-link { color:#5eead480;text-decoration:none;font-weight:400 }
|
||||
|
||||
/* Typography utilities */
|
||||
.card-body h1,.card-body h2,.card-body h3,.card-body h4,.card-body h5,.card-body h6 { color:#ccfbf1!important }
|
||||
.card-body a,.card-body a:link,.card-body a:visited { color:#5eead4!important;text-decoration:underline!important }
|
||||
.card-body a:hover,.card-body a:active { color:#99f6e4!important }
|
||||
.card-body li { color:#d2fcf5e0!important }
|
||||
|
||||
/* Content */
|
||||
.aurora-content,.aurora-content * { color:#d2fcf5e0!important }
|
||||
.aurora-content a,.aurora-content a:link,.aurora-content a:visited { color:#5eead4!important;text-decoration:underline!important }
|
||||
.aurora-content a:hover,.aurora-content a:active { color:#99f6e4!important }
|
||||
.aurora-content code,.aurora-content pre { color:#99f6e4!important;background:#0000004d!important;border-color:#0d948833!important }
|
||||
.aurora-content blockquote { color:#ffffff99!important }
|
||||
.aurora-content h1,.aurora-content h2,.aurora-content h3,.aurora-content h4,.aurora-content h5,.aurora-content h6 { color:#ccfbf1!important }
|
||||
.aurora-content hr { border-color:#0d94884d!important }
|
||||
.aurora-content img { border:0;max-width:100%!important }
|
||||
|
||||
/* Responsive */
|
||||
@media only screen and (max-width:480px) {
|
||||
.email-card { padding:32px 20px!important }
|
||||
.email-btn-row { padding:24px 20px!important }
|
||||
.email-heading { font-size:20px!important }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background:#0a0815;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" class="email-wrap">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<tr>
|
||||
<td class="email-header">
|
||||
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="24" height="24" class="email-logo" />
|
||||
<span class="email-brand">{{AppName}}</span>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Card -->
|
||||
<tr>
|
||||
<td class="email-card card-body">
|
||||
|
||||
<p class="email-heading"></p>
|
||||
|
||||
<p class="email-text">
|
||||
{{if .IsMention}}
|
||||
<p>{{.locale.Tr "mail.issue.x_mentioned_you" .Doer.Name}}</p>
|
||||
{{end}}
|
||||
|
||||
{{if eq .ActionName "push"}}
|
||||
<p>
|
||||
{{if .Comment.IsForcePush}}
|
||||
{{$oldCommitUrl := printf "%s/commit/%s" .Comment.Issue.PullRequest.BaseRepo.HTMLURL .Comment.OldCommit}}
|
||||
{{$oldShortSha := ShortSha .Comment.OldCommit}}
|
||||
{{$oldCommitLink := HTMLFormat "<a href=\047%[1]s\047>%[2]s</a>" $oldCommitUrl $oldShortSha}}
|
||||
{{$newCommitUrl := printf "%s/commit/%s" .Comment.Issue.PullRequest.BaseRepo.HTMLURL .Comment.NewCommit}}
|
||||
{{$newShortSha := ShortSha .Comment.NewCommit}}
|
||||
{{$newCommitLink := HTMLFormat "<a href=\047%[1]s\047>%[2]s</a>" $newCommitUrl $newShortSha}}
|
||||
{{.locale.Tr "mail.issue.action.force_push" .Doer.Name .Comment.Issue.PullRequest.HeadBranch $oldCommitLink $newCommitLink}}
|
||||
{{else}}
|
||||
{{.locale.TrN (len .Comment.Commits) "mail.issue.action.push_1" "mail.issue.action.push_n" .Doer.Name .Comment.Issue.PullRequest.HeadBranch (len .Comment.Commits)}}
|
||||
{{end}}
|
||||
</p>
|
||||
{{end}}
|
||||
|
||||
<p>
|
||||
{{if eq .ActionName "close"}}
|
||||
{{.locale.Tr "mail.issue.action.close" .Doer.Name .Issue.Index}}
|
||||
{{else if eq .ActionName "reopen"}}
|
||||
{{.locale.Tr "mail.issue.action.reopen" .Doer.Name .Issue.Index}}
|
||||
{{else if eq .ActionName "merge"}}
|
||||
{{.locale.Tr "mail.issue.action.merge" .Doer.Name .Issue.Index .Issue.PullRequest.BaseBranch}}
|
||||
{{else if eq .ActionName "approve"}}
|
||||
{{.locale.Tr "mail.issue.action.approve" .Doer.Name}}
|
||||
{{else if eq .ActionName "reject"}}
|
||||
{{.locale.Tr "mail.issue.action.reject" .Doer.Name}}
|
||||
{{else if eq .ActionName "review"}}
|
||||
{{.locale.Tr "mail.issue.action.review" .Doer.Name}}
|
||||
{{else if eq .ActionName "review_dismissed"}}
|
||||
{{.locale.Tr "mail.issue.action.review_dismissed" .Doer.Name .Comment.Review.Reviewer.Name}}
|
||||
{{else if eq .ActionName "ready_for_review"}}
|
||||
{{.locale.Tr "mail.issue.action.ready_for_review" .Doer.Name}}
|
||||
{{end}}
|
||||
{{- if eq .Body ""}}
|
||||
{{if eq .ActionName "new"}}
|
||||
{{.locale.Tr "mail.issue.action.new" .Doer.Name .Issue.Index}}
|
||||
{{end}}
|
||||
{{else}}
|
||||
<span class="aurora-content">{{.Body}}</span>
|
||||
{{end -}}
|
||||
{{if .CanReply}}
|
||||
{{.locale.Tr "mail.reply"}}
|
||||
{{end}}
|
||||
</p>
|
||||
|
||||
{{- range .ReviewComments}}
|
||||
<hr>
|
||||
<p>{{$.locale.Tr "mail.issue.in_tree_path" .TreePath}}</p>
|
||||
<pre>{{.Patch}}</pre>
|
||||
<div class="aurora-content">{{.RenderedContent}}</div>
|
||||
{{end -}}
|
||||
|
||||
{{if eq .ActionName "push"}}
|
||||
<ul>
|
||||
{{range .Comment.Commits}}
|
||||
<li>
|
||||
<a href="{{$.Comment.Issue.PullRequest.BaseRepo.HTMLURL}}/commit/{{.UserCommit.GitCommit.ID}}">{{ShortSha .UserCommit.GitCommit.ID.String}}</a> - {{.UserCommit.GitCommit.MessageTitle}}
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
</p>
|
||||
|
||||
<p class="email-subtext"></p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Divider -->
|
||||
<tr>
|
||||
<td class="email-divider"></td>
|
||||
</tr>
|
||||
|
||||
<!-- Button & Link -->
|
||||
<tr>
|
||||
<td class="email-btn-row">
|
||||
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-table">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{.Link}}" class="email-btn">{{.locale.Tr "mail.view_it_on" AppName}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" class="email-link-box">
|
||||
<tr>
|
||||
<td style="padding:16px 18px;">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{.Link}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td class="email-footer">
|
||||
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,166 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.Subject}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#0a0815 }
|
||||
.email-wrap { max-width:600px;margin:40px auto;font-family:system-ui,-apple-system,sans-serif }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:block;margin:0 auto 14px;border:0;outline:none;opacity:0.5 }
|
||||
.email-brand { font-size:16px;font-weight:300;color:#ffffff66;letter-spacing:4px;text-transform:uppercase }
|
||||
.email-header { padding:0 0 32px;text-align:center }
|
||||
|
||||
/* Card */
|
||||
.email-card { padding:48px 40px;background:#191432cc;border:1px solid #0d948833;box-shadow:0 0 60px #0d948814,0 0 120px #6366f10a }
|
||||
.email-heading { margin:0 0 20px;font-size:24px;font-weight:300;color:#ccfbf1;line-height:1.3;letter-spacing:-.5px }
|
||||
.email-text { margin:0 0 24px;font-size:14px;line-height:2;color:#d2fcf5e0;font-weight:300 }
|
||||
.email-subtext { margin:0;font-size:13px;line-height:1.8;color:#b4c3d7f2;font-weight:300 }
|
||||
|
||||
/* Divider */
|
||||
.email-divider { height:1px;background:linear-gradient(90deg,transparent,#0d94884d,transparent) }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { padding:40px 40px;text-align:center }
|
||||
.email-btn { display:inline-block;padding:16px 48px;font-size:14px;font-weight:400;color:#e0f5f3!important;text-decoration:none;background:#0d94882e;border:1px solid #0d94884d;letter-spacing:1px }
|
||||
.email-btn-table { margin:0 0 32px }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { border:1px solid #0d94882e;background:#0d948808 }
|
||||
.email-link-label { margin:0 0 8px;font-size:11px;color:#b4fceebf;font-weight:300 }
|
||||
.email-link-url { margin:0;font-size:11px;font-family:monospace;color:#99f6e4;background:#00000033;padding:10px 12px;border:1px solid #0d948826;word-break:break-all }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:32px 0 0;text-align:center }
|
||||
.email-footer-text { margin:0;font-size:11px;color:#a0afc88c;font-weight:300 }
|
||||
.email-footer-link { color:#5eead480;text-decoration:none;font-weight:400 }
|
||||
|
||||
/* Typography utilities */
|
||||
.card-body h1,.card-body h2,.card-body h3,.card-body h4,.card-body h5,.card-body h6 { color:#ccfbf1!important }
|
||||
.card-body a,.card-body a:link,.card-body a:visited { color:#5eead4!important;text-decoration:underline!important }
|
||||
.card-body a:hover,.card-body a:active { color:#99f6e4!important }
|
||||
.card-body li { color:#d2fcf5e0!important }
|
||||
|
||||
/* Content */
|
||||
.aurora-content,.aurora-content * { color:#d2fcf5e0!important }
|
||||
.aurora-content a,.aurora-content a:link,.aurora-content a:visited { color:#5eead4!important;text-decoration:underline!important }
|
||||
.aurora-content a:hover,.aurora-content a:active { color:#99f6e4!important }
|
||||
.aurora-content code,.aurora-content pre { color:#99f6e4!important;background:#0000004d!important;border-color:#0d948833!important }
|
||||
.aurora-content blockquote { color:#ffffff99!important }
|
||||
.aurora-content h1,.aurora-content h2,.aurora-content h3,.aurora-content h4,.aurora-content h5,.aurora-content h6 { color:#ccfbf1!important }
|
||||
.aurora-content hr { border-color:#0d94884d!important }
|
||||
.aurora-content img { border:0;max-width:100%!important }
|
||||
|
||||
/* Responsive */
|
||||
@media only screen and (max-width:480px) {
|
||||
.email-card { padding:32px 20px!important }
|
||||
.email-btn-row { padding:24px 20px!important }
|
||||
.email-heading { font-size:20px!important }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background:#0a0815;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" class="email-wrap">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<tr>
|
||||
<td class="email-header">
|
||||
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="24" height="24" class="email-logo" />
|
||||
<span class="email-brand">{{AppName}}</span>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Card -->
|
||||
<tr>
|
||||
<td class="email-card card-body">
|
||||
|
||||
<p class="email-heading"></p>
|
||||
|
||||
<p class="email-text">
|
||||
{{$release_url := HTMLFormat "<a href=\047%s\047>%s</a>" .Release.HTMLURL .Release.TagName}}
|
||||
{{$repo_url := HTMLFormat "<a href=\047%s\047>%s</a>" .Release.Repo.HTMLURL .Release.Repo.FullName}}
|
||||
{{.locale.Tr "mail.release.new.text" .Release.Publisher.Name $release_url $repo_url}}
|
||||
|
||||
<h4>{{.locale.Tr "mail.release.title" .Release.Title}}</h4>
|
||||
|
||||
{{if .Release.RenderedNote}}
|
||||
<p class="email-text">{{.locale.Tr "mail.release.note"}}</p>
|
||||
<div class="aurora-content" style="font-size:14px;line-height:2;">{{.Release.RenderedNote}}</div>
|
||||
{{end}}
|
||||
|
||||
<p class="email-text">{{.locale.Tr "mail.release.downloads"}}</p>
|
||||
<ul>
|
||||
{{if not .DisableDownloadSourceArchives}}
|
||||
<li><a href="{{.Release.Repo.HTMLURL}}/archive/{{.Release.TagName | PathEscapeSegments}}.zip">{{.locale.Tr "mail.release.download.zip"}}</a></li>
|
||||
<li><a href="{{.Release.Repo.HTMLURL}}/archive/{{.Release.TagName | PathEscapeSegments}}.tar.gz">{{.locale.Tr "mail.release.download.targz"}}</a></li>
|
||||
{{end}}
|
||||
{{if .Release.Attachments}}
|
||||
{{range .Release.Attachments}}
|
||||
<li><a href="{{.DownloadURL}}">{{.Name}} ({{.Size | FileSize}})</a></li>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<p class="email-subtext"></p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Divider -->
|
||||
<tr>
|
||||
<td class="email-divider"></td>
|
||||
</tr>
|
||||
|
||||
<!-- Button & Link -->
|
||||
<tr>
|
||||
<td class="email-btn-row">
|
||||
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-table">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{.Link}}" class="email-btn">{{.locale.Tr "mail.view_it_on" AppName}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" class="email-link-box">
|
||||
<tr>
|
||||
<td style="padding:16px 18px;">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{.Link}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td class="email-footer">
|
||||
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,135 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.Subject}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#0a0815 }
|
||||
.email-wrap { max-width:600px;margin:40px auto;font-family:system-ui,-apple-system,sans-serif }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:block;margin:0 auto 14px;border:0;outline:none;opacity:0.5 }
|
||||
.email-brand { font-size:16px;font-weight:300;color:#ffffff66;letter-spacing:4px;text-transform:uppercase }
|
||||
.email-header { padding:0 0 32px;text-align:center }
|
||||
|
||||
/* Card */
|
||||
.email-card { padding:48px 40px;background:#191432cc;border:1px solid #0d948833;box-shadow:0 0 60px #0d948814,0 0 120px #6366f10a }
|
||||
.email-heading { margin:0 0 20px;font-size:24px;font-weight:300;color:#ccfbf1;line-height:1.3;letter-spacing:-.5px }
|
||||
.email-text { margin:0 0 24px;font-size:14px;line-height:2;color:#d2fcf5e0;font-weight:300 }
|
||||
.email-subtext { margin:0;font-size:13px;line-height:1.8;color:#b4c3d7f2;font-weight:300 }
|
||||
|
||||
/* Divider */
|
||||
.email-divider { height:1px;background:linear-gradient(90deg,transparent,#0d94884d,transparent) }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { padding:40px 40px;text-align:center }
|
||||
.email-btn { display:inline-block;padding:16px 48px;font-size:14px;font-weight:400;color:#e0f5f3!important;text-decoration:none;background:#0d94882e;border:1px solid #0d94884d;letter-spacing:1px }
|
||||
.email-btn-table { margin:0 0 32px }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { border:1px solid #0d94882e;background:#0d948808 }
|
||||
.email-link-label { margin:0 0 8px;font-size:11px;color:#b4fceebf;font-weight:300 }
|
||||
.email-link-url { margin:0;font-size:11px;font-family:monospace;color:#99f6e4;background:#00000033;padding:10px 12px;border:1px solid #0d948826;word-break:break-all }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:32px 0 0;text-align:center }
|
||||
.email-footer-text { margin:0;font-size:11px;color:#a0afc88c;font-weight:300 }
|
||||
.email-footer-link { color:#5eead480;text-decoration:none;font-weight:400 }
|
||||
|
||||
/* Typography utilities */
|
||||
.card-body h1,.card-body h2,.card-body h3,.card-body h4,.card-body h5,.card-body h6 { color:#ccfbf1!important }
|
||||
.card-body a,.card-body a:link,.card-body a:visited { color:#5eead4!important;text-decoration:underline!important }
|
||||
.card-body a:hover,.card-body a:active { color:#99f6e4!important }
|
||||
.card-body li { color:#d2fcf5e0!important }
|
||||
|
||||
/* Responsive */
|
||||
@media only screen and (max-width:480px) {
|
||||
.email-card { padding:32px 20px!important }
|
||||
.email-btn-row { padding:24px 20px!important }
|
||||
.email-heading { font-size:20px!important }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background:#0a0815;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" class="email-wrap">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<tr>
|
||||
<td class="email-header">
|
||||
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="24" height="24" class="email-logo" />
|
||||
<span class="email-brand">{{AppName}}</span>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Card -->
|
||||
<tr>
|
||||
<td class="email-card card-body">
|
||||
|
||||
<p class="email-heading"></p>
|
||||
|
||||
<p class="email-text">
|
||||
{{$url := HTMLFormat "<a href=\047%[1]s\047>%[2]s</a>" .Link .Repo}}
|
||||
{{.Subject}}. {{.locale.Tr "mail.repo.transfer.body" $url}}
|
||||
</p>
|
||||
|
||||
<p class="email-subtext"></p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Divider -->
|
||||
<tr>
|
||||
<td class="email-divider"></td>
|
||||
</tr>
|
||||
|
||||
<!-- Button & Link -->
|
||||
<tr>
|
||||
<td class="email-btn-row">
|
||||
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-table">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{.Link}}" class="email-btn">{{.locale.Tr "mail.view_it_on" AppName}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" class="email-link-box">
|
||||
<tr>
|
||||
<td style="padding:16px 18px;">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{.Link}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td class="email-footer">
|
||||
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,136 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.locale.Tr "mail.activate_account.title" (.DisplayName|DotEscape)}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#0a0815 }
|
||||
.email-wrap { max-width:600px;margin:40px auto;font-family:system-ui,-apple-system,sans-serif }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:block;margin:0 auto 14px;border:0;outline:none;opacity:0.5 }
|
||||
.email-brand { font-size:16px;font-weight:300;color:#ffffff66;letter-spacing:4px;text-transform:uppercase }
|
||||
.email-header { padding:0 0 32px;text-align:center }
|
||||
|
||||
/* Card */
|
||||
.email-card { padding:48px 40px;background:#191432cc;border:1px solid #0d948833;box-shadow:0 0 60px #0d948814,0 0 120px #6366f10a }
|
||||
.email-heading { margin:0 0 20px;font-size:24px;font-weight:300;color:#ccfbf1;line-height:1.3;letter-spacing:-.5px }
|
||||
.email-text { margin:0 0 24px;font-size:14px;line-height:2;color:#d2fcf5e0;font-weight:300 }
|
||||
.email-subtext { margin:0;font-size:13px;line-height:1.8;color:#b4c3d7f2;font-weight:300 }
|
||||
|
||||
/* Divider */
|
||||
.email-divider { height:1px;background:linear-gradient(90deg,transparent,#0d94884d,transparent) }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { padding:40px 40px;text-align:center }
|
||||
.email-btn { display:inline-block;padding:16px 48px;font-size:14px;font-weight:400;color:#e0f5f3!important;text-decoration:none;background:#0d94882e;border:1px solid #0d94884d;letter-spacing:1px }
|
||||
.email-btn-table { margin:0 0 32px }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { border:1px solid #0d94882e;background:#0d948808 }
|
||||
.email-link-label { margin:0 0 8px;font-size:11px;color:#b4fceebf;font-weight:300 }
|
||||
.email-link-url { margin:0;font-size:11px;font-family:monospace;color:#99f6e4;background:#00000033;padding:10px 12px;border:1px solid #0d948826;word-break:break-all }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:32px 0 0;text-align:center }
|
||||
.email-footer-text { margin:0;font-size:11px;color:#a0afc88c;font-weight:300 }
|
||||
.email-footer-link { color:#5eead480;text-decoration:none;font-weight:400 }
|
||||
|
||||
/* Typography utilities */
|
||||
.card-body h1,.card-body h2,.card-body h3,.card-body h4,.card-body h5,.card-body h6 { color:#ccfbf1!important }
|
||||
.card-body a,.card-body a:link,.card-body a:visited { color:#5eead4!important;text-decoration:underline!important }
|
||||
.card-body a:hover,.card-body a:active { color:#99f6e4!important }
|
||||
.card-body li { color:#d2fcf5e0!important }
|
||||
|
||||
/* Responsive */
|
||||
@media only screen and (max-width:480px) {
|
||||
.email-card { padding:32px 20px!important }
|
||||
.email-btn-row { padding:24px 20px!important }
|
||||
.email-heading { font-size:20px!important }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background:#0a0815;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" class="email-wrap">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<tr>
|
||||
<td class="email-header">
|
||||
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="24" height="24" class="email-logo" />
|
||||
<span class="email-brand">{{AppName}}</span>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Card -->
|
||||
<tr>
|
||||
<td class="email-card card-body">
|
||||
|
||||
<p class="email-text">
|
||||
{{.locale.Tr "mail.activate_account.text_1" (.DisplayName|DotEscape) AppName}}
|
||||
</p>
|
||||
|
||||
<p class="email-subtext">
|
||||
{{.locale.Tr "mail.activate_account.text_2" .ActiveCodeLives}}
|
||||
</p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Divider -->
|
||||
<tr>
|
||||
<td class="email-divider"></td>
|
||||
</tr>
|
||||
|
||||
<!-- Button & Link -->
|
||||
<tr>
|
||||
<td class="email-btn-row">
|
||||
|
||||
{{$activate_url := printf "%suser/activate?code=%s" AppUrl (QueryEscape .Code)}}
|
||||
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-table">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{$activate_url}}" class="email-btn">{{.locale.Tr "mail.activate_account"}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" class="email-link-box">
|
||||
<tr>
|
||||
<td style="padding:16px 18px;">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{$activate_url}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td class="email-footer">
|
||||
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,138 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.locale.Tr "mail.activate_email.title" (.DisplayName|DotEscape)}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#0a0815 }
|
||||
.email-wrap { max-width:600px;margin:40px auto;font-family:system-ui,-apple-system,sans-serif }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:block;margin:0 auto 14px;border:0;outline:none;opacity:0.5 }
|
||||
.email-brand { font-size:16px;font-weight:300;color:#ffffff66;letter-spacing:4px;text-transform:uppercase }
|
||||
.email-header { padding:0 0 32px;text-align:center }
|
||||
|
||||
/* Card */
|
||||
.email-card { padding:48px 40px;background:#191432cc;border:1px solid #0d948833;box-shadow:0 0 60px #0d948814,0 0 120px #6366f10a }
|
||||
.email-heading { margin:0 0 20px;font-size:24px;font-weight:300;color:#ccfbf1;line-height:1.3;letter-spacing:-.5px }
|
||||
.email-text { margin:0 0 24px;font-size:14px;line-height:2;color:#d2fcf5e0;font-weight:300 }
|
||||
.email-subtext { margin:0;font-size:13px;line-height:1.8;color:#b4c3d7f2;font-weight:300 }
|
||||
|
||||
/* Divider */
|
||||
.email-divider { height:1px;background:linear-gradient(90deg,transparent,#0d94884d,transparent) }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { padding:40px 40px;text-align:center }
|
||||
.email-btn { display:inline-block;padding:16px 48px;font-size:14px;font-weight:400;color:#e0f5f3!important;text-decoration:none;background:#0d94882e;border:1px solid #0d94884d;letter-spacing:1px }
|
||||
.email-btn-table { margin:0 0 32px }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { border:1px solid #0d94882e;background:#0d948808 }
|
||||
.email-link-label { margin:0 0 8px;font-size:11px;color:#b4fceebf;font-weight:300 }
|
||||
.email-link-url { margin:0;font-size:11px;font-family:monospace;color:#99f6e4;background:#00000033;padding:10px 12px;border:1px solid #0d948826;word-break:break-all }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:32px 0 0;text-align:center }
|
||||
.email-footer-text { margin:0;font-size:11px;color:#a0afc88c;font-weight:300 }
|
||||
.email-footer-link { color:#5eead480;text-decoration:none;font-weight:400 }
|
||||
|
||||
/* Typography utilities */
|
||||
.card-body h1,.card-body h2,.card-body h3,.card-body h4,.card-body h5,.card-body h6 { color:#ccfbf1!important }
|
||||
.card-body a,.card-body a:link,.card-body a:visited { color:#5eead4!important;text-decoration:underline!important }
|
||||
.card-body a:hover,.card-body a:active { color:#99f6e4!important }
|
||||
.card-body li { color:#d2fcf5e0!important }
|
||||
|
||||
/* Responsive */
|
||||
@media only screen and (max-width:480px) {
|
||||
.email-card { padding:32px 20px!important }
|
||||
.email-btn-row { padding:24px 20px!important }
|
||||
.email-heading { font-size:20px!important }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background:#0a0815;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" class="email-wrap">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<tr>
|
||||
<td class="email-header">
|
||||
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="24" height="24" class="email-logo" />
|
||||
<span class="email-brand">{{AppName}}</span>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Card -->
|
||||
<tr>
|
||||
<td class="email-card card-body">
|
||||
|
||||
<p class="email-heading">
|
||||
{{.locale.Tr "mail.hi_user_x" (.DisplayName|DotEscape)}}
|
||||
</p>
|
||||
|
||||
<p class="email-text">
|
||||
{{.locale.Tr "mail.activate_email.text" .ActiveCodeLives}}
|
||||
</p>
|
||||
|
||||
<p class="email-subtext"></p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Divider -->
|
||||
<tr>
|
||||
<td class="email-divider"></td>
|
||||
</tr>
|
||||
|
||||
<!-- Button & Link -->
|
||||
<tr>
|
||||
<td class="email-btn-row">
|
||||
|
||||
{{$activate_url := printf "%suser/activate_email?code=%s&email=%s" AppUrl (QueryEscape .Code) (QueryEscape .Email)}}
|
||||
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-table">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{$activate_url}}" class="email-btn">{{.locale.Tr "mail.activate_email"}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" class="email-link-box">
|
||||
<tr>
|
||||
<td style="padding:16px 18px;">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{$activate_url}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td class="email-footer">
|
||||
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,140 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.locale.Tr "mail.register_notify.title" (.DisplayName|DotEscape) AppName}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#0a0815 }
|
||||
.email-wrap { max-width:600px;margin:40px auto;font-family:system-ui,-apple-system,sans-serif }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:block;margin:0 auto 14px;border:0;outline:none;opacity:0.5 }
|
||||
.email-brand { font-size:16px;font-weight:300;color:#ffffff66;letter-spacing:4px;text-transform:uppercase }
|
||||
.email-header { padding:0 0 32px;text-align:center }
|
||||
|
||||
/* Card */
|
||||
.email-card { padding:48px 40px;background:#191432cc;border:1px solid #0d948833;box-shadow:0 0 60px #0d948814,0 0 120px #6366f10a }
|
||||
.email-heading { margin:0 0 20px;font-size:24px;font-weight:300;color:#ccfbf1;line-height:1.3;letter-spacing:-.5px }
|
||||
.email-text { margin:0 0 24px;font-size:14px;line-height:2;color:#d2fcf5e0;font-weight:300 }
|
||||
.email-subtext { margin:0;font-size:13px;line-height:1.8;color:#b4c3d7f2;font-weight:300 }
|
||||
|
||||
/* Divider */
|
||||
.email-divider { height:1px;background:linear-gradient(90deg,transparent,#0d94884d,transparent) }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { padding:40px 40px;text-align:center }
|
||||
.email-btn { display:inline-block;padding:16px 48px;font-size:14px;font-weight:400;color:#e0f5f3!important;text-decoration:none;background:#0d94882e;border:1px solid #0d94884d;letter-spacing:1px }
|
||||
.email-btn-table { margin:0 0 32px }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { border:1px solid #0d94882e;background:#0d948808 }
|
||||
.email-link-label { margin:0 0 8px;font-size:11px;color:#b4fceebf;font-weight:300 }
|
||||
.email-link-url { margin:0;font-size:11px;font-family:monospace;color:#99f6e4;background:#00000033;padding:10px 12px;border:1px solid #0d948826;word-break:break-all }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:32px 0 0;text-align:center }
|
||||
.email-footer-text { margin:0;font-size:11px;color:#a0afc88c;font-weight:300 }
|
||||
.email-footer-link { color:#5eead480;text-decoration:none;font-weight:400 }
|
||||
|
||||
/* Typography utilities */
|
||||
.card-body h1,.card-body h2,.card-body h3,.card-body h4,.card-body h5,.card-body h6 { color:#ccfbf1!important }
|
||||
.card-body a,.card-body a:link,.card-body a:visited { color:#5eead4!important;text-decoration:underline!important }
|
||||
.card-body a:hover,.card-body a:active { color:#99f6e4!important }
|
||||
.card-body li { color:#d2fcf5e0!important }
|
||||
|
||||
/* Responsive */
|
||||
@media only screen and (max-width:480px) {
|
||||
.email-card { padding:32px 20px!important }
|
||||
.email-btn-row { padding:24px 20px!important }
|
||||
.email-heading { font-size:20px!important }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background:#0a0815;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" class="email-wrap">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<tr>
|
||||
<td class="email-header">
|
||||
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="24" height="24" class="email-logo" />
|
||||
<span class="email-brand">{{AppName}}</span>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Card -->
|
||||
<tr>
|
||||
<td class="email-card card-body">
|
||||
|
||||
<p class="email-heading">
|
||||
{{.locale.Tr "mail.hi_user_x" (.DisplayName|DotEscape)}}
|
||||
</p>
|
||||
|
||||
<p class="email-text">
|
||||
{{.locale.Tr "mail.register_notify.text_1" AppName}}
|
||||
</p>
|
||||
|
||||
<p class="email-subtext">
|
||||
{{.locale.Tr "mail.register_notify.text_2" .Username}}
|
||||
{{$set_pwd_url := printf "%suser/forgot_password" AppUrl}}
|
||||
{{.locale.Tr "mail.register_notify.text_3" $set_pwd_url}}
|
||||
</p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Divider -->
|
||||
<tr>
|
||||
<td class="email-divider"></td>
|
||||
</tr>
|
||||
|
||||
<!-- Button & Link -->
|
||||
<tr>
|
||||
<td class="email-btn-row">
|
||||
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-table">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{AppUrl}}user/login" class="email-btn">{{.locale.Tr "mail.view_it_on" AppName}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" class="email-link-box">
|
||||
<tr>
|
||||
<td style="padding:16px 18px;">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{AppUrl}}user/login</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td class="email-footer">
|
||||
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,138 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.locale.Tr "mail.reset_password.title" (.DisplayName|DotEscape)}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#0a0815 }
|
||||
.email-wrap { max-width:600px;margin:40px auto;font-family:system-ui,-apple-system,sans-serif }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:block;margin:0 auto 14px;border:0;outline:none;opacity:0.5 }
|
||||
.email-brand { font-size:16px;font-weight:300;color:#ffffff66;letter-spacing:4px;text-transform:uppercase }
|
||||
.email-header { padding:0 0 32px;text-align:center }
|
||||
|
||||
/* Card */
|
||||
.email-card { padding:48px 40px;background:#191432cc;border:1px solid #0d948833;box-shadow:0 0 60px #0d948814,0 0 120px #6366f10a }
|
||||
.email-heading { margin:0 0 20px;font-size:24px;font-weight:300;color:#ccfbf1;line-height:1.3;letter-spacing:-.5px }
|
||||
.email-text { margin:0 0 24px;font-size:14px;line-height:2;color:#d2fcf5e0;font-weight:300 }
|
||||
.email-subtext { margin:0;font-size:13px;line-height:1.8;color:#b4c3d7f2;font-weight:300 }
|
||||
|
||||
/* Divider */
|
||||
.email-divider { height:1px;background:linear-gradient(90deg,transparent,#0d94884d,transparent) }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { padding:40px 40px;text-align:center }
|
||||
.email-btn { display:inline-block;padding:16px 48px;font-size:14px;font-weight:400;color:#e0f5f3!important;text-decoration:none;background:#0d94882e;border:1px solid #0d94884d;letter-spacing:1px }
|
||||
.email-btn-table { margin:0 0 32px }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { border:1px solid #0d94882e;background:#0d948808 }
|
||||
.email-link-label { margin:0 0 8px;font-size:11px;color:#b4fceebf;font-weight:300 }
|
||||
.email-link-url { margin:0;font-size:11px;font-family:monospace;color:#99f6e4;background:#00000033;padding:10px 12px;border:1px solid #0d948826;word-break:break-all }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:32px 0 0;text-align:center }
|
||||
.email-footer-text { margin:0;font-size:11px;color:#a0afc88c;font-weight:300 }
|
||||
.email-footer-link { color:#5eead480;text-decoration:none;font-weight:400 }
|
||||
|
||||
/* Typography utilities */
|
||||
.card-body h1,.card-body h2,.card-body h3,.card-body h4,.card-body h5,.card-body h6 { color:#ccfbf1!important }
|
||||
.card-body a,.card-body a:link,.card-body a:visited { color:#5eead4!important;text-decoration:underline!important }
|
||||
.card-body a:hover,.card-body a:active { color:#99f6e4!important }
|
||||
.card-body li { color:#d2fcf5e0!important }
|
||||
|
||||
/* Responsive */
|
||||
@media only screen and (max-width:480px) {
|
||||
.email-card { padding:32px 20px!important }
|
||||
.email-btn-row { padding:24px 20px!important }
|
||||
.email-heading { font-size:20px!important }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background:#0a0815;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" class="email-wrap">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<tr>
|
||||
<td class="email-header">
|
||||
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="24" height="24" class="email-logo" />
|
||||
<span class="email-brand">{{AppName}}</span>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Card -->
|
||||
<tr>
|
||||
<td class="email-card card-body">
|
||||
|
||||
<p class="email-heading">
|
||||
{{.locale.Tr "mail.hi_user_x" (.DisplayName|DotEscape)}}
|
||||
</p>
|
||||
|
||||
<p class="email-text">
|
||||
{{.locale.Tr "mail.reset_password.text" .ResetPwdCodeLives}}
|
||||
</p>
|
||||
|
||||
<p class="email-subtext"></p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Divider -->
|
||||
<tr>
|
||||
<td class="email-divider"></td>
|
||||
</tr>
|
||||
|
||||
<!-- Button & Link -->
|
||||
<tr>
|
||||
<td class="email-btn-row">
|
||||
|
||||
{{$recover_url := printf "%suser/recover_account?code=%s" AppUrl (QueryEscape .Code)}}
|
||||
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-table">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{$recover_url}}" class="email-btn">{{.locale.Tr "mail.reset_password"}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" class="email-link-box">
|
||||
<tr>
|
||||
<td style="padding:16px 18px;">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{$recover_url}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td class="email-footer">
|
||||
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,86 @@
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#0a0815 }
|
||||
.email-wrap { max-width:600px;margin:40px auto;font-family:system-ui,-apple-system,sans-serif }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:block;margin:0 auto 14px;border:0;outline:none;opacity:0.5 }
|
||||
.email-brand { font-size:16px;font-weight:300;color:#ffffff66;letter-spacing:4px;text-transform:uppercase }
|
||||
.email-header { padding:0 0 32px;text-align:center }
|
||||
|
||||
/* Card */
|
||||
.email-card { padding:48px 40px;background:#191432cc;border:1px solid #0d948833;box-shadow:0 0 60px #0d948814,0 0 120px #6366f10a }
|
||||
.email-heading,
|
||||
.mail-type-activate > p:first-child,
|
||||
.mail-type-activate_email > p:first-child { margin:0 0 20px;font-size:24px;font-weight:300;color:#ccfbf1;line-height:1.3;letter-spacing:-.5px }
|
||||
.email-text,
|
||||
.mail-type-activate > p:nth-of-type(2) { margin:0 0 24px;font-size:14px;line-height:2;color:#d2fcf5e0;font-weight:300 }
|
||||
.email-subtext { margin:0;font-size:13px;line-height:1.8;color:#b4c3d7f2;font-weight:300 }
|
||||
|
||||
/* Divider */
|
||||
.email-divider { height:1px;background:linear-gradient(90deg,transparent,#0d94884d,transparent) }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { padding:40px 40px;text-align:center }
|
||||
.email-btn,.card-body .email-btn,.card-body .email-btn:link,.card-body .email-btn:visited { display:inline-block;padding:16px 48px;font-size:14px;font-weight:400;color:#e0f5f3!important;text-decoration:none!important;background:#0d94882e;border:1px solid #0d94884d;letter-spacing:1px }
|
||||
.email-btn-table { margin:0 0 32px }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { border:1px solid #0d94882e;background:#0d948808 }
|
||||
.email-link-label { margin:0 0 8px;font-size:11px;color:#b4fceebf;font-weight:300 }
|
||||
.email-link-url { margin:0;font-size:11px;font-family:monospace;color:#99f6e4;background:#00000033;padding:10px 12px;border:1px solid #0d948826;word-break:break-all }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:32px 0 0;text-align:center }
|
||||
.email-footer-text { margin:0;font-size:11px;color:#a0afc88c;font-weight:300 }
|
||||
.email-footer-link { color:#5eead480;text-decoration:none;font-weight:400 }
|
||||
|
||||
/* Typography utilities */
|
||||
.card-body h1,.card-body h2,.card-body h3,.card-body h4,.card-body h5,.card-body h6 { color:#ccfbf1!important }
|
||||
.card-body a,.card-body a:link,.card-body a:visited { color:#5eead4!important;text-decoration:underline!important }
|
||||
.card-body a:hover,.card-body a:active { color:#99f6e4!important }
|
||||
.card-body li { color:#d2fcf5e0!important }
|
||||
|
||||
/* Responsive */
|
||||
@media only screen and (max-width:480px) {
|
||||
.email-card { padding:32px 20px!important }
|
||||
.email-btn-row { padding:24px 20px!important }
|
||||
.email-heading,
|
||||
.mail-type-activate > p:first-child,
|
||||
.mail-type-activate_email > p:first-child { font-size:20px!important }
|
||||
}
|
||||
|
||||
/* Official-snapshot presentation adapter: no content or translation overrides. */
|
||||
body { margin:0; padding:24px 16px; background-color:#0a0815; }
|
||||
body, td { font-family:Arial,sans-serif; }
|
||||
.email-wrap { width:100%; max-width:600px; border-collapse:separate; border-spacing:0; }
|
||||
.card-body { font-size:14px; line-height:1.7; overflow-wrap:anywhere; }
|
||||
.card-body > pre, .card-body code { font-family:Consolas,monospace; white-space:pre-wrap; overflow-wrap:anywhere; }
|
||||
.card-body ul { padding-left:22px; }
|
||||
.card-body blockquote { padding-left:16px; border-left:3px solid #0d9488; }
|
||||
|
||||
|
||||
.mail-footer { margin:0; }
|
||||
.mail-footer td { border-top-color:transparent!important; color:#d2fcf5!important; }
|
||||
.mail-footer a { color:#0d9488!important; text-decoration:none; }
|
||||
.email-body { padding:32px; }
|
||||
body { background-image:radial-gradient(ellipse at center,#0d948814,transparent 65%); }
|
||||
@media only screen and (max-width:480px) {
|
||||
body { padding:12px 8px; }
|
||||
.email-wrap { margin:0 auto; }
|
||||
.email-body { padding:24px 18px!important; }
|
||||
.card-body { width:auto!important; }
|
||||
.resp-hide { display:none!important; }
|
||||
|
||||
}
|
||||
|
||||
/* Shared framework controls: keep URLs readable, never style them as CTAs. */
|
||||
.email-link-cell { padding:16px 18px; }
|
||||
.email-link-url a,.card-body .email-link-url a { color:inherit!important; text-decoration:none!important; font-size:inherit; }
|
||||
.email-link-url { overflow-wrap:anywhere; word-break:break-all; }
|
||||
.email-action { margin:20px 0 0; }
|
||||
.email-btn-row { box-sizing:border-box; }
|
||||
.card-body .email-btn { max-width:100%; box-sizing:border-box; white-space:normal; overflow-wrap:anywhere; }
|
||||
@media only screen and (max-width:480px) { .card-body .email-btn { display:block; text-align:center; width:100%; } .email-btn-table { width:100%; } }
|
||||
|
||||
/* Account activation instructions use the normal body style. */
|
||||
.mail-type-activate > p:nth-of-type(2) { font-style:normal; border-left:0; padding-left:0; }
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "aurora",
|
||||
"description": "Ethereal / Dreamlike",
|
||||
"mode": "framed",
|
||||
"layout": "aurora"
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.locale.Tr "mail.team_invite.subject" (.Inviter.DisplayName|DotEscape) (.Organization.DisplayName|DotEscape)}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#e2e8f2 }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:inline-block;margin:0 auto 8px;border:0;outline:none }
|
||||
.email-brand { margin:0;font-size:18px;font-weight:700;color:#1e293b;letter-spacing:-0.3px }
|
||||
.email-header { padding:28px 32px 18px;background:linear-gradient(135deg,#dbeafe,#e0e7ff,#e8f0fe);border-bottom:1px solid #3b82f61a;text-align:center }
|
||||
|
||||
/* Card */
|
||||
.email-card { max-width:600px;margin:24px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 4px #0000000f,0 8px 24px #0000000f,0 0 0 1px #0000000a;overflow:hidden }
|
||||
.email-heading { margin:0 0 12px;font-size:17px;line-height:1.5;color:#1e293b;font-weight:600 }
|
||||
.email-text { margin:0 0 8px;font-size:15px;line-height:1.6;color:#475569 }
|
||||
.email-subtext { margin:0 0 20px;font-size:14px;line-height:1.6;color:#64748b }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { margin:28px 0 24px;border-collapse:separate }
|
||||
.email-btn { display:inline-block;padding:14px 40px;font-size:15px;font-weight:600;color:#ffffff!important;text-decoration:none;background-color:#3b82f6;border-radius:40px;border:1px solid #3b82f6;letter-spacing:0.2px }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { background:linear-gradient(135deg,#eff4fd,#f5f7fe);border:1px solid #3b82f61a;border-radius:12px;padding:16px 18px;margin:24px 0 16px;box-shadow:inset 0 1px 0 #ffffffe6 }
|
||||
.email-link-label { margin:0 0 8px;font-size:13px;color:#64748b;line-height:1.5 }
|
||||
.email-link-url { margin:0;font-size:13px;color:#3b82f6;background:#ffffff;padding:10px 12px;border-radius:8px;border:1px solid #0000000f;word-break:break-all;font-family:monospace }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:18px 32px 22px;background:linear-gradient(135deg,#f8f9fc,#f4f6fb);border-top:1px solid #0000000f;text-align:center }
|
||||
.email-footer-text { margin:0;font-size:13px;color:#94a3b8 }
|
||||
.email-footer-link { color:#3b82f6;text-decoration:none;font-weight:500 }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background:#e2e8f2;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<div class="email-card">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<div class="email-header">
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="32" height="32" class="email-logo" />
|
||||
<p class="email-brand">{{AppName}}</p>
|
||||
</div>
|
||||
|
||||
<!-- Card -->
|
||||
<div style="padding:32px 32px 24px;color:#334155;">
|
||||
|
||||
<p class="email-heading">
|
||||
{{.locale.Tr "mail.hi_user_x" .Invite.Email}}
|
||||
</p>
|
||||
|
||||
<p class="email-text">
|
||||
{{.locale.Tr "mail.team_invite.text_1" (DotEscape .Inviter.DisplayName) (DotEscape .Team.Name) (DotEscape .Organization.DisplayName)}}
|
||||
</p>
|
||||
|
||||
<p class="email-subtext">
|
||||
{{.locale.Tr "mail.team_invite.text_2"}}
|
||||
</p>
|
||||
|
||||
<!-- Button -->
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-row">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{.InviteURL}}" class="email-btn">{{.locale.Tr "mail.view_it_on" AppName}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Fallback link -->
|
||||
<div class="email-link-box">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{.InviteURL}}</p>
|
||||
</div>
|
||||
|
||||
<p style="margin:16px 0 0;font-size:13px;color:#64748b;line-height:1.5;">
|
||||
{{.locale.Tr "mail.team_invite.text_3" .Invite.Email}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="email-footer">
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,100 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.Subject}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#e2e8f2 }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:inline-block;margin:0 auto 8px;border:0;outline:none }
|
||||
.email-brand { margin:0;font-size:18px;font-weight:700;color:#1e293b;letter-spacing:-0.3px }
|
||||
.email-header { padding:28px 32px 18px;background:linear-gradient(135deg,#dbeafe,#e0e7ff,#e8f0fe);border-bottom:1px solid #3b82f61a;text-align:center }
|
||||
|
||||
/* Card */
|
||||
.email-card { max-width:600px;margin:24px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 4px #0000000f,0 8px 24px #0000000f,0 0 0 1px #0000000a;overflow:hidden }
|
||||
.email-heading { font-size:17px;font-weight:600;color:#1e293b;margin:0 0 16px }
|
||||
|
||||
/* Content */
|
||||
.job-list { list-style:none;padding:0;margin:0 0 24px 0 }
|
||||
.job-item { background:linear-gradient(135deg,#f8faff,#f4f7fe);border:1px solid #0000000f;border-radius:10px;padding:12px 16px;margin-bottom:8px;box-shadow:inset 0 1px 0 #ffffffb3 }
|
||||
.job-link { color:#3b82f6;text-decoration:none;font-weight:500;font-size:14px;display:block }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { margin:16px 0 24px;border-collapse:separate }
|
||||
.email-btn { display:inline-block;padding:14px 40px;font-size:15px;font-weight:600;color:#ffffff!important;text-decoration:none;background-color:#3b82f6;border-radius:40px;border:1px solid #3b82f6;letter-spacing:0.2px }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { background:linear-gradient(135deg,#eff4fd,#f5f7fe);border:1px solid #3b82f61a;border-radius:12px;padding:16px 18px;margin:24px 0 16px;box-shadow:inset 0 1px 0 #ffffffe6 }
|
||||
.email-link-label { margin:0 0 8px;font-size:13px;color:#64748b;line-height:1.5 }
|
||||
.email-link-url { margin:0;font-size:13px;color:#3b82f6;background:#ffffff;padding:10px 12px;border-radius:8px;border:1px solid #0000000f;word-break:break-all;font-family:monospace }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:18px 32px 22px;background:linear-gradient(135deg,#f8f9fc,#f4f6fb);border-top:1px solid #0000000f;text-align:center }
|
||||
.email-footer-text { margin:0;font-size:13px;color:#94a3b8 }
|
||||
.email-footer-link { color:#3b82f6;text-decoration:none;font-weight:500 }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background:#e2e8f2;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<div class="email-card">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<div class="email-header">
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="32" height="32" class="email-logo" />
|
||||
<p class="email-brand">{{AppName}}</p>
|
||||
</div>
|
||||
|
||||
<!-- Card -->
|
||||
<div style="padding:32px 32px 24px;color:#334155;">
|
||||
|
||||
<h2 class="email-heading">
|
||||
{{.Repo.FullName}} — {{.Run.WorkflowID}}: {{.RunStatusText}}
|
||||
</h2>
|
||||
|
||||
<ul class="job-list">
|
||||
{{range $job := .Jobs}}
|
||||
<li class="job-item">
|
||||
<a href="{{$job.HTMLURL}}" class="job-link">
|
||||
{{$job.Status}}: {{$job.Name}}{{if gt $job.Attempt 1}} (retry #{{$job.Attempt}}){{end}} — {{$job.Duration}}
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
|
||||
<!-- Button -->
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-row">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{.Run.HTMLURL}}" class="email-btn">{{.locale.Tr "mail.view_it_on" AppName}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Fallback link -->
|
||||
<div class="email-link-box">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{.Run.HTMLURL}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="email-footer">
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,88 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.Subject}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#e2e8f2 }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:inline-block;margin:0 auto 8px;border:0;outline:none }
|
||||
.email-brand { margin:0;font-size:18px;font-weight:700;color:#1e293b;letter-spacing:-0.3px }
|
||||
.email-header { padding:28px 32px 18px;background:linear-gradient(135deg,#dbeafe,#e0e7ff,#e8f0fe);border-bottom:1px solid #3b82f61a;text-align:center }
|
||||
|
||||
/* Card */
|
||||
.email-card { max-width:600px;margin:24px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 4px #0000000f,0 8px 24px #0000000f,0 0 0 1px #0000000a;overflow:hidden }
|
||||
.email-text { margin:0 0 20px;font-size:15px;line-height:1.6;color:#475569 }
|
||||
|
||||
/* Typography utilities */
|
||||
code { background:#eff4fd;padding:2px 8px;border-radius:5px;font-size:14px;color:#3b82f6;font-weight:500;border:1px solid #3b82f61f }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { margin:28px 0 24px;border-collapse:separate }
|
||||
.email-btn { display:inline-block;padding:14px 40px;font-size:15px;font-weight:600;color:#ffffff!important;text-decoration:none;background-color:#3b82f6;border-radius:40px;border:1px solid #3b82f6;letter-spacing:0.2px }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { background:linear-gradient(135deg,#eff4fd,#f5f7fe);border:1px solid #3b82f61a;border-radius:12px;padding:16px 18px;margin:24px 0 16px;box-shadow:inset 0 1px 0 #ffffffe6 }
|
||||
.email-link-label { margin:0 0 8px;font-size:13px;color:#64748b;line-height:1.5 }
|
||||
.email-link-url { margin:0;font-size:13px;color:#3b82f6;background:#ffffff;padding:10px 12px;border-radius:8px;border:1px solid #0000000f;word-break:break-all;font-family:monospace }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:18px 32px 22px;background:linear-gradient(135deg,#f8f9fc,#f4f6fb);border-top:1px solid #0000000f;text-align:center }
|
||||
.email-footer-text { margin:0;font-size:13px;color:#94a3b8 }
|
||||
.email-footer-link { color:#3b82f6;text-decoration:none;font-weight:500 }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background:#e2e8f2;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<div class="email-card">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<div class="email-header">
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="32" height="32" class="email-logo" />
|
||||
<p class="email-brand">{{AppName}}</p>
|
||||
</div>
|
||||
|
||||
<!-- Card -->
|
||||
<div style="padding:32px 32px 24px;color:#334155;">
|
||||
|
||||
<p class="email-text">
|
||||
{{.locale.Tr "mail.repo.collaborator.added.text"}} <code>{{.RepoName}}</code>.
|
||||
</p>
|
||||
|
||||
<!-- Button -->
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-row">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{.Link}}" class="email-btn">{{.locale.Tr "mail.view_it_on" AppName}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Fallback link -->
|
||||
<div class="email-link-box">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{.Link}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="email-footer">
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,90 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.Subject}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#e2e8f2 }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:inline-block;margin:0 auto 8px;border:0;outline:none }
|
||||
.email-brand { margin:0;font-size:18px;font-weight:700;color:#1e293b;letter-spacing:-0.3px }
|
||||
.email-header { padding:28px 32px 18px;background:linear-gradient(135deg,#dbeafe,#e0e7ff,#e8f0fe);border-bottom:1px solid #3b82f61a;text-align:center }
|
||||
|
||||
/* Card */
|
||||
.email-card { max-width:600px;margin:24px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 4px #0000000f,0 8px 24px #0000000f,0 0 0 1px #0000000a;overflow:hidden }
|
||||
.email-text { margin:0 0 20px;font-size:15px;line-height:1.6;color:#475569 }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { margin:28px 0 24px;border-collapse:separate }
|
||||
.email-btn { display:inline-block;padding:14px 40px;font-size:15px;font-weight:600;color:#ffffff!important;text-decoration:none;background-color:#3b82f6;border-radius:40px;border:1px solid #3b82f6;letter-spacing:0.2px }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { background:linear-gradient(135deg,#eff4fd,#f5f7fe);border:1px solid #3b82f61a;border-radius:12px;padding:16px 18px;margin:24px 0 16px;box-shadow:inset 0 1px 0 #ffffffe6 }
|
||||
.email-link-label { margin:0 0 8px;font-size:13px;color:#64748b;line-height:1.5 }
|
||||
.email-link-url { margin:0;font-size:13px;color:#3b82f6;background:#ffffff;padding:10px 12px;border-radius:8px;border:1px solid #0000000f;word-break:break-all;font-family:monospace }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:18px 32px 22px;background:linear-gradient(135deg,#f8f9fc,#f4f6fb);border-top:1px solid #0000000f;text-align:center }
|
||||
.email-footer-text { margin:0;font-size:13px;color:#94a3b8 }
|
||||
.email-footer-link { color:#3b82f6;text-decoration:none;font-weight:500 }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background:#e2e8f2;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<div class="email-card">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<div class="email-header">
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="32" height="32" class="email-logo" />
|
||||
<p class="email-brand">{{AppName}}</p>
|
||||
</div>
|
||||
|
||||
<!-- Card -->
|
||||
<div style="padding:32px 32px 24px;color:#334155;">
|
||||
{{$repo_url := HTMLFormat "<a href='%s' style='color:#3b82f6;text-decoration:none;font-weight:500;'>%s</a>" .Issue.Repo.HTMLURL .Issue.Repo.FullName}}
|
||||
{{$link := HTMLFormat "<a href='%s' style='color:#3b82f6;text-decoration:none;font-weight:500;'>#%d</a>" .Link .Issue.Index}}
|
||||
<p class="email-text">
|
||||
{{if .IsPull}}
|
||||
{{.locale.Tr "mail.issue_assigned.pull" .Doer.Name $link $repo_url}}
|
||||
{{else}}
|
||||
{{.locale.Tr "mail.issue_assigned.issue" .Doer.Name $link $repo_url}}
|
||||
{{end}}
|
||||
</p>
|
||||
|
||||
<!-- Button -->
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-row">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{.Link}}" class="email-btn">{{.locale.Tr "mail.view_it_on" AppName}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Fallback link -->
|
||||
<div class="email-link-box">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{.Link}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="email-footer">
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,162 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.Subject}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#e2e8f2 }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:inline-block;margin:0 auto 8px;border:0;outline:none }
|
||||
.email-brand { margin:0;font-size:18px;font-weight:700;color:#1e293b;letter-spacing:-0.3px }
|
||||
.email-header { padding:28px 32px 18px;background:linear-gradient(135deg,#dbeafe,#e0e7ff,#e8f0fe);border-bottom:1px solid #3b82f61a;text-align:center }
|
||||
|
||||
/* Card */
|
||||
.email-card { max-width:600px;margin:24px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 4px #0000000f,0 8px 24px #0000000f,0 0 0 1px #0000000a;overflow:hidden }
|
||||
.email-text { margin:0 0 16px;font-size:15px;line-height:1.6;color:#475569 }
|
||||
|
||||
/* Content */
|
||||
blockquote { padding-left:1em;margin:1em 0;border-left:2px solid #dbeafe;color:#64748b }
|
||||
pre { background:#eff4fd;padding:12px;border-radius:8px;overflow-x:auto;font-size:13px;line-height:1.5;border:1px solid #0000000f }
|
||||
hr { border:none;border-top:1px solid #0000000f;margin:20px 0 }
|
||||
.review-path { margin:8px 0;font-size:13px;color:#64748b }
|
||||
.review-content { padding-left:1em;margin:1em 0 }
|
||||
.review-body { margin-top:8px;font-size:14px;line-height:1.6 }
|
||||
.commit-list { margin:16px 0;padding-left:20px }
|
||||
.commit-item { margin-bottom:6px;font-size:14px }
|
||||
.commit-link { color:#3b82f6;text-decoration:none;font-weight:500 }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { margin:28px 0 24px;border-collapse:separate }
|
||||
.email-btn { display:inline-block;padding:14px 40px;font-size:15px;font-weight:600;color:#ffffff!important;text-decoration:none;background-color:#3b82f6;border-radius:40px;border:1px solid #3b82f6;letter-spacing:0.2px }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { background:linear-gradient(135deg,#eff4fd,#f5f7fe);border:1px solid #3b82f61a;border-radius:12px;padding:16px 18px;margin:24px 0 16px;box-shadow:inset 0 1px 0 #ffffffe6 }
|
||||
.email-link-label { margin:0 0 8px;font-size:13px;color:#64748b;line-height:1.5 }
|
||||
.email-link-url { margin:0;font-size:13px;color:#3b82f6;background:#ffffff;padding:10px 12px;border-radius:8px;border:1px solid #0000000f;word-break:break-all;font-family:monospace }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:18px 32px 22px;background:linear-gradient(135deg,#f8f9fc,#f4f6fb);border-top:1px solid #0000000f;text-align:center }
|
||||
.email-footer-text { margin:0;font-size:13px;color:#94a3b8 }
|
||||
.email-footer-link { color:#3b82f6;text-decoration:none;font-weight:500 }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background:#e2e8f2;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<div class="email-card">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<div class="email-header">
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="32" height="32" class="email-logo" />
|
||||
<p class="email-brand">{{AppName}}</p>
|
||||
</div>
|
||||
|
||||
<!-- Card -->
|
||||
<div style="padding:32px 32px 24px;color:#334155;">
|
||||
{{if .IsMention}}
|
||||
<p class="email-text">{{.locale.Tr "mail.issue.x_mentioned_you" .Doer.Name}}</p>
|
||||
{{end}}
|
||||
|
||||
{{if eq .ActionName "push"}}
|
||||
<p class="email-text">
|
||||
{{if .Comment.IsForcePush}}
|
||||
{{$oldCommitUrl := printf "%s/commit/%s" .Comment.Issue.PullRequest.BaseRepo.HTMLURL .Comment.OldCommit}}
|
||||
{{$oldShortSha := ShortSha .Comment.OldCommit}}
|
||||
{{$oldCommitLink := HTMLFormat "<a href='%[1]s' style='color:#3b82f6;text-decoration:none;font-weight:500;'>%[2]s</a>" $oldCommitUrl $oldShortSha}}
|
||||
|
||||
{{$newCommitUrl := printf "%s/commit/%s" .Comment.Issue.PullRequest.BaseRepo.HTMLURL .Comment.NewCommit}}
|
||||
{{$newShortSha := ShortSha .Comment.NewCommit}}
|
||||
{{$newCommitLink := HTMLFormat "<a href='%[1]s' style='color:#3b82f6;text-decoration:none;font-weight:500;'>%[2]s</a>" $newCommitUrl $newShortSha}}
|
||||
|
||||
{{.locale.Tr "mail.issue.action.force_push" .Doer.Name .Comment.Issue.PullRequest.HeadBranch $oldCommitLink $newCommitLink}}
|
||||
{{else}}
|
||||
{{.locale.TrN (len .Comment.Commits) "mail.issue.action.push_1" "mail.issue.action.push_n" .Doer.Name .Comment.Issue.PullRequest.HeadBranch (len .Comment.Commits)}}
|
||||
{{end}}
|
||||
</p>
|
||||
{{end}}
|
||||
|
||||
<p class="email-text">
|
||||
{{if eq .ActionName "close"}}
|
||||
{{.locale.Tr "mail.issue.action.close" .Doer.Name .Issue.Index}}
|
||||
{{else if eq .ActionName "reopen"}}
|
||||
{{.locale.Tr "mail.issue.action.reopen" .Doer.Name .Issue.Index}}
|
||||
{{else if eq .ActionName "merge"}}
|
||||
{{.locale.Tr "mail.issue.action.merge" .Doer.Name .Issue.Index .Issue.PullRequest.BaseBranch}}
|
||||
{{else if eq .ActionName "approve"}}
|
||||
{{.locale.Tr "mail.issue.action.approve" .Doer.Name}}
|
||||
{{else if eq .ActionName "reject"}}
|
||||
{{.locale.Tr "mail.issue.action.reject" .Doer.Name}}
|
||||
{{else if eq .ActionName "review"}}
|
||||
{{.locale.Tr "mail.issue.action.review" .Doer.Name}}
|
||||
{{else if eq .ActionName "review_dismissed"}}
|
||||
{{.locale.Tr "mail.issue.action.review_dismissed" .Doer.Name .Comment.Review.Reviewer.Name}}
|
||||
{{else if eq .ActionName "ready_for_review"}}
|
||||
{{.locale.Tr "mail.issue.action.ready_for_review" .Doer.Name}}
|
||||
{{end}}
|
||||
|
||||
{{- if eq .Body ""}}
|
||||
{{if eq .ActionName "new"}}
|
||||
{{.locale.Tr "mail.issue.action.new" .Doer.Name .Issue.Index}}
|
||||
{{end}}
|
||||
{{else}}
|
||||
{{.Body}}
|
||||
{{end -}}{{if .CanReply}} {{.locale.Tr "mail.reply"}}{{end}}
|
||||
</p>
|
||||
|
||||
{{- range .ReviewComments}}
|
||||
<hr>
|
||||
<p class="review-path">{{$.locale.Tr "mail.issue.in_tree_path" .TreePath}}</p>
|
||||
<div class="review-content">
|
||||
<pre>{{.Patch}}</pre>
|
||||
<div class="review-body">{{.RenderedContent}}</div>
|
||||
</div>
|
||||
{{end -}}
|
||||
|
||||
{{if eq .ActionName "push"}}
|
||||
<ul class="commit-list">
|
||||
{{range .Comment.Commits}}
|
||||
<li class="commit-item">
|
||||
<a href="{{$.Comment.Issue.PullRequest.BaseRepo.HTMLURL}}/commit/{{.UserCommit.GitCommit.ID}}" class="commit-link">
|
||||
{{ShortSha .UserCommit.GitCommit.ID.String}}
|
||||
</a> — {{.UserCommit.GitCommit.MessageTitle}}
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
|
||||
<!-- Button -->
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-row">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{.Link}}" class="email-btn">{{.locale.Tr "mail.view_it_on" AppName}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Fallback link -->
|
||||
<div class="email-link-box">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{.Link}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="email-footer">
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,122 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.Subject}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#e2e8f2 }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:inline-block;margin:0 auto 8px;border:0;outline:none }
|
||||
.email-brand { margin:0;font-size:18px;font-weight:700;color:#1e293b;letter-spacing:-0.3px }
|
||||
.email-header { padding:28px 32px 18px;background:linear-gradient(135deg,#dbeafe,#e0e7ff,#e8f0fe);border-bottom:1px solid #3b82f61a;text-align:center }
|
||||
|
||||
/* Card */
|
||||
.email-card { max-width:600px;margin:24px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 4px #0000000f,0 8px 24px #0000000f,0 0 0 1px #0000000a;overflow:hidden }
|
||||
.email-heading { margin:20px 0 8px;font-size:17px;font-weight:600;color:#1e293b }
|
||||
.email-subheading { margin:20px 0 8px;font-weight:600;color:#1e293b }
|
||||
.email-text { margin:0 0 16px;font-size:15px;line-height:1.6;color:#475569 }
|
||||
|
||||
/* Content */
|
||||
blockquote { padding-left:1em;margin:1em 0;border-left:2px solid #dbeafe;color:#64748b }
|
||||
ul { margin:12px 0 16px;padding-left:20px }
|
||||
li { margin-bottom:6px;font-size:14px;line-height:1.5;color:#475569 }
|
||||
.email-content { margin:12px 0 16px;font-size:14px;line-height:1.6;color:#475569 }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { margin:16px 0 24px;border-collapse:separate }
|
||||
.email-btn { display:inline-block;padding:14px 40px;font-size:15px;font-weight:600;color:#ffffff!important;text-decoration:none;background-color:#3b82f6;border-radius:40px;border:1px solid #3b82f6;letter-spacing:0.2px }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { background:linear-gradient(135deg,#eff4fd,#f5f7fe);border:1px solid #3b82f61a;border-radius:12px;padding:16px 18px;margin:24px 0 16px;box-shadow:inset 0 1px 0 #ffffffe6 }
|
||||
.email-link-label { margin:0 0 8px;font-size:13px;color:#64748b;line-height:1.5 }
|
||||
.email-link-url { margin:0;font-size:13px;color:#3b82f6;background:#ffffff;padding:10px 12px;border-radius:8px;border:1px solid #0000000f;word-break:break-all;font-family:monospace }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:18px 32px 22px;background:linear-gradient(135deg,#f8f9fc,#f4f6fb);border-top:1px solid #0000000f;text-align:center }
|
||||
.email-footer-text { margin:0;font-size:13px;color:#94a3b8 }
|
||||
.email-footer-link { color:#3b82f6;text-decoration:none;font-weight:500 }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background:#e2e8f2;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<div class="email-card">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<div class="email-header">
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="32" height="32" class="email-logo" />
|
||||
<p class="email-brand">{{AppName}}</p>
|
||||
</div>
|
||||
|
||||
<!-- Card -->
|
||||
<div style="padding:32px 32px 24px;color:#334155;">
|
||||
{{$release_url := HTMLFormat "<a href='%s' style='color:#3b82f6;text-decoration:none;font-weight:500;'>%s</a>" .Release.HTMLURL .Release.TagName}}
|
||||
{{$repo_url := HTMLFormat "<a href='%s' style='color:#3b82f6;text-decoration:none;font-weight:500;'>%s</a>" .Release.Repo.HTMLURL .Release.Repo.FullName}}
|
||||
<p class="email-text">
|
||||
{{.locale.Tr "mail.release.new.text" .Release.Publisher.Name $release_url $repo_url}}
|
||||
</p>
|
||||
|
||||
<h4 class="email-heading">{{.locale.Tr "mail.release.title" .Release.Title}}</h4>
|
||||
|
||||
{{if .Release.RenderedNote}}
|
||||
{{.locale.Tr "mail.release.note"}}<br>
|
||||
<div class="email-content">
|
||||
{{.Release.RenderedNote}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<p class="email-subheading">{{.locale.Tr "mail.release.downloads"}}</p>
|
||||
<ul>
|
||||
{{if not .DisableDownloadSourceArchives}}
|
||||
<li>
|
||||
<a href="{{.Release.Repo.HTMLURL}}/archive/{{.Release.TagName | PathEscapeSegments}}.zip" rel="nofollow" style="color:#3b82f6;text-decoration:none;font-weight:500;">{{.locale.Tr "mail.release.download.zip"}}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{.Release.Repo.HTMLURL}}/archive/{{.Release.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow" style="color:#3b82f6;text-decoration:none;font-weight:500;">{{.locale.Tr "mail.release.download.targz"}}</a>
|
||||
</li>
|
||||
{{end}}
|
||||
{{if .Release.Attachments}}
|
||||
{{range .Release.Attachments}}
|
||||
<li>
|
||||
<a href="{{.DownloadURL}}" target="_blank" style="color:#3b82f6;text-decoration:none;font-weight:500;">{{.Name}} ({{.Size | FileSize}})</a>
|
||||
</li>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</ul>
|
||||
|
||||
<!-- Button -->
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-row">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{.Link}}" class="email-btn">{{.locale.Tr "mail.view_it_on" AppName}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Fallback link -->
|
||||
<div class="email-link-box">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{.Link}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="email-footer">
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,85 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.Subject}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#e2e8f2 }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:inline-block;margin:0 auto 8px;border:0;outline:none }
|
||||
.email-brand { margin:0;font-size:18px;font-weight:700;color:#1e293b;letter-spacing:-0.3px }
|
||||
.email-header { padding:28px 32px 18px;background:linear-gradient(135deg,#dbeafe,#e0e7ff,#e8f0fe);border-bottom:1px solid #3b82f61a;text-align:center }
|
||||
|
||||
/* Card */
|
||||
.email-card { max-width:600px;margin:24px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 4px #0000000f,0 8px 24px #0000000f,0 0 0 1px #0000000a;overflow:hidden }
|
||||
.email-text { margin:0 0 20px;font-size:15px;line-height:1.6;color:#475569 }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { margin:28px 0 24px;border-collapse:separate }
|
||||
.email-btn { display:inline-block;padding:14px 40px;font-size:15px;font-weight:600;color:#ffffff!important;text-decoration:none;background-color:#3b82f6;border-radius:40px;border:1px solid #3b82f6;letter-spacing:0.2px }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { background:linear-gradient(135deg,#eff4fd,#f5f7fe);border:1px solid #3b82f61a;border-radius:12px;padding:16px 18px;margin:24px 0 16px;box-shadow:inset 0 1px 0 #ffffffe6 }
|
||||
.email-link-label { margin:0 0 8px;font-size:13px;color:#64748b;line-height:1.5 }
|
||||
.email-link-url { margin:0;font-size:13px;color:#3b82f6;background:#ffffff;padding:10px 12px;border-radius:8px;border:1px solid #0000000f;word-break:break-all;font-family:monospace }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:18px 32px 22px;background:linear-gradient(135deg,#f8f9fc,#f4f6fb);border-top:1px solid #0000000f;text-align:center }
|
||||
.email-footer-text { margin:0;font-size:13px;color:#94a3b8 }
|
||||
.email-footer-link { color:#3b82f6;text-decoration:none;font-weight:500 }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background:#e2e8f2;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<div class="email-card">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<div class="email-header">
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="32" height="32" class="email-logo" />
|
||||
<p class="email-brand">{{AppName}}</p>
|
||||
</div>
|
||||
|
||||
<!-- Card -->
|
||||
<div style="padding:32px 32px 24px;color:#334155;">
|
||||
{{$url := HTMLFormat "<a href='%[1]s' style='color:#3b82f6;text-decoration:none;font-weight:500;'>%[2]s</a>" .Link .Repo}}
|
||||
<p class="email-text">
|
||||
{{.Subject}}. {{.locale.Tr "mail.repo.transfer.body" $url}}
|
||||
</p>
|
||||
|
||||
<!-- Button -->
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-row">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{.Link}}" class="email-btn">{{.locale.Tr "mail.view_it_on" AppName}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Fallback link -->
|
||||
<div class="email-link-box">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{.Link}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="email-footer">
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,92 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.locale.Tr "mail.activate_account.title" (.DisplayName|DotEscape)}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#e2e8f2 }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:inline-block;margin:0 auto 8px;border:0;outline:none }
|
||||
.email-brand { margin:0;font-size:18px;font-weight:700;color:#1e293b;letter-spacing:-0.3px }
|
||||
.email-header { padding:28px 32px 18px;background:linear-gradient(135deg,#dbeafe,#e0e7ff,#e8f0fe);border-bottom:1px solid #3b82f61a;text-align:center }
|
||||
|
||||
/* Card */
|
||||
.email-card { max-width:600px;margin:24px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 4px #0000000f,0 8px 24px #0000000f,0 0 0 1px #0000000a;overflow:hidden }
|
||||
.email-heading { margin:0 0 12px;font-size:17px;line-height:1.5;color:#1e293b;font-weight:600 }
|
||||
.email-text { margin:0 0 8px;font-size:15px;line-height:1.6;color:#475569 }
|
||||
.email-subtext { margin:0 0 20px;font-size:14px;line-height:1.6;color:#64748b }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { margin:28px 0 24px;border-collapse:separate }
|
||||
.email-btn { display:inline-block;padding:14px 40px;font-size:15px;font-weight:600;color:#ffffff!important;text-decoration:none;background-color:#3b82f6;border-radius:40px;border:1px solid #3b82f6;letter-spacing:0.2px }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { background:linear-gradient(135deg,#eff4fd,#f5f7fe);border:1px solid #3b82f61a;border-radius:12px;padding:16px 18px;margin:24px 0 16px;box-shadow:inset 0 1px 0 #ffffffe6 }
|
||||
.email-link-label { margin:0 0 8px;font-size:13px;color:#64748b;line-height:1.5 }
|
||||
.email-link-url { margin:0;font-size:13px;color:#3b82f6;background:#ffffff;padding:10px 12px;border-radius:8px;border:1px solid #0000000f;word-break:break-all;font-family:monospace }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:18px 32px 22px;background:linear-gradient(135deg,#f8f9fc,#f4f6fb);border-top:1px solid #0000000f;text-align:center }
|
||||
.email-footer-text { margin:0;font-size:13px;color:#94a3b8 }
|
||||
.email-footer-link { color:#3b82f6;text-decoration:none;font-weight:500 }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background:#e2e8f2;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<div class="email-card">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<div class="email-header">
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="32" height="32" class="email-logo" />
|
||||
<p class="email-brand">{{AppName}}</p>
|
||||
</div>
|
||||
|
||||
<!-- Card -->
|
||||
<div style="padding:32px 32px 24px;color:#334155;">
|
||||
|
||||
<p class="email-text">
|
||||
{{.locale.Tr "mail.activate_account.text_1" (.DisplayName|DotEscape) AppName}}
|
||||
</p>
|
||||
|
||||
<p class="email-subtext">
|
||||
{{.locale.Tr "mail.activate_account.text_2" .ActiveCodeLives}}
|
||||
</p>
|
||||
|
||||
<!-- Button -->
|
||||
{{$activate_url := printf "%suser/activate?code=%s" AppUrl (QueryEscape .Code)}}
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-row">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{$activate_url}}" class="email-btn">{{.locale.Tr "mail.activate_account"}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Fallback link -->
|
||||
<div class="email-link-box">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{$activate_url}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="email-footer">
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,91 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.locale.Tr "mail.activate_email.title" (.DisplayName|DotEscape)}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#e2e8f2 }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:inline-block;margin:0 auto 8px;border:0;outline:none }
|
||||
.email-brand { margin:0;font-size:18px;font-weight:700;color:#1e293b;letter-spacing:-0.3px }
|
||||
.email-header { padding:28px 32px 18px;background:linear-gradient(135deg,#dbeafe,#e0e7ff,#e8f0fe);border-bottom:1px solid #3b82f61a;text-align:center }
|
||||
|
||||
/* Card */
|
||||
.email-card { max-width:600px;margin:24px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 4px #0000000f,0 8px 24px #0000000f,0 0 0 1px #0000000a;overflow:hidden }
|
||||
.email-heading { margin:0 0 12px;font-size:17px;line-height:1.5;color:#1e293b;font-weight:600 }
|
||||
.email-text { margin:0 0 20px;font-size:15px;line-height:1.6;color:#475569 }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { margin:28px 0 24px;border-collapse:separate }
|
||||
.email-btn { display:inline-block;padding:14px 40px;font-size:15px;font-weight:600;color:#ffffff!important;text-decoration:none;background-color:#3b82f6;border-radius:40px;border:1px solid #3b82f6;letter-spacing:0.2px }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { background:linear-gradient(135deg,#eff4fd,#f5f7fe);border:1px solid #3b82f61a;border-radius:12px;padding:16px 18px;margin:24px 0 16px;box-shadow:inset 0 1px 0 #ffffffe6 }
|
||||
.email-link-label { margin:0 0 8px;font-size:13px;color:#64748b;line-height:1.5 }
|
||||
.email-link-url { margin:0;font-size:13px;color:#3b82f6;background:#ffffff;padding:10px 12px;border-radius:8px;border:1px solid #0000000f;word-break:break-all;font-family:monospace }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:18px 32px 22px;background:linear-gradient(135deg,#f8f9fc,#f4f6fb);border-top:1px solid #0000000f;text-align:center }
|
||||
.email-footer-text { margin:0;font-size:13px;color:#94a3b8 }
|
||||
.email-footer-link { color:#3b82f6;text-decoration:none;font-weight:500 }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background:#e2e8f2;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<div class="email-card">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<div class="email-header">
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="32" height="32" class="email-logo" />
|
||||
<p class="email-brand">{{AppName}}</p>
|
||||
</div>
|
||||
|
||||
<!-- Card -->
|
||||
<div style="padding:32px 32px 24px;color:#334155;">
|
||||
|
||||
<p class="email-heading">
|
||||
{{.locale.Tr "mail.hi_user_x" (.DisplayName|DotEscape)}}
|
||||
</p>
|
||||
|
||||
<p class="email-text">
|
||||
{{.locale.Tr "mail.activate_email.text" .ActiveCodeLives}}
|
||||
</p>
|
||||
|
||||
<!-- Button -->
|
||||
{{$activate_url := printf "%suser/activate_email?code=%s&email=%s" AppUrl (QueryEscape .Code) (QueryEscape .Email)}}
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-row">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{$activate_url}}" class="email-btn">{{.locale.Tr "mail.activate_email"}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Fallback link -->
|
||||
<div class="email-link-box">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{$activate_url}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="email-footer">
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,100 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.locale.Tr "mail.register_notify.title" (.DisplayName|DotEscape) AppName}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#e2e8f2 }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:inline-block;margin:0 auto 8px;border:0;outline:none }
|
||||
.email-brand { margin:0;font-size:18px;font-weight:700;color:#1e293b;letter-spacing:-0.3px }
|
||||
.email-header { padding:28px 32px 18px;background:linear-gradient(135deg,#dbeafe,#e0e7ff,#e8f0fe);border-bottom:1px solid #3b82f61a;text-align:center }
|
||||
|
||||
/* Card */
|
||||
.email-card { max-width:600px;margin:24px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 4px #0000000f,0 8px 24px #0000000f,0 0 0 1px #0000000a;overflow:hidden }
|
||||
.email-heading { margin:0 0 12px;font-size:17px;line-height:1.5;color:#1e293b;font-weight:600 }
|
||||
.email-text { margin:0 0 8px;font-size:15px;line-height:1.6;color:#475569 }
|
||||
.email-subtext { margin:0 0 20px;font-size:14px;line-height:1.6;color:#64748b }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { margin:28px 0 24px;border-collapse:separate }
|
||||
.email-btn { display:inline-block;padding:14px 40px;font-size:15px;font-weight:600;color:#ffffff!important;text-decoration:none;background-color:#3b82f6;border-radius:40px;border:1px solid #3b82f6;letter-spacing:0.2px }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { background:linear-gradient(135deg,#eff4fd,#f5f7fe);border:1px solid #3b82f61a;border-radius:12px;padding:16px 18px;margin:24px 0 16px;box-shadow:inset 0 1px 0 #ffffffe6 }
|
||||
.email-link-label { margin:0 0 8px;font-size:13px;color:#64748b;line-height:1.5 }
|
||||
.email-link-url { margin:0;font-size:13px;color:#3b82f6;background:#ffffff;padding:10px 12px;border-radius:8px;border:1px solid #0000000f;word-break:break-all;font-family:monospace }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:18px 32px 22px;background:linear-gradient(135deg,#f8f9fc,#f4f6fb);border-top:1px solid #0000000f;text-align:center }
|
||||
.email-footer-text { margin:0;font-size:13px;color:#94a3b8 }
|
||||
.email-footer-link { color:#3b82f6;text-decoration:none;font-weight:500 }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background:#e2e8f2;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<div class="email-card">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<div class="email-header">
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="32" height="32" class="email-logo" />
|
||||
<p class="email-brand">{{AppName}}</p>
|
||||
</div>
|
||||
|
||||
<!-- Card -->
|
||||
<div style="padding:32px 32px 24px;color:#334155;">
|
||||
|
||||
<p class="email-heading">
|
||||
{{.locale.Tr "mail.hi_user_x" (.DisplayName|DotEscape)}}
|
||||
</p>
|
||||
|
||||
<p class="email-text">
|
||||
{{.locale.Tr "mail.register_notify.text_1" AppName}}
|
||||
</p>
|
||||
|
||||
<p class="email-subtext">
|
||||
{{.locale.Tr "mail.register_notify.text_2" .Username}}
|
||||
</p>
|
||||
|
||||
<!-- Button -->
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-row">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{AppUrl}}user/login" class="email-btn">{{.locale.Tr "mail.view_it_on" AppName}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
{{$set_pwd_url := printf "%suser/forgot_password" AppUrl}}
|
||||
<p style="margin:0 0 16px;font-size:14px;line-height:1.6;color:#64748b;">
|
||||
{{.locale.Tr "mail.register_notify.text_3" $set_pwd_url}}
|
||||
</p>
|
||||
|
||||
<!-- Fallback link -->
|
||||
<div class="email-link-box">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{AppUrl}}user/login</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="email-footer">
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,91 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.locale.Tr "mail.reset_password.title" (.DisplayName|DotEscape)}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#e2e8f2 }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:inline-block;margin:0 auto 8px;border:0;outline:none }
|
||||
.email-brand { margin:0;font-size:18px;font-weight:700;color:#1e293b;letter-spacing:-0.3px }
|
||||
.email-header { padding:28px 32px 18px;background:linear-gradient(135deg,#dbeafe,#e0e7ff,#e8f0fe);border-bottom:1px solid #3b82f61a;text-align:center }
|
||||
|
||||
/* Card */
|
||||
.email-card { max-width:600px;margin:24px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 4px #0000000f,0 8px 24px #0000000f,0 0 0 1px #0000000a;overflow:hidden }
|
||||
.email-heading { margin:0 0 12px;font-size:17px;line-height:1.5;color:#1e293b;font-weight:600 }
|
||||
.email-text { margin:0 0 20px;font-size:15px;line-height:1.6;color:#475569 }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { margin:28px 0 24px;border-collapse:separate }
|
||||
.email-btn { display:inline-block;padding:14px 40px;font-size:15px;font-weight:600;color:#ffffff!important;text-decoration:none;background-color:#3b82f6;border-radius:40px;border:1px solid #3b82f6;letter-spacing:0.2px }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { background:linear-gradient(135deg,#eff4fd,#f5f7fe);border:1px solid #3b82f61a;border-radius:12px;padding:16px 18px;margin:24px 0 16px;box-shadow:inset 0 1px 0 #ffffffe6 }
|
||||
.email-link-label { margin:0 0 8px;font-size:13px;color:#64748b;line-height:1.5 }
|
||||
.email-link-url { margin:0;font-size:13px;color:#3b82f6;background:#ffffff;padding:10px 12px;border-radius:8px;border:1px solid #0000000f;word-break:break-all;font-family:monospace }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:18px 32px 22px;background:linear-gradient(135deg,#f8f9fc,#f4f6fb);border-top:1px solid #0000000f;text-align:center }
|
||||
.email-footer-text { margin:0;font-size:13px;color:#94a3b8 }
|
||||
.email-footer-link { color:#3b82f6;text-decoration:none;font-weight:500 }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background:#e2e8f2;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<div class="email-card">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<div class="email-header">
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="32" height="32" class="email-logo" />
|
||||
<p class="email-brand">{{AppName}}</p>
|
||||
</div>
|
||||
|
||||
<!-- Card -->
|
||||
<div style="padding:32px 32px 24px;color:#334155;">
|
||||
|
||||
<p class="email-heading">
|
||||
{{.locale.Tr "mail.hi_user_x" (.DisplayName|DotEscape)}}
|
||||
</p>
|
||||
|
||||
<p class="email-text">
|
||||
{{.locale.Tr "mail.reset_password.text" .ResetPwdCodeLives}}
|
||||
</p>
|
||||
|
||||
<!-- Button -->
|
||||
{{$recover_url := printf "%suser/recover_account?code=%s" AppUrl (QueryEscape .Code)}}
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-row">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{$recover_url}}" class="email-btn">{{.locale.Tr "mail.reset_password"}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Fallback link -->
|
||||
<div class="email-link-box">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{$recover_url}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="email-footer">
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,67 @@
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#e2e8f2 }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:inline-block;margin:0 auto 8px;border:0;outline:none }
|
||||
.email-brand { margin:0;font-size:18px;font-weight:700;color:#1e293b;letter-spacing:-0.3px }
|
||||
.email-header { padding:28px 32px 18px;background:linear-gradient(135deg,#dbeafe,#e0e7ff,#e8f0fe);border-bottom:1px solid #3b82f61a;text-align:center }
|
||||
|
||||
/* Card */
|
||||
.email-card { max-width:600px;margin:24px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 4px #0000000f,0 8px 24px #0000000f,0 0 0 1px #0000000a;overflow:hidden }
|
||||
.email-heading,
|
||||
.mail-type-activate > p:first-child,
|
||||
.mail-type-activate_email > p:first-child { margin:0 0 12px;font-size:17px;line-height:1.5;color:#1e293b;font-weight:600 }
|
||||
.email-text,
|
||||
.mail-type-activate > p:nth-of-type(2) { margin:0 0 8px;font-size:15px;line-height:1.6;color:#475569 }
|
||||
.email-subtext { margin:0 0 20px;font-size:14px;line-height:1.6;color:#64748b }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { margin:28px 0 24px;border-collapse:separate }
|
||||
.email-btn,.card-body .email-btn,.card-body .email-btn:link,.card-body .email-btn:visited { display:inline-block;padding:14px 40px;font-size:15px;font-weight:600;color:#ffffff!important;text-decoration:none!important;background-color:#3b82f6;border-radius:40px;border:1px solid #3b82f6;letter-spacing:0.2px }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { background:linear-gradient(135deg,#eff4fd,#f5f7fe);border:1px solid #3b82f61a;border-radius:12px;padding:16px 18px;margin:24px 0 16px;box-shadow:inset 0 1px 0 #ffffffe6 }
|
||||
.email-link-label { margin:0 0 8px;font-size:13px;color:#64748b;line-height:1.5 }
|
||||
.email-link-url { margin:0;font-size:13px;color:#3b82f6;background:#ffffff;padding:10px 12px;border-radius:8px;border:1px solid #0000000f;word-break:break-all;font-family:monospace }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:18px 32px 22px;background:linear-gradient(135deg,#f8f9fc,#f4f6fb);border-top:1px solid #0000000f;text-align:center }
|
||||
.email-footer-text { margin:0;font-size:13px;color:#94a3b8 }
|
||||
.email-footer-link { color:#3b82f6;text-decoration:none;font-weight:500 }
|
||||
|
||||
/* Official-snapshot presentation adapter: no content or translation overrides. */
|
||||
body { margin:0; padding:24px 16px; background-color:#e2e8f2; }
|
||||
body, td { font-family:Arial,sans-serif; }
|
||||
.email-wrap { width:100%; max-width:600px; border-collapse:separate; border-spacing:0; }
|
||||
.card-body { font-size:14px; line-height:1.7; overflow-wrap:anywhere; }
|
||||
.card-body > pre, .card-body code { font-family:Consolas,monospace; white-space:pre-wrap; overflow-wrap:anywhere; }
|
||||
.card-body ul { padding-left:22px; }
|
||||
.card-body blockquote { padding-left:16px; border-left:3px solid #3b82f6; }
|
||||
|
||||
|
||||
.mail-footer { margin:0; }
|
||||
.mail-footer td { border-top-color:transparent!important; color:#334155!important; }
|
||||
.mail-footer a { color:#3b82f6!important; text-decoration:none; }
|
||||
.email-body { padding:32px 32px 24px; color:#334155; }
|
||||
.glass-halo { display:inline-block; padding:15px 17px; border-radius:18px; background:#ffffffa8; border:1px solid #ffffff; box-shadow:0 4px 18px #3b82f617; }
|
||||
.email-body { background:linear-gradient(180deg,#ffffff,#fefeff); }
|
||||
@media only screen and (max-width:480px) {
|
||||
body { padding:12px 8px; }
|
||||
.email-wrap { margin:0 auto; }
|
||||
.email-body { padding:24px 18px!important; }
|
||||
.card-body { width:auto!important; }
|
||||
.resp-hide { display:none!important; }
|
||||
|
||||
}
|
||||
|
||||
/* Shared framework controls: keep URLs readable, never style them as CTAs. */
|
||||
.email-link-cell { padding:16px 18px; }
|
||||
.email-link-url a,.card-body .email-link-url a { color:inherit!important; text-decoration:none!important; font-size:inherit; }
|
||||
.email-link-url { overflow-wrap:anywhere; word-break:break-all; }
|
||||
.email-action { margin:20px 0 0; }
|
||||
.email-btn-row { box-sizing:border-box; }
|
||||
.card-body .email-btn { max-width:100%; box-sizing:border-box; white-space:normal; overflow-wrap:anywhere; }
|
||||
@media only screen and (max-width:480px) { .card-body .email-btn { display:block; text-align:center; width:100%; } .email-btn-table { width:100%; } }
|
||||
|
||||
/* Account activation instructions use the normal body style. */
|
||||
.mail-type-activate > p:nth-of-type(2) { font-style:normal; border-left:0; padding-left:0; }
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "bloom",
|
||||
"description": "Creative / Startup",
|
||||
"mode": "framed",
|
||||
"layout": "bloom"
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.locale.Tr "mail.team_invite.subject" (.Inviter.DisplayName|DotEscape) (.Organization.DisplayName|DotEscape)}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#fff7ed }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:inline-block;vertical-align:middle;margin:0 10px;border:0;outline:none }
|
||||
.email-brand { font-size:20px;font-weight:700;color:#9a3412;letter-spacing:-0.3px;vertical-align:middle;font-family:Georgia,'Times New Roman',serif }
|
||||
.email-header { padding:28px 32px 16px;border-bottom:1px solid #fed7aa;background:linear-gradient(135deg,#fff7ed,#ffffff) }
|
||||
|
||||
/* Card */
|
||||
.email-card { max-width:600px;margin:20px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 16px #ea580c14;overflow:hidden;border:1px solid #fed7aa }
|
||||
.email-heading { margin:0 0 12px;font-size:17px;line-height:1.5;color:#9a3412;font-family:Georgia,'Times New Roman',serif;font-weight:600 }
|
||||
.email-text { margin:0 0 8px;font-size:16px;line-height:1.6;color:#78350f }
|
||||
.email-subtext { margin:0 0 20px;font-size:15px;line-height:1.6;color:#92400e }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { margin:28px 0 24px;border-collapse:separate }
|
||||
.email-btn { display:inline-block;padding:14px 40px;font-size:16px;font-weight:600;color:#ffffff!important;text-decoration:none;background:linear-gradient(135deg,#ea580c,#c2410c);border-radius:50px;font-family:system-ui,sans-serif }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { background:#fff7ed;border:1px solid #fed7aa;border-radius:12px;padding:16px 18px;margin:24px 0 16px }
|
||||
.email-link-label { margin:0 0 8px;font-size:14px;color:#92400e;line-height:1.5 }
|
||||
.email-link-url { margin:0;font-size:13px;color:#c2410c;background:#ffffff;padding:10px 12px;border-radius:8px;border:1px solid #fed7aa;word-break:break-all;font-family:monospace }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:20px 32px 24px;background:#fff7ed;border-top:1px solid #fed7aa }
|
||||
.email-footer-text { margin:0;font-size:14px;color:#92400e }
|
||||
.email-footer-link { color:#ea580c;text-decoration:none;font-weight:600 }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background-color:#fff7ed;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<div class="email-card">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<div class="email-header">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;">
|
||||
<tr>
|
||||
<td style="vertical-align:middle;">
|
||||
<span style="font-size:15px;">👋</span>
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="30" height="30" class="email-logo" />
|
||||
<span class="email-brand">{{AppName}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Card -->
|
||||
<div style="padding:32px 32px 24px;color:#78350f;">
|
||||
|
||||
<p class="email-heading">
|
||||
{{.locale.Tr "mail.hi_user_x" .Invite.Email}}
|
||||
</p>
|
||||
|
||||
<p class="email-text">
|
||||
{{.locale.Tr "mail.team_invite.text_1" (DotEscape .Inviter.DisplayName) (DotEscape .Team.Name) (DotEscape .Organization.DisplayName)}}
|
||||
</p>
|
||||
|
||||
<p class="email-subtext">
|
||||
{{.locale.Tr "mail.team_invite.text_2"}}
|
||||
</p>
|
||||
|
||||
<!-- Button -->
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-row">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{.InviteURL}}" class="email-btn">{{.locale.Tr "mail.view_it_on" AppName}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Fallback link -->
|
||||
<div class="email-link-box">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{.InviteURL}}</p>
|
||||
</div>
|
||||
|
||||
<p style="margin:16px 0 0;font-size:14px;color:#92400e;line-height:1.5;">
|
||||
{{.locale.Tr "mail.team_invite.text_3" .Invite.Email}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="email-footer">
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,107 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.Subject}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#fff7ed }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:inline-block;vertical-align:middle;margin:0 10px;border:0;outline:none }
|
||||
.email-brand { font-size:20px;font-weight:700;color:#9a3412;letter-spacing:-0.3px;vertical-align:middle;font-family:Georgia,'Times New Roman',serif }
|
||||
.email-header { padding:28px 32px 16px;border-bottom:1px solid #fed7aa;background:linear-gradient(135deg,#fff7ed,#ffffff) }
|
||||
|
||||
/* Card */
|
||||
.email-card { max-width:600px;margin:20px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 16px #ea580c14;overflow:hidden;border:1px solid #fed7aa }
|
||||
.email-heading { font-size:19px;font-weight:700;color:#9a3412;margin:0 0 16px;font-family:Georgia,'Times New Roman',serif }
|
||||
|
||||
/* Content */
|
||||
.job-list { list-style:none;padding:0;margin:0 0 24px 0 }
|
||||
.job-item { background-color:#fff7ed;border:1px solid #fed7aa;border-radius:10px;padding:12px 16px;margin-bottom:8px }
|
||||
.job-link { color:#c2410c;text-decoration:none;font-weight:600;font-size:15px;display:block }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { margin:16px 0 24px;border-collapse:separate }
|
||||
.email-btn { display:inline-block;padding:14px 40px;font-size:16px;font-weight:600;color:#ffffff!important;text-decoration:none;background:linear-gradient(135deg,#ea580c,#c2410c);border-radius:50px;font-family:system-ui,sans-serif }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { background:#fff7ed;border:1px solid #fed7aa;border-radius:12px;padding:16px 18px;margin:24px 0 16px }
|
||||
.email-link-label { margin:0 0 8px;font-size:14px;color:#92400e;line-height:1.5 }
|
||||
.email-link-url { margin:0;font-size:13px;color:#c2410c;background:#ffffff;padding:10px 12px;border-radius:8px;border:1px solid #fed7aa;word-break:break-all;font-family:monospace }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:20px 32px 24px;background:#fff7ed;border-top:1px solid #fed7aa }
|
||||
.email-footer-text { margin:0;font-size:14px;color:#92400e }
|
||||
.email-footer-link { color:#ea580c;text-decoration:none;font-weight:600 }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background-color:#fff7ed;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<div class="email-card">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<div class="email-header">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;">
|
||||
<tr>
|
||||
<td style="vertical-align:middle;">
|
||||
<span style="font-size:15px;">👋</span>
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="30" height="30" class="email-logo" />
|
||||
<span class="email-brand">{{AppName}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Card -->
|
||||
<div style="padding:32px 32px 24px;color:#78350f;">
|
||||
|
||||
<h2 class="email-heading">
|
||||
{{.Repo.FullName}} — {{.Run.WorkflowID}}: {{.RunStatusText}}
|
||||
</h2>
|
||||
|
||||
<ul class="job-list">
|
||||
{{range $job := .Jobs}}
|
||||
<li class="job-item">
|
||||
<a href="{{$job.HTMLURL}}" class="job-link">
|
||||
{{$job.Status}}: {{$job.Name}}{{if gt $job.Attempt 1}} (retry #{{$job.Attempt}}){{end}} — {{$job.Duration}}
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
|
||||
<!-- Button -->
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-row">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{.Run.HTMLURL}}" class="email-btn">{{.locale.Tr "mail.view_it_on" AppName}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Fallback link -->
|
||||
<div class="email-link-box">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{.Run.HTMLURL}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="email-footer">
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,95 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.Subject}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#fff7ed }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:inline-block;vertical-align:middle;margin:0 10px;border:0;outline:none }
|
||||
.email-brand { font-size:20px;font-weight:700;color:#9a3412;letter-spacing:-0.3px;vertical-align:middle;font-family:Georgia,'Times New Roman',serif }
|
||||
.email-header { padding:28px 32px 16px;border-bottom:1px solid #fed7aa;background:linear-gradient(135deg,#fff7ed,#ffffff) }
|
||||
|
||||
/* Card */
|
||||
.email-card { max-width:600px;margin:20px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 16px #ea580c14;overflow:hidden;border:1px solid #fed7aa }
|
||||
.email-text { margin:0 0 20px;font-size:16px;line-height:1.6;color:#78350f }
|
||||
|
||||
/* Typography utilities */
|
||||
code { background:#fff7ed;padding:2px 8px;border-radius:6px;font-size:15px;color:#c2410c;font-weight:600 }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { margin:28px 0 24px;border-collapse:separate }
|
||||
.email-btn { display:inline-block;padding:14px 40px;font-size:16px;font-weight:600;color:#ffffff!important;text-decoration:none;background:linear-gradient(135deg,#ea580c,#c2410c);border-radius:50px;font-family:system-ui,sans-serif }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { background:#fff7ed;border:1px solid #fed7aa;border-radius:12px;padding:16px 18px;margin:24px 0 16px }
|
||||
.email-link-label { margin:0 0 8px;font-size:14px;color:#92400e;line-height:1.5 }
|
||||
.email-link-url { margin:0;font-size:13px;color:#c2410c;background:#ffffff;padding:10px 12px;border-radius:8px;border:1px solid #fed7aa;word-break:break-all;font-family:monospace }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:20px 32px 24px;background:#fff7ed;border-top:1px solid #fed7aa }
|
||||
.email-footer-text { margin:0;font-size:14px;color:#92400e }
|
||||
.email-footer-link { color:#ea580c;text-decoration:none;font-weight:600 }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background-color:#fff7ed;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<div class="email-card">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<div class="email-header">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;">
|
||||
<tr>
|
||||
<td style="vertical-align:middle;">
|
||||
<span style="font-size:15px;">👋</span>
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="30" height="30" class="email-logo" />
|
||||
<span class="email-brand">{{AppName}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Card -->
|
||||
<div style="padding:32px 32px 24px;color:#78350f;">
|
||||
|
||||
<p class="email-text">
|
||||
{{.locale.Tr "mail.repo.collaborator.added.text"}} <code>{{.RepoName}}</code>.
|
||||
</p>
|
||||
|
||||
<!-- Button -->
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-row">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{.Link}}" class="email-btn">{{.locale.Tr "mail.view_it_on" AppName}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Fallback link -->
|
||||
<div class="email-link-box">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{.Link}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="email-footer">
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,97 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.Subject}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#fff7ed }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:inline-block;vertical-align:middle;margin:0 10px;border:0;outline:none }
|
||||
.email-brand { font-size:20px;font-weight:700;color:#9a3412;letter-spacing:-0.3px;vertical-align:middle;font-family:Georgia,'Times New Roman',serif }
|
||||
.email-header { padding:28px 32px 16px;border-bottom:1px solid #fed7aa;background:linear-gradient(135deg,#fff7ed,#ffffff) }
|
||||
|
||||
/* Card */
|
||||
.email-card { max-width:600px;margin:20px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 16px #ea580c14;overflow:hidden;border:1px solid #fed7aa }
|
||||
.email-text { margin:0 0 20px;font-size:16px;line-height:1.6;color:#78350f }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { margin:28px 0 24px;border-collapse:separate }
|
||||
.email-btn { display:inline-block;padding:14px 40px;font-size:16px;font-weight:600;color:#ffffff!important;text-decoration:none;background:linear-gradient(135deg,#ea580c,#c2410c);border-radius:50px;font-family:system-ui,sans-serif }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { background:#fff7ed;border:1px solid #fed7aa;border-radius:12px;padding:16px 18px;margin:24px 0 16px }
|
||||
.email-link-label { margin:0 0 8px;font-size:14px;color:#92400e;line-height:1.5 }
|
||||
.email-link-url { margin:0;font-size:13px;color:#c2410c;background:#ffffff;padding:10px 12px;border-radius:8px;border:1px solid #fed7aa;word-break:break-all;font-family:monospace }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:20px 32px 24px;background:#fff7ed;border-top:1px solid #fed7aa }
|
||||
.email-footer-text { margin:0;font-size:14px;color:#92400e }
|
||||
.email-footer-link { color:#ea580c;text-decoration:none;font-weight:600 }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background-color:#fff7ed;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<div class="email-card">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<div class="email-header">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;">
|
||||
<tr>
|
||||
<td style="vertical-align:middle;">
|
||||
<span style="font-size:15px;">👋</span>
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="30" height="30" class="email-logo" />
|
||||
<span class="email-brand">{{AppName}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Card -->
|
||||
<div style="padding:32px 32px 24px;color:#78350f;">
|
||||
{{$repo_url := HTMLFormat "<a href='%s' style='color:#ea580c;text-decoration:none;font-weight:600;'>%s</a>" .Issue.Repo.HTMLURL .Issue.Repo.FullName}}
|
||||
{{$link := HTMLFormat "<a href='%s' style='color:#ea580c;text-decoration:none;font-weight:600;'>#%d</a>" .Link .Issue.Index}}
|
||||
<p class="email-text">
|
||||
{{if .IsPull}}
|
||||
{{.locale.Tr "mail.issue_assigned.pull" .Doer.Name $link $repo_url}}
|
||||
{{else}}
|
||||
{{.locale.Tr "mail.issue_assigned.issue" .Doer.Name $link $repo_url}}
|
||||
{{end}}
|
||||
</p>
|
||||
|
||||
<!-- Button -->
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-row">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{.Link}}" class="email-btn">{{.locale.Tr "mail.view_it_on" AppName}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Fallback link -->
|
||||
<div class="email-link-box">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{.Link}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="email-footer">
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,168 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.Subject}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#fff7ed }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:inline-block;vertical-align:middle;margin:0 10px;border:0;outline:none }
|
||||
.email-brand { font-size:20px;font-weight:700;color:#9a3412;letter-spacing:-0.3px;vertical-align:middle;font-family:Georgia,'Times New Roman',serif }
|
||||
.email-header { padding:28px 32px 16px;border-bottom:1px solid #fed7aa;background:linear-gradient(135deg,#fff7ed,#ffffff) }
|
||||
|
||||
/* Card */
|
||||
.email-card { max-width:600px;margin:20px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 16px #ea580c14;overflow:hidden;border:1px solid #fed7aa }
|
||||
.email-text { margin:0 0 16px;font-size:16px;line-height:1.6;color:#78350f }
|
||||
|
||||
/* Content */
|
||||
blockquote { padding-left:1em;margin:1em 0;border-left:3px solid #fdba74;color:#92400e }
|
||||
pre { background:#fff7ed;padding:12px;border-radius:8px;overflow-x:auto;font-size:14px;line-height:1.5;border:1px solid #fed7aa }
|
||||
hr { border:none;border-top:1px solid #fed7aa;margin:20px 0 }
|
||||
.review-path { margin:8px 0;font-size:14px;color:#92400e }
|
||||
.review-content { padding-left:1em;margin:1em 0 }
|
||||
.review-body { margin-top:8px;font-size:15px;line-height:1.6 }
|
||||
.commit-list { margin:16px 0;padding-left:20px }
|
||||
.commit-item { margin-bottom:6px;font-size:15px }
|
||||
.commit-link { color:#ea580c;text-decoration:none;font-weight:600 }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { margin:28px 0 24px;border-collapse:separate }
|
||||
.email-btn { display:inline-block;padding:14px 40px;font-size:16px;font-weight:600;color:#ffffff!important;text-decoration:none;background:linear-gradient(135deg,#ea580c,#c2410c);border-radius:50px;font-family:system-ui,sans-serif }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { background:#fff7ed;border:1px solid #fed7aa;border-radius:12px;padding:16px 18px;margin:24px 0 16px }
|
||||
.email-link-label { margin:0 0 8px;font-size:14px;color:#92400e;line-height:1.5 }
|
||||
.email-link-url { margin:0;font-size:13px;color:#c2410c;background:#ffffff;padding:10px 12px;border-radius:8px;border:1px solid #fed7aa;word-break:break-all;font-family:monospace }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:20px 32px 24px;background:#fff7ed;border-top:1px solid #fed7aa }
|
||||
.email-footer-text { margin:0;font-size:14px;color:#92400e }
|
||||
.email-footer-link { color:#ea580c;text-decoration:none;font-weight:600 }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background-color:#fff7ed;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<div class="email-card">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<div class="email-header">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;">
|
||||
<tr>
|
||||
<td style="vertical-align:middle;">
|
||||
<span style="font-size:15px;">👋</span>
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="30" height="30" class="email-logo" />
|
||||
<span class="email-brand">{{AppName}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Card -->
|
||||
<div style="padding:32px 32px 24px;color:#78350f;">
|
||||
{{if .IsMention}}
|
||||
<p class="email-text">{{.locale.Tr "mail.issue.x_mentioned_you" .Doer.Name}}</p>
|
||||
{{end}}
|
||||
{{if eq .ActionName "push"}}
|
||||
<p class="email-text">
|
||||
{{if .Comment.IsForcePush}}
|
||||
{{$oldCommitUrl := printf "%s/commit/%s" .Comment.Issue.PullRequest.BaseRepo.HTMLURL .Comment.OldCommit}}
|
||||
{{$oldShortSha := ShortSha .Comment.OldCommit}}
|
||||
{{$oldCommitLink := HTMLFormat "<a href='%[1]s' style='color:#ea580c;text-decoration:none;font-weight:600;'>%[2]s</a>" $oldCommitUrl $oldShortSha}}
|
||||
|
||||
{{$newCommitUrl := printf "%s/commit/%s" .Comment.Issue.PullRequest.BaseRepo.HTMLURL .Comment.NewCommit}}
|
||||
{{$newShortSha := ShortSha .Comment.NewCommit}}
|
||||
{{$newCommitLink := HTMLFormat "<a href='%[1]s' style='color:#ea580c;text-decoration:none;font-weight:600;'>%[2]s</a>" $newCommitUrl $newShortSha}}
|
||||
|
||||
{{.locale.Tr "mail.issue.action.force_push" .Doer.Name .Comment.Issue.PullRequest.HeadBranch $oldCommitLink $newCommitLink}}
|
||||
{{else}}
|
||||
{{.locale.TrN (len .Comment.Commits) "mail.issue.action.push_1" "mail.issue.action.push_n" .Doer.Name .Comment.Issue.PullRequest.HeadBranch (len .Comment.Commits)}}
|
||||
{{end}}
|
||||
</p>
|
||||
{{end}}
|
||||
<p class="email-text">
|
||||
{{if eq .ActionName "close"}}
|
||||
{{.locale.Tr "mail.issue.action.close" .Doer.Name .Issue.Index}}
|
||||
{{else if eq .ActionName "reopen"}}
|
||||
{{.locale.Tr "mail.issue.action.reopen" .Doer.Name .Issue.Index}}
|
||||
{{else if eq .ActionName "merge"}}
|
||||
{{.locale.Tr "mail.issue.action.merge" .Doer.Name .Issue.Index .Issue.PullRequest.BaseBranch}}
|
||||
{{else if eq .ActionName "approve"}}
|
||||
{{.locale.Tr "mail.issue.action.approve" .Doer.Name}}
|
||||
{{else if eq .ActionName "reject"}}
|
||||
{{.locale.Tr "mail.issue.action.reject" .Doer.Name}}
|
||||
{{else if eq .ActionName "review"}}
|
||||
{{.locale.Tr "mail.issue.action.review" .Doer.Name}}
|
||||
{{else if eq .ActionName "review_dismissed"}}
|
||||
{{.locale.Tr "mail.issue.action.review_dismissed" .Doer.Name .Comment.Review.Reviewer.Name}}
|
||||
{{else if eq .ActionName "ready_for_review"}}
|
||||
{{.locale.Tr "mail.issue.action.ready_for_review" .Doer.Name}}
|
||||
{{end}}
|
||||
|
||||
{{- if eq .Body ""}}
|
||||
{{if eq .ActionName "new"}}
|
||||
{{.locale.Tr "mail.issue.action.new" .Doer.Name .Issue.Index}}
|
||||
{{end}}
|
||||
{{else}}
|
||||
{{.Body}}
|
||||
{{end -}}
|
||||
</p>
|
||||
|
||||
{{- range .ReviewComments}}
|
||||
<hr>
|
||||
<p class="review-path">{{$.locale.Tr "mail.issue.in_tree_path" .TreePath}}</p>
|
||||
<div class="review-content">
|
||||
<pre>{{.Patch}}</pre>
|
||||
<div class="review-body">{{.RenderedContent}}</div>
|
||||
</div>
|
||||
{{end -}}
|
||||
|
||||
{{if eq .ActionName "push"}}
|
||||
<ul class="commit-list">
|
||||
{{range .Comment.Commits}}
|
||||
<li class="commit-item">
|
||||
<a href="{{$.Comment.Issue.PullRequest.BaseRepo.HTMLURL}}/commit/{{.UserCommit.GitCommit.ID}}" class="commit-link">
|
||||
{{ShortSha .UserCommit.GitCommit.ID.String}}
|
||||
</a> — {{.UserCommit.GitCommit.MessageTitle}}
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
|
||||
<!-- Button -->
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-row">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{.Link}}" class="email-btn">{{.locale.Tr "mail.view_it_on" AppName}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Fallback link -->
|
||||
<div class="email-link-box">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{.Link}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="email-footer">
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
{{if .CanReply}} {{.locale.Tr "mail.reply"}}{{end}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,129 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
|
||||
<title>
|
||||
{{.Subject}}
|
||||
</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Base */
|
||||
body,.ExternalClass,.ReadMsgBody { background-color:#fff7ed }
|
||||
|
||||
/* Header */
|
||||
.email-logo { display:inline-block;vertical-align:middle;margin:0 10px;border:0;outline:none }
|
||||
.email-brand { font-size:20px;font-weight:700;color:#9a3412;letter-spacing:-0.3px;vertical-align:middle;font-family:Georgia,'Times New Roman',serif }
|
||||
.email-header { padding:28px 32px 16px;border-bottom:1px solid #fed7aa;background:linear-gradient(135deg,#fff7ed,#ffffff) }
|
||||
|
||||
/* Card */
|
||||
.email-card { max-width:600px;margin:20px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 16px #ea580c14;overflow:hidden;border:1px solid #fed7aa }
|
||||
.email-heading { margin:20px 0 8px;font-size:19px;font-weight:700;color:#9a3412;font-family:Georgia,'Times New Roman',serif }
|
||||
.email-subheading { margin:20px 0 8px;font-weight:600;color:#9a3412 }
|
||||
.email-text { margin:0 0 16px;font-size:16px;line-height:1.6;color:#78350f }
|
||||
|
||||
/* Content */
|
||||
blockquote { padding-left:1em;margin:1em 0;border-left:3px solid #fdba74;color:#92400e }
|
||||
ul { margin:12px 0 16px;padding-left:20px }
|
||||
li { margin-bottom:8px;font-size:15px;line-height:1.5;color:#78350f }
|
||||
.email-content { margin:12px 0 16px;font-size:15px;line-height:1.6;color:#78350f }
|
||||
|
||||
/* Button */
|
||||
.email-btn-row { margin:16px 0 24px;border-collapse:separate }
|
||||
.email-btn { display:inline-block;padding:14px 40px;font-size:16px;font-weight:600;color:#ffffff!important;text-decoration:none;background:linear-gradient(135deg,#ea580c,#c2410c);border-radius:50px;font-family:system-ui,sans-serif }
|
||||
|
||||
/* Fallback link */
|
||||
.email-link-box { background:#fff7ed;border:1px solid #fed7aa;border-radius:12px;padding:16px 18px;margin:24px 0 16px }
|
||||
.email-link-label { margin:0 0 8px;font-size:14px;color:#92400e;line-height:1.5 }
|
||||
.email-link-url { margin:0;font-size:13px;color:#c2410c;background:#ffffff;padding:10px 12px;border-radius:8px;border:1px solid #fed7aa;word-break:break-all;font-family:monospace }
|
||||
|
||||
/* Footer */
|
||||
.email-footer { padding:20px 32px 24px;background:#fff7ed;border-top:1px solid #fed7aa }
|
||||
.email-footer-text { margin:0;font-size:14px;color:#92400e }
|
||||
.email-footer-link { color:#ea580c;text-decoration:none;font-weight:600 }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="margin:0;padding:0;background-color:#fff7ed;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||
<center style="width:100%;table-layout:fixed;">
|
||||
<div class="email-card">
|
||||
|
||||
<!-- Floating Title -->
|
||||
<div class="email-header">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;">
|
||||
<tr>
|
||||
<td style="vertical-align:middle;">
|
||||
<span style="font-size:15px;">👋</span>
|
||||
<img src="{{AppUrl}}assets/img/favicon.png" alt="logo" width="30" height="30" class="email-logo" />
|
||||
<span class="email-brand">{{AppName}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Card -->
|
||||
<div style="padding:32px 32px 24px;color:#78350f;">
|
||||
{{$release_url := HTMLFormat "<a href='%s' style='color:#ea580c;text-decoration:none;font-weight:600;'>%s</a>" .Release.HTMLURL .Release.TagName}}
|
||||
{{$repo_url := HTMLFormat "<a href='%s' style='color:#ea580c;text-decoration:none;font-weight:600;'>%s</a>" .Release.Repo.HTMLURL .Release.Repo.FullName}}
|
||||
<p class="email-text">
|
||||
{{.locale.Tr "mail.release.new.text" .Release.Publisher.Name $release_url $repo_url}}
|
||||
</p>
|
||||
|
||||
<h4 class="email-heading">{{.locale.Tr "mail.release.title" .Release.Title}}</h4>
|
||||
|
||||
<p style="margin:0 0 8px;font-weight:600;color:#9a3412;">{{.locale.Tr "mail.release.note"}}</p>
|
||||
{{if .Release.RenderedNote}}
|
||||
<div class="email-content">
|
||||
{{.Release.RenderedNote}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<p class="email-subheading">{{.locale.Tr "mail.release.downloads"}}</p>
|
||||
<ul>
|
||||
{{if not .DisableDownloadSourceArchives}}
|
||||
<li>
|
||||
<a href="{{.Release.Repo.HTMLURL}}/archive/{{.Release.TagName | PathEscapeSegments}}.zip" rel="nofollow" style="color:#ea580c;text-decoration:none;font-weight:600;">{{.locale.Tr "mail.release.download.zip"}}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{.Release.Repo.HTMLURL}}/archive/{{.Release.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow" style="color:#ea580c;text-decoration:none;font-weight:600;">{{.locale.Tr "mail.release.download.targz"}}</a>
|
||||
</li>
|
||||
{{end}}
|
||||
{{if .Release.Attachments}}
|
||||
{{range .Release.Attachments}}
|
||||
<li>
|
||||
<a href="{{.DownloadURL}}" target="_blank" style="color:#ea580c;text-decoration:none;font-weight:600;">{{.Name}} ({{.Size | FileSize}})</a>
|
||||
</li>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</ul>
|
||||
|
||||
<!-- Button -->
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="email-btn-row">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{.Link}}" class="email-btn">{{.locale.Tr "mail.view_it_on" AppName}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Fallback link -->
|
||||
<div class="email-link-box">
|
||||
<p class="email-link-label">{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
|
||||
<p class="email-link-url">{{.Link}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="email-footer">
|
||||
<p class="email-footer-text">
|
||||
©
|
||||
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||