34 Commits
Author SHA1 Message Date
KenanZhu b8089e8a7e chore: ready for release 28.1.0
Validate / Validate (push) Successful in 2m49s
Validate / Validate (push) Successful in 2m39s
Release / Validate (push) Successful in 2m39s
Release / Package (push) Successful in 6m33s
2026-10-12 10:32:26 +08:00
KenanZhu cb9382ade4 docs: modify contribution guides
Validate / Validate (push) Successful in 2m47s
2026-10-12 10:18:29 +08:00
KenanZhu 8ccb5ac443 docs: modifiy COMPATIBILITY. 2026-10-12 09:34:50 +08:00
KenanZhu 3d7cf17fb1 docs: modify README. 2026-10-12 09:27:33 +08:00
KenanZhu bcf1b87e82 preview: remove screenshot export and restore UI animations
Validate / Validate (push) Successful in 2m52s
2026-10-11 16:59:00 +08:00
KenanZhu 6733e81356 preview: improve sidebar layout and screenshot export
Validate / Validate (push) Successful in 3m29s
2026-10-11 11:05:48 +08:00
KenanZhu e51e2e9996 docs: align English compatibility guide with Chinese version 2026-10-11 11:04:41 +08:00
KenanZhu 5f5ad058a8 chore: split validation and release workflows and refactor packaging 2026-10-11 09:53:25 +08:00
KenanZhu 5b06a795e4 preview: hide email preview scrollbars
Release / Validate Templates (push) Successful in 2m53s
Release / Package & Release (push) Skipped
Release / Remind to Update Release Documentation (push) Skipped
2026-10-10 15:03:44 +08:00
KenanZhu c93f2bf8b2 chore: simplify workflows and unify tool logging and mail presentation
Release / Validate Templates (push) Successful in 2m55s
Release / Package & Release (push) Skipped
Release / Remind to Update Release Documentation (push) Skipped
2026-10-10 14:54:40 +08:00
KenanZhu f4d96de79e chore: refresh docs and adapt workflows for Gitea
Release / Validate Templates (push) Successful in 3m1s
Release / Package & Release (push) Skipped
Release / Update Latest Release Documentation (push) Skipped
2026-10-09 22:02:33 +08:00
KenanZhu fec3ace600 chore: migrate mail themes to shared framework and locked upstream inputs
Release / Validate Templates (push) Canceled after 0s
Release / Package & Release (push) Canceled after 0s
Release / Update Latest Release Documentation (push) Canceled after 0s
2026-10-09 18:34:36 +08:00
KenanZhu 5c0589f6f6 docs: clarify per-version Gitea compatibility
Release / Validate Templates (push) Canceled after 0s
Release / Package & Release (push) Canceled after 0s
Release / Update Latest Release Documentation (push) Canceled after 0s
2026-10-09 12:27:00 +08:00
KenanZhu 14dc37046d refactor: unify markdown release tracking blocks
Release / Validate Templates (push) Canceled after 0s
Release / Package & Release (push) Canceled after 0s
Release / Update Latest Release Documentation (push) Canceled after 0s
2026-10-09 12:10:12 +08:00
KenanZhu 23f0456622 chore: streamline compatibility tracking and documentation
Release / Validate Templates (push) Canceled after 0s
Release / Package & Release (push) Canceled after 0s
2026-10-09 11:43:02 +08:00
KenanZhu 92d2ba9889 fix: support Gitea 28.0.0 mail templates
Release / Validate Templates (push) Canceled after 0s
Release / Package & Release (push) Canceled after 0s
2026-10-02 19:44:20 +08:00
KenanZhu ffb674669b chore: prepare v1.27.3 release and guard template publishing
Release / Validate Templates (push) Canceled after 0s
Release / Package & Release (push) Canceled after 0s
2026-09-23 14:15:47 +08:00
KenanZhu 4ff008e564 docs: clarify Gitea 1.27.3 fix is on main, not yet released 2026-09-23 14:02:59 +08:00
KenanZhu dfb5024dfb fix: verify Gitea 1.27.3 and repair push mail templates 2026-09-23 13:51:22 +08:00
KenanZhuandClaude a4ba20418c docs: track Gitea 1.27.2 — verified compatible (annotation versioning)
Release / Validate Templates (push) Canceled after 0s
Release / Package & Release (push) Canceled after 0s
Gitea 1.27.2 (2026-08-14, security + bugfix release) contains no mail
template changes — verified against the local gitea clone: only
modules/templates/util_render.go (avatar-stack UI fix) differs from
1.27.1, templates/mail and services/mailer are untouched.

Versioning now uses the annotation scheme: release numbers stay on the
project's own scheme and the supported Gitea version is appended in
parentheses — v1.0.1(v1.27.2). Synced everywhere:
- COMPATIBILITY.md: active row v1.0.1(v1.27.2), latest verified 1.27.2,
  Versioning section reworded, 1.27.2 history row added
- README.md / docs/README.zh-CN.md: badge, release line, latest tested,
  versioning bullets
- AGENTS.md: annotation scheme + current release v1.0.1 (tracks 1.27.2)
- gitea-tracker.yml: PR checklist now updates the parenthesized Gitea
  version instead of bumping the release number; new tags only when
  template content changes
- CONTRIBUTING.md: simplified Translations section

The local v1.27.1 tag was removed — it conflicts with the annotation
scheme; releases keep v1.0.x tags.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 12:01:46 +08:00
KenanZhuandClaude 3f6a2851c7 docs: keep only bilingual (English + Simplified Chinese) README and CONTRIBUTING
Remove ja/ko/ru/zh-TW translations of README.md and CONTRIBUTING.md.
The tracker workflow now only syncs docs/README.zh-CN.md; language links
in README.md, CONTRIBUTING.md, and the AGENTS.md layout are updated to
the two maintained languages.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 11:46:50 +08:00
KenanZhuandClaude e06e215d2a docs: adopt semantic versioning — release 1.27.1 tracks Gitea 1.27.1
Templates were re-checked against the local gitea clone (v1.27.1 tag): all
46 translation keys exist in locale_en-US, all data paths match the official
templates, and all 110 templates render via `go run . preview all` — no
template changes required.

From now on release versions mirror the Gitea version they support:
- COMPATIBILITY.md: quick reference now leads with release 1.27.1
  (max tested Gitea 1.27.1, active), v1.0.x marked superseded; new
  Versioning section documents the scheme
- README: release version in header + versioning bullet
- Tracker workflow PR body: added semantic-versioning release checklist
  (bump Template Release column, tag vX.Y.Z)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 11:46:04 +08:00
KenanZhuandClaude d020e35f77 refactor(themes): convert all rgba() to #RRGGBBAA hex notation
453 occurrences, 37 unique rgba() values converted across 66 files.
All 110 templates (10 themes × 11 types) render successfully.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-02 11:50:25 +08:00
KenanZhuandClaude ac559eb26c style(aurora): add email-text class to release template paragraphs
Add missing .email-text CSS class to release note paragraphs
for consistent typography styling.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-02 11:48:56 +08:00
KenanZhuandClaude f33cad7a7e fix: update push commit data paths for Gitea 1.27.1 compatibility
Gitea 1.27.1 fixed the mail template for push events (#38467) —
commit data must now be accessed via .UserCommit.GitCommit instead
of directly on the commit object.

Changed across all 10 themes:
- {{.ID}}               → {{.UserCommit.GitCommit.ID}}
- {{.ID.String}}        → {{.UserCommit.GitCommit.ID.String}}
- {{.MessageTitle}}     → {{.UserCommit.GitCommit.MessageTitle}}

Verification:
- 3 mail-related files changed in Gitea v1.27.0→v1.27.1
- All 110 templates (10 themes × 11 types) render successfully
- No breaking changes — data structure unchanged, only access path

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-02 11:18:22 +08:00
KenanZhu 070858d473 docs: track Gitea 1.27.1 — pending verification 2026-08-01 10:01:28 +00:00
KenanZhuandClaude 0fd9afa671 docs: verify Gitea 1.27.0 compatibility — no mail template changes
Verification results:
- 0 files changed in templates/mail/ or services/mailer/ between v1.26.0 and v1.27.0
- All 110 templates (10 themes × 11 types) render successfully
- No new mail template types, no removed types
- No changes to template functions or data contexts
- No breaking changes for mail templates

Marking v1.0.1 as compatible with Gitea 1.27.0.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 11:54:38 +08:00
KenanZhu 349ce602f3 docs: track Gitea 1.27.0 — pending verification 2026-07-14 03:43:30 +00:00
KenanZhuandClaude e7a2fef953 docs: add v1.0.1 to compatibility matrix
Release / Validate Templates (push) Canceled after 0s
Release / Package & Release (push) Canceled after 0s
v1.0.1 is a patch release with:
- Floating collapsible preview panel
- Ink theme mobile layout fixes
- Responsive topbar with multi-row wrapping
- CSS overflow prevention for mobile viewports

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-26 14:52:39 +08:00
KenanZhuandClaude 112a061fde feat(preview): responsive topbar with multi-row wrapping
Three-tier responsive system for the navigation bar:

Structure:
- Each label+select pair wrapped in .ctl-group (inline-flex,
  white-space:nowrap) so they never split across rows.
- Separators gain flex-shrink:0;align-self:center for stability.

Breakpoints:
- Default: single row, all controls + hint visible, compact 8px gap
- ≤900px: hint text hidden (first sacrifice for space)
- ≤700px: separators hidden, tighter gaps/padding, smaller fonts
- ≤500px: ultra-compact — min select widths, smaller brand

CSS changes:
- height → min-height so bar grows vertically when wrapping
- flex-wrap:wrap enables natural multi-row flow
- row-gap:4px keeps wrapped rows visually connected
- Separators hidden on narrow screens (gap provides spacing)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-26 14:34:26 +08:00
KenanZhuandClaude 433b5f9259 fix(ink): prevent content overflow on mobile viewports
Five targeted fixes applied to all 11 ink templates:

1. Convert table/tr/tbody to display:block on mobile so the
   block-level stacking of <td> elements works correctly.
2. Add box-sizing:border-box to all <td> elements to prevent
   padding from pushing content beyond the viewport width.
3. Add word-break:break-word + overflow-wrap:break-word to
   table, td, th elements to force long text to wrap.
4. Add white-space:pre-wrap + word-break:break-all to <pre>
   and <code> to prevent horizontal scroll from code blocks.
5. Add max-width:100% + height:auto to all images, and
   max-width:100% to nested tables.
6. Remove invalid table-layout:fixed from <center> tag
   (was CSS property on wrong element — had no effect).

Also reduced mobile padding from 28px to 16px for more
usable content width on narrow screens.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-26 14:27:38 +08:00
KenanZhuandClaude f2a3c0f033 fix(ink): add .email-sidebar class for vertical stacking on mobile
Quick Info sidebar now stacks below main content at <=480px viewports
via CSS class instead of fragile inline styles.

- Added .email-sidebar base style (extracted from inline styles)
- Added responsive rule: width:100%, display:block, removes left border,
  adds top border separator when stacked vertically
- All 11 ink templates updated consistently

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-26 14:20:38 +08:00
KenanZhuandClaude f85520a808 feat(preview): convert side panel to floating collapsible overlay with collapsible sections
Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-26 14:16:36 +08:00
KenanZhuandClaude fd527b01a5 style(ink): update ink theme templates
Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-26 14:08:49 +08:00
235 changed files with 5795 additions and 16737 deletions

No files matched your search

+8
View File
@@ -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
+245
View File
@@ -0,0 +1,245 @@
"""Package verified generated mail overrides; never copy stale build directories."""
import argparse
import hashlib
import io
import json
import re
import tarfile
import zipfile
from pathlib import Path, PurePosixPath
VERSION_PATTERN = re.compile(r"v[0-9]+\.[0-9]+\.[0-9]+")
THEME_NAME_PATTERN = re.compile(r"[a-z][a-z0-9-]*")
BASE_TEMPLATES = {"mail/base/head.tmpl", "mail/base/footer.tmpl"}
REQUIRED_FILES = (
"LICENSE",
"README.md",
"CONTRIBUTING.md",
"COMPATIBILITY.md",
"docs/README.zh-CN.md",
"docs/CONTRIBUTING.zh-CN.md",
"preview/index.html",
)
OPTIONAL_ROOT_DOCUMENTS = ("AGENTS.md", "THIRD_PARTY_NOTICES.md")
DOCUMENTATION_DIRECTORIES = ("docs", ".github/release-notes")
PREVIEW_MANIFEST_PREFIX = b"window.__MAIL_PREVIEW__ = "
ARCHIVE_EXTENSIONS = (".zip", ".tar.gz")
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 read_json(path):
return json.loads(read_file(path))
def load_lock(root, version):
"""Validate the requested release identity before collecting any output."""
if not VERSION_PATTERN.fullmatch(version):
raise ValueError("Expected a stable vX.Y.Z package version")
lock = read_json(root / "gitea.lock.json")
if version != lock["tag"]:
raise ValueError("Package version must match the locked Gitea tag")
return lock
def discover_theme_names(root):
names = sorted(
path.name
for path in (root / "themes").iterdir()
if path.is_dir() and not path.is_symlink()
)
if not names:
raise ValueError("No source theme manifests")
return names
def expected_template_paths(root, lock, mode):
"""Shared themes override base files; framed themes also supply all bodies."""
paths = set(BASE_TEMPLATES)
if mode == "framed":
paths.update(
path.removeprefix("templates/")
for path in lock["files"]
if path.startswith("templates/mail/") and path.endswith(".tmpl")
)
paths.update(
path.relative_to(root / "framework").as_posix()
for path in (root / "framework" / "mail").rglob("*.tmpl")
)
return paths
def collect_theme(root, lock, name):
"""Accept only a complete build whose identity and checksums still match."""
if not THEME_NAME_PATTERN.fullmatch(name):
raise ValueError(f"Invalid theme name: {name}")
source = root / "themes" / name
theme = read_json(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
metadata = read_json(built / "build.json")
actual_identity = (
metadata["theme"], metadata["mode"],
metadata["gitea_tag"], metadata["gitea_commit"],
)
expected_identity = (name, theme["mode"], lock["tag"], lock["commit"])
if actual_identity != expected_identity:
raise ValueError(f"Stale build identity: {name}")
has_sources = {"theme.json", "theme.css"}.issubset(metadata.get("sources") or {})
if not has_sources or not metadata.get("files"):
raise ValueError(f"Missing source/file provenance: {name}")
if set(metadata["files"]) != expected_template_paths(root, lock, theme["mode"]):
raise ValueError(f"Incomplete generated install package: {name}")
for path, expected in metadata["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}")
files = {}
for path, expected in metadata["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")
return files
def parse_script_payload(data, prefix):
"""Read generated JSON without executing the surrounding JavaScript."""
return json.loads(data.split(prefix, 1)[1].strip().removesuffix(b";"))
def collect_preview(root, lock, theme_names):
manifest_data = read_file(root / "preview" / "rendered.js")
manifest = parse_script_payload(manifest_data, PREVIEW_MANIFEST_PREFIX)
languages = sorted(
path.removeprefix("options/locale/locale_").removesuffix(".json")
for path in lock["files"]
if path.startswith("options/locale/locale_") and path.endswith(".json")
)
matches_build = (
manifest["upstream"] == lock["tag"]
and manifest.get("upstream_commit") == lock["commit"]
and sorted(manifest["themes"]) == theme_names
and sorted(manifest["languages"]) == languages
)
if not matches_build:
raise ValueError(
"Preview does not match lock/all source themes and languages; run preview all"
)
files = {"preview/rendered.js": manifest_data}
expected_templates = set(manifest["registry"])
for language in languages:
data = read_file(root / "preview" / "rendered" / (language + ".js"))
prefix = ('window.__MAIL_PREVIEW_DATA__[' + json.dumps(language) + '] = ').encode()
payload = parse_script_payload(data, prefix)
if sorted(payload) != theme_names or any(
set(payload[name]) != expected_templates for name in theme_names
):
raise ValueError(f"Incomplete preview language bundle: {language}")
files[f"preview/rendered/{language}.js"] = data
return files
def collect_documentation(root):
files = {name: read_file(root / name) for name in REQUIRED_FILES}
for name in OPTIONAL_ROOT_DOCUMENTS:
if (root / name).exists():
files[name] = read_file(root / name)
for directory in DOCUMENTATION_DIRECTORIES:
for path in sorted((root / 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)
return files
def collect_provenance(root, lock):
license_data = read_file(root / "build" / "upstream" / "LICENSE")
if digest(license_data) != lock["files"]["LICENSE"]:
raise ValueError("Upstream license checksum mismatch")
return {
"GITEA-LICENSE": license_data,
"upstream-lock.json": read_file(root / "gitea.lock.json"),
}
def collect(root, version):
"""Collect verified release content without writing any archives."""
root = Path(root)
lock = load_lock(root, version)
theme_names = discover_theme_names(root)
files = {}
for name in theme_names:
files.update(collect_theme(root, lock, name))
files.update(collect_preview(root, lock, theme_names))
files.update(collect_documentation(root))
files.update(collect_provenance(root, lock))
return files
def package(root, version, output):
"""Write both archive formats from the same verified file collection."""
files = collect(root, version)
archive = f"gitea-mail-templates-{version}"
output = Path(output)
targets = [output / (archive + ext) for ext in ARCHIVE_EXTENSIONS]
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 = len(data)
info.mode = 0o644
tar.addfile(info, io.BytesIO(data))
print(f"[PASS] Packaged {len(files)} verified files: {targets[0]}, {targets[1]}")
def 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")
if __name__ == "__main__":
main()
+200
View File
@@ -0,0 +1,200 @@
"""Offline packaging tests using a minimal, independently generated repository."""
import importlib.util
import json
import tarfile
import tempfile
import unittest
import zipfile
from pathlib import Path
SCRIPT_PATH = Path(__file__).with_name("package_release.py")
SPEC = importlib.util.spec_from_file_location("package_release", SCRIPT_PATH)
PACKER = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(PACKER)
class PackagingTests(unittest.TestCase):
VERSION = "v28.0.0"
COMMIT = "a" * 40
THEME = "demo"
LANGUAGE = "en-US"
ROOT_DOCUMENTS = (
"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",
)
def setUp(self):
temporary_directory = tempfile.TemporaryDirectory()
self.addCleanup(temporary_directory.cleanup)
self.root = Path(temporary_directory.name)
self.output = self.root / "artifacts"
self.create_theme()
self.create_snapshot()
self.create_preview()
for name in self.ROOT_DOCUMENTS:
self.write(name, b"test")
# Fixture helpers keep test bodies focused on the behavior being checked.
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("utf-8"))
def create_theme(self):
theme_data = json.dumps({"name": self.THEME, "mode": "shared"}).encode("utf-8")
css_data = b"a { color:blue; }"
self.write("themes/demo/theme.json", theme_data)
self.write("themes/demo/theme.css", css_data)
generated_files = {
"mail/base/head.tmpl": b"<title>test</title>",
"mail/base/footer.tmpl": b"<p>footer</p>",
}
metadata = {
"theme": self.THEME,
"mode": "shared",
"gitea_tag": self.VERSION,
"gitea_commit": self.COMMIT,
"sources": {
"theme.json": PACKER.digest(theme_data),
"theme.css": PACKER.digest(css_data),
},
"files": {
path: PACKER.digest(data)
for path, data in generated_files.items()
},
}
for path, data in generated_files.items():
self.write("build/themes/demo/" + path, data)
self.write_json("build/themes/demo/build.json", metadata)
def create_snapshot(self):
self.write_json("gitea.lock.json", {
"tag": self.VERSION,
"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")
def create_preview(self):
manifest = {
"upstream": self.VERSION,
"upstream_commit": self.COMMIT,
"themes": [self.THEME],
"languages": [self.LANGUAGE],
"registry": {"activate": {}},
}
bundle = {self.THEME: {"activate": "<html></html>"}}
self.write("preview/rendered.js", (
"window.__MAIL_PREVIEW__ = " + json.dumps(manifest) + ";"
).encode("utf-8"))
self.write("preview/rendered/en-US.js", (
'window.__MAIL_PREVIEW_DATA__["en-US"] = ' + json.dumps(bundle) + ";"
).encode("utf-8"))
# Archives: both formats contain the verified bytes, never stale build files.
def test_archives_contain_only_verified_outputs(self):
self.write("build/themes/stale/mail/stale.tmpl", b"stale")
expected_files = PACKER.collect(self.root, self.VERSION)
prefix = f"gitea-mail-templates-{self.VERSION}/"
expected_members = {
prefix + name: data for name, data in expected_files.items()
}
PACKER.package(self.root, self.VERSION, self.output)
with zipfile.ZipFile(self.output / f"gitea-mail-templates-{self.VERSION}.zip") as archive:
zipped = {name: archive.read(name) for name in archive.namelist()}
with tarfile.open(self.output / f"gitea-mail-templates-{self.VERSION}.tar.gz") as archive:
tarred = {
member.name: archive.extractfile(member).read()
for member in archive.getmembers()
}
self.assertTrue(all(member.mode == 0o644 for member in archive.getmembers()))
self.assertEqual(expected_members, zipped)
self.assertEqual(expected_members, tarred)
self.assertIn(prefix + "themes/demo/mail/base/head.tmpl", zipped)
self.assertFalse(any("stale" in name or "/build/" in name for name in zipped))
def test_existing_archives_are_not_overwritten(self):
PACKER.package(self.root, self.VERSION, self.output)
original_archives = {
path.name: path.read_bytes() for path in self.output.iterdir()
}
with self.assertRaisesRegex(ValueError, "Refusing to overwrite"):
PACKER.package(self.root, self.VERSION, self.output)
self.assertEqual(original_archives, {
path.name: path.read_bytes() for path in self.output.iterdir()
})
# Validation: release identity, source checksums and generated checksums.
def test_wrong_version_is_rejected(self):
with self.assertRaisesRegex(ValueError, "must match the locked Gitea tag"):
PACKER.collect(self.root, "v28.1.0")
def test_changed_source_is_rejected(self):
self.write("themes/demo/theme.css", b"changed")
with self.assertRaisesRegex(ValueError, "Source changed"):
PACKER.collect(self.root, self.VERSION)
def test_missing_language_bundle_is_rejected(self):
(self.root / "preview/rendered/en-US.js").unlink()
with self.assertRaisesRegex(ValueError, "Expected regular file"):
PACKER.collect(self.root, self.VERSION)
def test_modified_generated_files_are_rejected(self):
self.write("build/themes/demo/mail/base/head.tmpl", b"modified")
with self.assertRaisesRegex(ValueError, "checksum mismatch"):
PACKER.collect(self.root, self.VERSION)
# Documentation: recursive inclusion and optional root files.
def test_nested_documentation_and_release_notes_are_included(self):
documents = {
"docs/INDEX.zh-CN.md": "中文索引".encode("utf-8"),
"docs/images/README.zh-CN.md": "截图指南".encode("utf-8"),
"docs/release-notes/v28.0.0.zh-CN.md": "发布说明".encode("utf-8"),
".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.VERSION)
for name, data in documents.items():
with self.subTest(document=name):
self.assertEqual(data, files[name])
self.assertIn("AGENTS.md", files)
self.assertNotIn("docs/private.tmp", files)
def test_optional_root_documents_may_be_absent(self):
for name in ("AGENTS.md", "THIRD_PARTY_NOTICES.md"):
(self.root / name).unlink()
files = PACKER.collect(self.root, self.VERSION)
self.assertNotIn("AGENTS.md", files)
self.assertNotIn("THIRD_PARTY_NOTICES.md", files)
self.assertIn("GITEA-LICENSE", files)
if __name__ == "__main__":
unittest.main()
-131
View File
@@ -1,131 +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: COMPATIBILITY.md — latest verified note
if: steps.version.outputs.skip != 'true'
run: |
NEW="${{ steps.version.outputs.new_version }}"
# Update version inline on the line with TRACKER:LATEST-VERIFIED
sed -i "/TRACKER:LATEST-VERIFIED/s/Gitea [0-9.]*/Gitea ${NEW}/" COMPATIBILITY.md
- name: README.md — badge and latest tested
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
# Latest tested: update version on the TRACKER:LATEST-TESTED line
sed -i "/TRACKER:LATEST-TESTED/s/Gitea [0-9.]*/Gitea ${NEW}/" README.md
- name: README translations — latest tested
if: steps.version.outputs.skip != 'true'
run: |
NEW="${{ steps.version.outputs.new_version }}"
for f in docs/README.{ja,ko,ru,zh-CN,zh-TW}.md; do
sed -i "/TRACKER:LATEST-TESTED/s/Gitea [0-9.]*/Gitea ${NEW}/" "$f"
done
- 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`
- Updates version badges in `README.md` and translations
- 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
- [ ] Update status from ⏳ to ✅ (compatible) or ❌ (breaking) in COMPATIBILITY.md
- [ ] Update the `Mail Template Changes` and `Breaking?` columns with accurate notes
labels: compatibility, automated
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+56 -32
View File
@@ -2,48 +2,72 @@ name: Release
on: on:
push: push:
branches: [main]
tags: ['v*'] tags: ['v*']
jobs: jobs:
validate: validate:
name: Validate Templates name: Validate
runs-on: ubuntu-latest uses: ./.github/workflows/validate.yml
steps: permissions:
- uses: actions/checkout@v4 contents: read
- uses: actions/setup-go@v5
with:
go-version: '1.21'
- name: Lint — render all templates
working-directory: tools
run: go run . preview all
package: package:
name: Package & Release name: Package
if: startsWith(github.ref, 'refs/tags/v')
needs: validate needs: validate
runs-on: ubuntu-latest runs-on: linux-amd64-docker-small
permissions: permissions:
contents: write contents: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Package themes/
run: |
VERSION=${GITHUB_REF#refs/tags/}
ARCHIVE="gitea-mail-templates-${VERSION}"
mkdir -p "dist/${ARCHIVE}"
cp -r themes LICENSE README.md COMPATIBILITY.md "dist/${ARCHIVE}/"
cd dist
zip -r "${ARCHIVE}.zip" "${ARCHIVE}"
tar -czf "${ARCHIVE}.tar.gz" "${ARCHIVE}"
- name: Create Release
uses: softprops/action-gh-release@v2
with: with:
files: dist/*.zip,dist/*.tar.gz token: ${{ secrets.GITEA_TOKEN }}
generate_release_notes: true
- uses: actions/setup-go@v5
with:
go-version: '1.25.x'
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Verify tag matches the snapshot
run: |
python - <<'PY'
import json
import os
with open("gitea.lock.json", encoding="utf-8") as lock_file:
lock = json.load(lock_file)
assert lock["tag"] == os.environ["GITHUB_REF_NAME"], (
"Release tag must match the pinned Gitea tag"
)
PY
- name: Generate preview
working-directory: tools
run: go run . preview all
- name: Package themes and preview
run: python -B .github/scripts/package_release.py --version "$TEMPLATE_RELEASE"
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} TEMPLATE_RELEASE: ${{ github.ref_name }}
- name: Prepare release runtime
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Release
uses: actions/gitea-release-action@v1.3.7
with:
server_url: ${{ github.server_url }}
repository: ${{ github.repository }}
token: ${{ secrets.GITEA_TOKEN }}
name: ${{ github.ref_name }}
tag_name: ${{ github.ref_name }}
body: ''
draft: false
prerelease: false
files: |
dist/gitea-mail-templates-${{ github.ref_name }}.zip
dist/gitea-mail-templates-${{ github.ref_name }}.tar.gz
+42
View File
@@ -0,0 +1,42 @@
name: Validate
on:
push:
branches: ['**']
pull_request:
workflow_call:
permissions:
contents: read
jobs:
validate:
name: Validate
runs-on: linux-amd64-docker-small
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.25.x'
- 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 snapshot
working-directory: tools
run: |
go run . upstream prepare
go run . upstream verify
- name: Tools test
working-directory: tools
run: go test ./...
- name: Build and render all themes and official languages
working-directory: tools
run: go run . preview all
+10 -5
View File
@@ -1,7 +1,10 @@
# Dependencies # Go dependencies
node_modules/
vendor/ vendor/
# Python test cache
__pycache__/
*.pyc
# OS files # OS files
.DS_Store .DS_Store
Thumbs.db Thumbs.db
@@ -18,8 +21,10 @@ Desktop.ini
*.zip *.zip
*.tar.gz *.tar.gz
# Package lock file
package-lock.json
# Preview generated files # Preview generated files
preview/rendered.js preview/rendered.js
preview/rendered/
build/
/upstream/
dist/
node_modules/
-81
View File
@@ -1,81 +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/ # Multi-language documentation
```
## 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
## 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)
+49 -81
View File
@@ -1,98 +1,66 @@
# Gitea Compatibility # Gitea Compatibility
---
[简体中文](docs/COMPATIBILITY.zh-CN.md)
This document tracks the compatibility between **Gitea Mail Templates** releases and **Gitea** versions. This document describes the compatibility of Gitea Mail Templates with different Gitea versions.
## Quick Reference ## Compatibility Matrix
<!-- TRACKER:QUICK-REF-MAX OFFSET=3 --> Select the recommended template release for the Gitea version you run. `[PASS]` identifies a combination recorded as compatible; `[PENDING]` means verification is incomplete.
| Template Release | Min Gitea | Max Tested Gitea | Status |
|-----------------|-----------|-----------------|--------|
| **v1.0.0** | **1.25.0** | **1.26.4** | ✅ Active |
> **Latest verified:** All 11 templates pass validation against Gitea 1.26.4 data contexts. <!-- TRACKER:LATEST-VERIFIED --> > [!NOTE]
> Historical entries reflect the project's existing compatibility assessments. They do not mean that every patch version is retested for each release.
## Check Your Gitea Version | Gitea version | Recommended template release | Status | Notes |
|---|---|---|---|
| 28.1.0 | **v28.1.0** | [PASS] | - |
| 28.0.0 | **v28.1.0** | [PASS] | Gitea replaced the mail function `FileSize` with `FormatByteSize` |
| 1.27.3 | **v1.27.3** | [PASS] | Pull request push notification commit links fixed in every theme |
| 1.27.2 | **v1.27.3** | [PASS] | The matching v1.27.2 template release has push notification defects in Bloom, Ember and Heritage |
| 1.27.1 | **v1.27.3** | [PASS] | Older v1.0.x templates use obsolete push commit fields |
| 1.27.0 | — | [PENDING] | Gitea uses a new commit data structure; v1.27.3 may be compatible but has not been verified against this version |
| 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] | — |
```bash > [!NOTE]
# On your Gitea server: > Gitea 1.24.x and earlier use a different mail template layout and are `[UNSUPPORTED]`; Gitea Mail Templates no longer provides separate support for them. The v1.0.0 template tag has been superseded by v1.0.1 and is no longer recommended separately.
gitea --version
# Or check the web UI footer / Site Administration → Monitoring
```
## Gitea Version History — Mail Template Impact ## Gitea Version History — Mail Template Impact
<!-- TRACKER:VERSION-INSERT OFFSET=2 --> This table describes changes in **Gitea**, not fixes in this template repository. “None” means that the Gitea version introduces no relevant mail template changes; recommended archives are listed in the compatibility matrix above.
| Gitea | Release Date | Mail Template Changes | Breaking? |
|-------|-------------|----------------------|-----------| | Gitea | Release date | Mail template changes | Impact |
| **1.26.4** | 2026-06-21 | None — hotfix release | No | |---|---|---|---|
| **1.26.3** | 2026-06-20 | None — security release | No | | **28.1.0** | 2026-10-06 | None | None |
| **1.26.2** | 2026-05-20 | None — security + bug fixes | No | | **28.0.0** | 2026-09-29 | Mail function `FileSize` renamed to `FormatByteSize` ([PR](https://github.com/go-gitea/gitea/pull/39083)) | **Breaking change:** old release attachment templates fail |
| **1.26.1** | 2026-04-22 | None — bug fixes | No | | | | Introduced `templates/mail/base/` ([PR](https://github.com/go-gitea/gitea/pull/38396)) | **Breaking change:** older Gitea versions cannot directly use the new mail templates |
| **1.26.0** | 2026-04-19 | AppURL cleanup; SanitizeHTML deprecated → use HTMLFormat | No | | **1.27.3** | 2026-08-29 | None | None |
| **1.25.5** | 2026-03-10 | None — security + maintenance | No | | **1.27.2** | 2026-08-14 | None | None |
| **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.27.1** | 2026-07-27 | Bundled push template updated to use `.UserCommit.GitCommit` ([PR](https://github.com/go-gitea/gitea/pull/38467)) | **Breaking change: an existing defect remains unresolved.** Old custom pull request push templates can fail |
| **≤ 1.24.x** | — | Flat directory structure under `custom/templates/mail/` | ❌ Unsupported | | **1.27.0** | 2026-07-13 | Push commit data moved under `.UserCommit.GitCommit` ([ISSUE](https://github.com/go-gitea/gitea/issues/38469)) | **Breaking change:** old pull request push templates 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 ([PR](https://github.com/go-gitea/gitea/pull/35150)) | **Breaking change:** older template layouts are unsupported |
| **≤ 1.24.x** | — | Legacy custom mail layout | [UNSUPPORTED] |
## Template Variable Reference ## 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 the context for each mail type. For specific mail templates, functions and variables used during development, refer to the official mail template documentation [1](#notes).
### Template Functions (available in all templates) ## Translation Keys
| Function | Since Gitea | Notes | Official templates reference `mail.*` and `actions.runs.attempt`. AST-based checks cover `.locale.Tr`, `$.locale.Tr` and both plural keys in `TrN`, including nested pipelines. Every referenced key must exist in the locked English catalog.
|----------|------------|-------|
| `AppName` | ≤ 1.21 | Application name |
| `AppUrl` | ≤ 1.21 | Application base URL |
| `AppDomain` | ≤ 1.21 | Server domain |
| `DotEscape` | ≤ 1.21 | Prevents auto-linking of dotted text |
| `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 |
| `HTMLFormat` | ≤ 1.21 | Render string as safe HTML |
| `Iif` | ≤ 1.21 | Inline conditional |
| `dict` | ≤ 1.21 | Build maps from key-value pairs |
| `Eval` | ≤ 1.21 | Evaluate template tokens |
| `StringUtils` | ≤ 1.21 | String manipulation helpers |
| `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 ---
### Notes
| Template | Key Variables | [1](#notes): [Mail templates | Gitea Documentation](https://docs.gitea.com/administration/mail-templates/)
|----------|--------------|
| `user/auth/activate` | `DisplayName`, `Code`, `ActiveCodeLives` |
| `user/auth/activate_email` | `DisplayName`, `Code`, `Email`, `ActiveCodeLives` |
| `user/auth/register_notify` | `DisplayName`, `Username` |
| `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/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/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.
### Translation Keys
All templates use Gitea's official `mail.*` translation namespace. Keys are stable across Gitea 1.25+.
## 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
## Automated 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`.
## 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
+341 -81
View File
@@ -1,104 +1,364 @@
# Contributing to Gitea Mail Templates # Contributing
Thanks for your interest in contributing! This project aims to provide a diverse, well-maintained collection of email templates for the Gitea ecosystem.
--- ---
## Ways to Contribute [简体中文](docs/CONTRIBUTING.zh-CN.md)
### Adding a New Style Contributions to themes, tools, documentation and translations are welcome. This guide covers the development environment, command-line tools and contribution workflow. For template installation and release selection, see the [README](README.md#installation) and [compatibility matrix](COMPATIBILITY.md#compatibility-matrix).
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 ## Local Development
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 Use **Go 1.24 or later**. From the repository root, prepare the dependencies and locked official files, run the tests and generate the preview:
- 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)
```bash ```bash
cd tools && go run . dev cd tools
# → http://localhost:3456 go mod download
go run . upstream prepare
go test ./...
go run . preview all
``` ```
- Watches `themes/**/*.tmpl` — auto-rebuilds on save Then open [preview/index.html](preview/index.html) in a browser, or run `go run . dev` for live preview. The validation and packaging workflows use Python 3.11.
- 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`
### Integration Testing The project is organized into a shared framework, theme styles and build tools. Official templates provide notification content, conditions, subjects and links. The shared framework handles presentation, while theme metadata and CSS select layouts and define styles. See the [README](README.md#project-directory-structure) for the directory layout.
Deploy the templates to a Gitea instance and verify with real transactional emails. > [!NOTE]
The admin test email (**Site Administration > Configuration > Mailer > Send Test Email**) > Downloaded official files, generated templates and preview data are not tracked in version control. Submit the relevant source files, tests and documentation; do not include `build/`, `dist/` or generated preview data.
does not use custom mail templates — it follows a built-in code path.
The most reliable method is to trigger a real notification. For example, the password ## Development Tools
reset flow:
1. Log out and click **"Forgot password"** on the login page The CLI prepares official files, manages themes, builds templates and generates previews.
2. Enter your account email and submit
3. Check the password reset email — it will render with your custom mail templates
--- > [!TIP]
> Run all commands below from `tools/`. Place options before positional arguments. In the examples, `my-theme` is a theme name; quote paths containing spaces.
## Commit Conventions | Command | Purpose |
|---|---|
| `upstream` | Prepare, synchronize or verify locked official build inputs |
| `build` | Generate installable themes from official inputs and shared controls |
| `list` | List available themes and their metadata |
| `create` | Create theme CSS and metadata for the shared framework |
| `delete` | Delete one or more theme source directories |
| `preview` | Pre-render mail templates to generate preview and test data |
| `dev` | Start a live-reload development server with SSE browser refresh |
| `help`, `h` | Show the command list or help for a specific command |
Any readable commit message in semantic format is welcome. Such as: ### upstream
- `style(horizon|terminal|ember|bloom|heritage|neon|mono|terra|ink|aurora):` — template changes `upstream` manages the official files pinned by `gitea.lock.json`, including mail templates, locale files, the icon, license and reviewed adapter reference sources. Downloaded files are stored in `build/upstream/`.
- `preview(*):` — preview tooling changes
- `tools(*):` — Go build script changes
- `docs(*):` — documentation and translations
- `fix(*):` — bug fixes
- `project(*):` — README, LICENSE, AGENTS.md, meta
--- | Subcommand | Behavior |
|---|---|
| `prepare` | Read the root lock file. Download and verify inputs if the cache is absent, or verify an existing cache before use. Does not modify the root lock file. |
| `sync --tag <TAG>` | Download the specified stable version's official files, verify them, then replace the cache and update the root lock file. |
| `verify` | Verify the existing cache against the root lock file, offline and read-only. |
## Translations To prepare inputs for the first time or check them independently, run:
- English ```bash
- [Simplified Chinese](docs/CONTRIBUTING.zh-CN.md) go run . upstream prepare
- [Traditional Chinese](docs/CONTRIBUTING.zh-TW.md) go run . upstream verify
- [Russian](docs/CONTRIBUTING.ru.md) ```
- [Japanese](docs/CONTRIBUTING.ja.md)
- [Korean](docs/CONTRIBUTING.ko.md)
--- All three subcommands support `--root`, which defaults to `..` relative to the current working directory. For example:
## License ```powershell
go run . upstream verify --root "C:\Projects\GiteaMailTemplates"
```
By contributing, you agree that your contributions will be licensed under the MIT License. The root lock file records the Gitea tag, its commit and file SHA-256 hashes. `verify` checks file hashes, reviewed adapter reference sources and English translation keys referenced by official templates. Missing keys in other languages produce warnings and fall back to English in the preview. Building and rendering also check framework translation keys and action-link anchors; cache verification does not replace those checks.
#### Updating the Official Version
Use `sync` only when reviewing a new official version. For example, to explicitly synchronize to `v28.1.0`:
```bash
go run . upstream sync --tag v28.1.0
go run . upstream verify
go test ./...
go run . preview all
```
Tags must use the `vX.Y.Z` format and identify a stable release of Gitea 28 or later. `latest`, prerelease tags and older version tags are not supported. `sync` resolves tags through `api.github.com` and downloads files from `raw.githubusercontent.com`; it does not accept a local Gitea clone or an authentication token option.
After synchronization, review the lock file diff, adjust adapter logic and preview data as needed, and update compatibility records after validation. Synchronization does not automatically update documentation, release tags or compatibility conclusions.
> [!IMPORTANT]
> When adapter reference sources change, review the affected behavior before updating their hashes. Do not change hashes simply to bypass the checks.
Network or validation errors before cache replacement leave existing files unchanged. Cache replacement and root lock file writing are separate operations; if a filesystem error interrupts the process, check that both agree before retrying. Stop `dev` before updating inputs to avoid concurrent processes writing to the cache.
### build
`build` generates installable mail templates from locked official files, the shared framework and theme sources. Specify one or more themes, or use `all` to build every theme:
```bash
go run . build aurora
go run . build aurora bloom
go run . build all
```
| Option | Default | Description |
|---|---|---|
| `--folder` | `../themes` | Theme source directory; its parent is used as the project root |
When using another project path, that project's root must also provide the lock file and shared framework:
```powershell
go run . build --folder "C:\Projects\GiteaMailTemplates\themes" aurora
```
Output is written to `build/themes/<name>/mail/`, with build provenance and generated file hashes recorded in the adjacent `build.json`. Rebuilding updates the selected themes and removes their obsolete templates; it does not clean unselected themes. Building does not deploy templates. See the [README](README.md#install-a-theme) for installation.
> [!TIP]
> To change templates, edit theme sources, the shared framework or build adapter logic. Direct edits to generated `.tmpl` files are overwritten by subsequent builds and cause packaging provenance checks to fail.
### list
`list` displays theme names, modes and short descriptions, sorted by name:
```bash
go run . list
```
Use `--folder` to select a theme directory; the default is `../themes`. The command validates theme metadata and styles without downloading official files or generating templates. Fix any reported theme-format errors first.
### create
`create` creates `theme.json` and `theme.css` for a new theme and supports creating multiple themes at once:
```bash
go run . create my-theme
go run . create first-theme second-theme
```
Use `--folder` to select the theme source directory; the default is `../themes`. The command does not overwrite existing directories. Theme names start with a lowercase letter and contain only lowercase letters, digits and hyphens. Avoid `all`, because build and preview commands treat it as the selector for every theme.
Edit the generated metadata and CSS to describe and style the theme. For example:
```json
{
"name": "my-theme",
"description": "A short description of the theme",
"mode": "framed",
"layout": "standard"
}
```
`name` must match the directory name. Theme directories contain only these two source files, without copies of official mail templates or translations.
| Mode | Presentation structure |
|---|---|
| `framed` | Shared controls and a layout, styled with theme CSS. New themes default to this mode and the `standard` layout. |
| `shared` | CSS is added to the official head partial, and the official footer partial is retained. No framework controls or per-mail template overrides are added. |
A `framed` theme can use `layout` to select a preset from `framework/layouts/`. Headers, action buttons, fallback links, sidebars and footers live in `framework/mail/base/`. Structural changes shared by multiple themes belong in the framework; adaptation between official content and controls is handled in `tools/builder/framework.go`.
Theme CSS uses existing classes such as `email-heading`, `email-text` and `email-subtext` to distinguish headings, body text and supporting text. Preserve official notification wording, translation keys, variables and functional links. Do not generate wording or hide notification content through CSS. The style validator rejects template expressions, external resource references and unsupported hiding rules.
After completing the theme, run the tests and generate a preview:
```bash
go test ./...
go run . preview my-theme
```
### delete
`delete` removes the source directories of one or more named themes:
```bash
go run . delete my-theme
go run . delete first-theme second-theme
```
Use `--folder` to select the theme directory; the default is `../themes`.
> [!WARNING]
> After validation, the command deletes directories directly, without a confirmation prompt or recovery through the recycle bin. Check the names and preserve any changes you need before running it.
Missing themes produce warnings; invalid themes and symlink directories are rejected. Themes are processed sequentially, so a later failure does not restore directories already deleted. `all` does not mean deleting every theme.
Deleting sources does not remove previously generated builds, preview data or deployed templates. Run `preview all` again to update the preview manifest. Remove deployed templates separately at their actual installation location.
### preview
`preview` builds the selected themes and renders all locked official languages using sample data. Specify one or more themes, or use `all`:
```bash
go run . preview aurora
go run . preview aurora bloom
go run . preview all
```
| Option | Default | Description |
|---|---|---|
| `--folder` | `../themes` | Theme source directory; its parent is used as the project root |
| `--config` | `./data/templates_config.json` | Mail type descriptions and preview sample data |
| `--output` | `preview/rendered.js` under the project root | Preview manifest path |
Default output includes `preview/rendered.js` and `preview/rendered/<locale>.js`. Open [preview/index.html](preview/index.html) in a browser; no server is required. See the [README](README.md#controls-and-keyboard-shortcuts) for controls and shortcuts.
> [!IMPORTANT]
> Each run overwrites the preview manifest at the output location instead of merging with previously selected themes. For example, after `preview aurora`, the manifest contains only Aurora. Run `preview all` to restore every theme.
To use a custom output path:
```bash
go run . preview --config ./data/templates_config.json --output ../build/preview-review/rendered.js all
```
> [!NOTE]
> Language data is written to a `rendered/` directory next to the output file. The command does not copy the preview interface or change its script references; the existing `preview/index.html` still reads data from the default location.
When adding mail types or rendering branches, extend the descriptions and sample contexts in `tools/data/templates_config.json`. Keep integer values, such as counts and sizes, as integers in JSON. Use controlled test content for HTML fields such as body text, release notes and review content; do not include personal information or real credentials.
> [!NOTE]
> The preview checks template structure and presentation using sample contexts. It does not replace parameter compatibility review for the target Gitea version. Browsers and mail clients also differ in their CSS support.
### dev
`dev` starts a local development server, building every theme and preparing preview data on startup:
```bash
go run . dev
```
Open [http://127.0.0.1:3456](http://127.0.0.1:3456). The default port is `3456`; use `--port` to change it:
```bash
go run . dev --port 3457
```
The server listens only on `127.0.0.1`. Press `Ctrl + C` in the terminal to stop it. The command locates the project relative to the current `tools/` working directory and does not provide `--folder`, `--config` or `--root` options.
The server watches theme sources, the shared framework, the root lock file, official cache and preview data in `tools/data/`. Changes trigger a rebuild and refresh the preview through server-sent events (SSE). If a build fails, resolve the terminal error and save the file again.
> [!TIP]
> Refresh the page manually after editing `preview/index.html`. Stop and restart the server after editing Go tool code. These files are outside the current automatic rebuild watch scope.
### help, h
View all commands or help for a specific command:
```bash
go run . help
go run . h
go run . help build
go run . preview --help
```
For `upstream` subcommand options, place `--help` after the subcommand:
```bash
go run . upstream prepare --help
go run . upstream sync --help
go run . upstream verify --help
```
Tool diagnostics use the `[LEVEL] [NAME] Message` format. `[I]`, `[W]`, `[E]` and `[F]` mean information, warning, error and fatal error, respectively. Level markers use different colors in supported terminals; set `NO_COLOR` to disable colors. Failed commands return a nonzero exit code. Preserve the complete output when troubleshooting.
## Frequently Asked Questions
### What if the lock file or official cache is missing?
If an existing clone lacks `gitea.lock.json`, restore the tracked root lock file. The copy at `build/upstream/lock.json` cannot replace it. If only the cache is missing, run `upstream prepare` rather than synchronizing to another version to recover it. `build`, `preview` and `dev` prepare official inputs automatically. They can run offline once the cache, Go toolchain and dependencies are available; `upstream verify` always checks local files only. For network errors or GitHub API rate limits during the initial download, resolve the network issue and retry.
### What if the cache is corrupt or does not match the lock file?
`prepare` does not repair corrupt caches automatically, and `sync` refuses to replace a non-empty invalid snapshot. Stop the development server, preserve the root lock file and move the cache to a backup location before preparing it again. For example, in PowerShell **from the repository root**, use a backup name that does not already exist:
```powershell
Move-Item -LiteralPath .\build\upstream -Destination .\build\upstream.backup
Set-Location tools
go run . upstream prepare
go run . upstream verify
```
> [!WARNING]
> Do not edit official cached files directly or delete the rest of `build/`. To revert the official version, restore the previously reviewed root lock file and rebuild the cache in the same way.
### Why does synchronizing a new version report changed adapter sources?
The project pins reviewed mail-rendering and translation reference sources. When upstream changes these implementations, the tool requires another review to avoid using incompatible preview functions or variable structures. Examine the official changes, adjust adapter logic and relevant tests, then update the reference hashes. New mail types, translation keys, functions or variable paths may also require changes. Update [compatibility records](COMPATIBILITY.md) after validation; a successful download does not establish compatibility.
### Why do some languages use English or produce formatting warnings?
Official English must contain all referenced translation keys. Missing keys in other languages fall back to English and produce warnings. Preserve official locale files without adding translations to the cache.
For example, Gitea v28.0.0 has a known placeholder defect in the Polish `mail.team_invite.text_1`. The preview preserves official output for the exact reviewed source text and reports a warning. This exception does not apply to other formatting errors. Include the version, translation key and reproduction details when reporting new issues for review and upstream feedback.
### Why does the preview not show a new theme or recent changes?
The static preview does not build automatically. Run `preview all` again and refresh the page, then confirm that the manifest contains the required themes. With a custom output path, check where the preview interface actually reads its data. With `dev`, first check the terminal for rebuild errors. Go code changes require a server restart; preview interface changes require a manual refresh. If the default port is occupied, select another port with `--port`.
### Does a successful build mean all Gitea versions are supported?
Builds target the official inputs specified in the root lock file. Other versions may change template directories, functions or variable structures, so a successful build or preview alone cannot establish backward compatibility. Select release archives using the [compatibility matrix](COMPATIBILITY.md#compatibility-matrix), rather than installing new-version templates directly on older instances.
### Why does the packaging script reject the current output?
Run `preview all` before packaging to ensure that every theme and language bundle is present and generated files match the current sources. The version argument must match the tag in the root lock file. Rebuild and regenerate previews after source or generated file changes. The script does not overwrite existing release archives. Use a new output directory when repackaging and retain old artifacts needed for comparison.
## Other Contribution Information
### Themes and the Shared Framework
Use CSS suitable for mail clients. When editing an existing theme, preserve its palette, fonts, borders and layout, keep headings, body text and supporting text distinct, and account for long text, long URLs and mobile widths. Layout fragments use presentation markup and support `table`, `tbody`, `tr`, `td`, `div`, `span` and attributes allowed by the validator. Combined markup must have balanced tags. `__HEADER__` inserts the shared header, `__MAIL_TYPE__` expands to the mail ID, and `__SIDEBAR__` inserts the shared sidebar in footer fragments. Review changes to notification variables and conditions in the build adapter logic, not in theme CSS or layout presets.
For new themes, update the galleries in both English and Chinese READMEs. Use PNG images of at most 50 KiB each; 10–20 KiB is preferred. Follow the [capture guide](docs/images/README.md) for consistent themes, mail types, languages and cropping.
### Commits and Pull Requests
Keep changes focused on a specific problem and avoid unrelated formatting changes. Describe the reason, scope and validation results in the pull request. For visual changes, you can include comparison images generated with sample data. When changing themes, the shared framework or Go tools, run from `tools/`:
```bash
go test ./...
go run . preview all
```
For changes to mail content, links, variables or function adaptation, add regression tests covering affected conditional branches. Existing tests check generated output, translation keys, notification subjects, text and links across every theme and language, including push, review, reply, workflow and attachment branches.
When changing packaging scripts, run from the repository root:
```bash
python -B -m unittest discover -s .github/scripts -p 'test_*.py'
```
For documentation-only changes, check wording, command examples and links. Keep Chinese and English documentation aligned; preserve commands, paths, variables and version information without changing their meaning in translation.
Use concise commit messages. The following prefixes are available as a reference:
| Prefix | Scope |
|---|---|
| `style(<name>):` | Theme presentation styles |
| `preview:` | Preview tools |
| `tools:` | CLI, build and snapshot tools |
| `docs:` | Documentation and translations |
| `fix:` | Bug fixes |
| `project:` | Repository configuration and project structure |
| `refactor:` | Code restructuring |
| `chore:` | Routine maintenance |
### Version and Release Maintenance
Release tags must match the locked Gitea tag. Version labels, badges and compatibility records are maintained manually; changing official inputs does not trigger automatic version maintenance. Retain historical release assets and check documentation and compatibility records before tagging. After validation and generation of all previews, you can package manually from the repository root:
```bash
python .github/scripts/package_release.py --version v28.1.0 --output build/release-review
```
Replace the example version with the tag in the root lock file and use an output directory without release archives of the same name. The script checks source and artifact hashes, packages themes, previews, documentation, the official license and provenance, and excludes stale theme builds.
`.github/workflows/validate.yml` handles checks for ordinary pushes and pull requests and is also called by the Release workflow. After validation, Release packages the archives and uploads them through the release action. It uses the tag as the title and creates new releases with an empty body, without requiring a release notes file. Republishing an existing tag preserves its existing body and replaces same-name attachments. Check the release and attachment state before retrying a failed publication.
Workflows use the `linux-amd64-docker-small` runner label, with setup steps preparing Go, Python and Node.js for publication. Actions use `actions/...@version` references, resolved through the runner's action source configuration; consider the current `github_mirror` setting when maintaining them. Publication uses the built-in `GITEA_TOKEN`, and the repository must permit release writes.
### Reporting Problems
Include the Gitea version, template release or source commit, theme, mail type, language and steps to reproduce. For build or preview issues, also include the tag in the root lock file, relevant commands and complete error output. For display issues, specify the mail client and attach screenshots with personal information removed.
For improvement suggestions, explain the use case, expected result and affected themes or tools. For official template or translation defects, preserve the original inputs and provide a minimal example that reproduces the issue.
### License
This project uses the [MIT License](LICENSE). Contribute code, styles and images that can be legally redistributed. When generating or distributing derived templates, retain Gitea copyright, the official license and snapshot provenance; release archives already include these files.
+136 -135
View File
@@ -1,207 +1,208 @@
# Gitea Mail Templates # Gitea Mail Template
A curated collection of professionally designed, audience-driven email templates for self-hosted [Gitea](https://about.gitea.com) instances.
[![Gitea](https://img.shields.io/badge/Gitea-1.25+%20%7C%201.26.4%20tested-blue)](COMPATIBILITY.md) <!-- TRACKER:BADGE -->
> **110 template files — 10 visual styles, 11 email types each**
--- ---
## 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.
Every template is a drop-in replacement. All Go template variables, translation keys, and Gitea data contexts are fully compatible. **No patches, no plugins, no forks required.** > Latest release: [v28.1.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 ## Style Gallery
| Preview | Style | Audience | Character | The gallery shows how the email templates render in the preview tool.
|---|---|---|---|
| ![Horizon](docs/images/horizon.png) | **Horizon** | Enterprise / Corporate | Blue accent, slate typography, centered cards |
| ![Terminal](docs/images/terminal.png) | **Terminal** | Developers / Tech | Dark mode, monospace, green CLI accents |
| ![Ember](docs/images/ember.png) | **Ember** | Community / Open Source | Warm amber, rounded, humanist, inclusive |
| ![Bloom](docs/images/bloom.png) | **Bloom** | Creative / Startup | Frosted glass, soft blue light, iridescent accents |
| ![Heritage](docs/images/heritage.png) | **Heritage** | Education / Research | Navy and gold, serif, classic, authoritative |
| ![Neon](docs/images/neon.png) | **Neon** | Gaming / Web3 / Creative Tech | Cyberpunk neon glow, hot pink & cyan, synthwave energy |
| ![Mono](docs/images/mono.png) | **Mono** | Design Studios / Editorial | Swiss brutalist, black & white, red accent, zero radius |
| ![Terra](docs/images/terra.png) | **Terra** | Sustainability / Wellness | Warm earth tones, organic textures, humanist serif |
| ![Ink](docs/images/ink.png) | **Ink** | Publishing / News / Literature | Editorial print, navy & gold, newspaper layout, drop caps |
| ![Aurora](docs/images/aurora.png) | **Aurora** | Premium SaaS / Mindfulness | Ethereal light gradients, deep purple & teal, atmospheric glow |
> Images are screenshots from the [live preview](preview/index.html). See [docs/images/README.md](docs/images/README.md) for capture instructions. | Preview | Theme | Style Features |
|---|---|---|
| ![Aurora](docs/images/aurora.png) | **Aurora** | Dark purple background, teal accents and a soft glow |
| ![Bloom](docs/images/bloom.png) | **Bloom** | Light blue gradients, rounded cards and buttons |
| ![Ember](docs/images/ember.png) | **Ember** | Warm orange tones, serif headings and rounded buttons |
| ![Heritage](docs/images/heritage.png) | **Heritage** | Navy and gold, double borders and serif fonts |
| ![Horizon](docs/images/horizon.png) | **Horizon** | Blue accents, gray text and a centered white card |
| ![Ink](docs/images/ink.png) | **Ink** | Newspaper layout, a sidebar, serif fonts and drop caps |
| ![Mono](docs/images/mono.png) | **Mono** | Black and white, red accents and square borders |
| ![Neon](docs/images/neon.png) | **Neon** | Dark background, pink and cyan accents and glow effects |
| ![Terminal](docs/images/terminal.png) | **Terminal** | Dark background, monospace fonts and green accents |
| ![Terra](docs/images/terra.png) | **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 [preview](#preview) to view other email types and languages. See the [screenshot guide](docs/images/README.md) when updating screenshots.
---
## Installation ## 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 ```bash
# Locate your Gitea custom directory cd tools
# (set by GITEA_CUSTOM; defaults shown below) go run . build all
cd ..
# Copy templates (example: Horizon style)
cp -r themes/horizon/mail/* /var/lib/gitea/custom/templates/mail/
# Restart Gitea
systemctl restart gitea
``` ```
### 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.
| 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 binary | `/var/lib/gitea/custom` |
| Linux (Docker) | `/data/gitea` | | Docker | `/data/gitea` |
| Windows | `C:\gitea\custom` | | Windows | `C:\gitea\custom` |
### Switching Styles For example, on a Linux host where systemd manages Gitea, run the following from the extracted release archive directory:
Overwrite the files with a different style. All templates share the exact same variable structure — no configuration changes needed.
```bash ```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 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 ### Switch Themes
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.
--- 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 ## 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 ### Static Preview
Generate the preview data once (Go only), then open in a browser: First, generate the preview data in the source repository:
```bash ```bash
cd tools && go run . preview all cd tools
open preview/index.html # no server needed 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 ```bash
cd tools && go run . dev cd tools
open http://localhost:3456 go run . dev
# Press Ctrl+C in the terminal to stop the server.
cd ..
``` ```
| Capability | Static | Dev | Open [http://127.0.0.1:3456](http://127.0.0.1:3456). The 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.
|-----------|--------|-----|
| Go template rendering | ✅ | ✅ |
| Theme/template switching | ✅ | ✅ |
| Live reload on save | — | ✅ |
### Features ### Controls and Keyboard Shortcuts
- Theme switcher — browse all 10 visual styles | Control | Options or Shortcuts |
- Template switcher — all 11 email types |---|---|
- View mode — Modern (rendered preview), Source (raw HTML) | Theme, template, language and view | `←` / `→` moves between selectors; `↑` / `↓` selects an option |
- Viewport toggle — Desktop 1386×780 / Mobile 390×780 | View | **Modern** displays the rendered result; **Source** displays the generated HTML text |
- Parameter panel — mock data per email type | Viewport | **Desktop** (1386 × 780), **Mobile** (390 × 780); shortcuts `d` / `m` |
- Keyboard shortcuts — `←→` tab between Theme/Template/View, `↑↓` select within, `d`/`m` viewport | 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.1.0
- **Latest release:** [v28.1.0](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/latest)
- **Upstream Gitea 28.1.0:** [PASS]
## 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
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
``` ```
gitea-mail-templates/
├── themes/ # 10 visual styles, 11 .tmpl files each 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.
│ ├── ... # Custom styles are added here as separate directories
├── preview/ # Live preview SPA ### Not Tracked in Version Control
│ ├── index.html # Style/template/client/viewport switcher
│ └── rendered.js # Pre-rendered templates (generated by tools/) 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:
├── tools/ # Modular CLI tooling
│ ├── tools.go # Main entry point ```text
│ ├── cli/ # CLI subcommands (list, create, delete, preview) build/themes/<THEME_NAME>/ # Theme build output
│ ├── config/ # Config types and templates_config.json loading mail/ # Email templates ready to install in Gitea
│ ├── data/ # templates_config.json — single source of truth build.json # Build provenance and generated file checksums
│ ├── preview/ # Template rendering engine build/upstream/ # Official templates, locales, assets and licenses pinned by the lock file
│ └── go.mod dist/ # Default output directory for release archives
├── docs/ # Multi-language documentation preview/rendered.js # Preview manifest
├── AGENTS.md # AI agent guidance preview/rendered/<LOCALE>.js # Preview data for each locale
├── CONTRIBUTING.md
├── LICENSE
├── README.md
└── .gitignore
``` ```
`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 ### 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.tmpl` | Account activation |
| `mail/user/auth/activate_email.tmpl` | Email address verification | | `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/user/auth/reset_passwd.tmpl` | Password reset |
| `mail/org/team_invite.tmpl` | Team invitation | | `mail/org/team_invite.tmpl` | Team invitation |
| `mail/repo/collaborator.tmpl` | Repository collaborator added | | `mail/repo/collaborator.tmpl` | Repository collaborator added |
| `mail/repo/transfer.tmpl` | Repository ownership transfer | | `mail/repo/transfer.tmpl` | Repository ownership transfer |
| `mail/repo/release.tmpl` | New release published | | `mail/repo/release.tmpl` | New release published |
| `mail/repo/actions/workflow_run.tmpl` | Actions workflow run | | `mail/repo/actions/workflow_run.tmpl` | Actions workflow run |
| `mail/repo/issue/assigned.tmpl` | Issue / Pull Request assigned | | `mail/repo/issue/assigned.tmpl` | Issue or pull request assignment |
| `mail/repo/issue/default.tmpl` | Issue / Pull Request updates | | `mail/repo/issue/default.tmpl` | Issue or pull request activity |
---
## Compatibility
- **Gitea 1.25+** — matches the refactored mail template directory structure (v1.25)
- **Latest tested:** Gitea 1.26.4 <!-- TRACKER:LATEST-TESTED -->
- 100% variable-compatible with official Gitea templates — see [COMPATIBILITY.md](COMPATIBILITY.md) for the full matrix
- 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)
- [Simplified Chinese](docs/README.zh-CN.md)
- [Traditional Chinese](docs/README.zh-TW.md)
- [Russian](docs/README.ru.md)
- [Japanese](docs/README.ja.md)
- [Korean](docs/README.ko.md)
---
## Contributing ## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. Multi-language versions available in [docs/](docs/). Contributions to themes, tools, documentation and translations are welcome. See the [contributor guide](CONTRIBUTING.md) for details on contributing.
## License ## 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.
--- ---
### Notes
<p align="center"> [1](#notes): [Mail templates | Gitea Documentation](https://docs.gitea.com/administration/mail-templates/)
<sub>Not affiliated with the Gitea project. Gitea is a community-managed lightweight code hosting solution written in Go.</sub>
</p> This project is not affiliated with Gitea.
+66
View File
@@ -0,0 +1,66 @@
# Gitea 兼容性
---
[English](../COMPATIBILITY.md)
此文件描述了 Gitea Mail Templates 与 Gitea 不同版本之间的兼容性。
## 兼容性表
根据所运行的 Gitea 版本选择对应推荐的模板版本。`[PASS]` 表示已记录为兼容的组合,`[PENDING]` 表示尚未完成验证。
> [!NOTE]
> 历史条目仅反映项目已有的兼容性评估,不代表每次发布都重新测试了每一个补丁版本。
| Gitea 版本 | 推荐模板版本 | 状态 | 说明 |
|---|---|---|---|
| 28.1.0 | **v28.1.0** | [PASS] | - |
| 28.0.0 | **v28.1.0** | [PASS] | Gitea 将邮件函数 `FileSize` 替换为 `FormatByteSize` |
| 1.27.3 | **v1.27.3** | [PASS] | 全部主题已修复拉取请求推送通知的提交链接 |
| 1.27.2 | **v1.27.3** | [PASS] | 同号 v1.27.2 模板的 Bloom、Ember、Heritage 存在推送通知缺陷 |
| 1.27.1 | **v1.27.3** | [PASS] | 较早 v1.0.x 模板使用过时的推送提交字段 |
| 1.27.0 | — | [PENDING] | Gitea 使用了新的提交数据结构;v1.27.3 可能适用,但未在此版本上验证 |
| 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] | — |
> [!NOTE]
> Gitea 1.24.x 及更早版本使用不同的邮件模板布局,属于 `[UNSUPPORTED]`,Gitea Mail Templates 不再另外支持。v1.0.0 模板标签已由 v1.0.1 替代,不再单独推荐。
## Gitea 历版本对邮件模板影响
此表描述 **Gitea** 的变化,不是本模板仓库的修复记录。“无”表示该 Gitea 版本没有新增相关邮件模板影响;推荐归档见上方兼容性表。
| Gitea | 发布日期 | 邮件模板变化 | 影响 |
|---|---|---|---|
| **28.1.0** | 2026-10-06 | 无 | 无 |
| **28.0.0** | 2026-09-29 | 邮件函数 `FileSize` 改为 `FormatByteSize` ([PR](https://github.com/go-gitea/gitea/pull/#39083)) | **不兼容变更:** 旧的发布附件模板失效 |
|||引入 templates/mail/base/ ([PR](https://github.com/go-gitea/gitea/pull/#38396))| **不兼容变更:** 旧版本 Gitea 无法直接使用新版本邮件模板 |
| **1.27.3** | 2026-08-29 | 无 | 无 |
| **1.27.2** | 2026-08-14 | 无 | 无 |
| **1.27.1** | 2026-07-27 | 内置推送模板改用 `.UserCommit.GitCommit`([PR](https://github.com/go-gitea/gitea/pull/38467)) | **不兼容变更:存在既有未修复缺陷** 旧的自定义拉取请求推送模板可能失效 |
| **1.27.0** | 2026-07-13 | 推送提交数据移至 `.UserCommit.GitCommit`([ISSUE](https://github.com/go-gitea/gitea/issues/38469)) | **不兼容变更:** 旧的拉取请求推送模板失效 |
| **1.26.4** | 2026-06-21 | 无 | 无 |
| **1.26.3** | 2026-06-20 | 无 | 无 |
| **1.26.2** | 2026-05-20 | 无 | 无 |
| **1.26.1** | 2026-04-22 | 无 | 无 |
| **1.26.0** | 2026-04-19 | 无 | 无 |
| **1.25.5** | 2026-03-10 | 无 | 无 |
| **1.25.0** | 2025 | 自定义邮件路径及主题和正文格式变化([PR](https://github.com/go-gitea/gitea/pull/35150)) | **不兼容变更:** 不支持更早的模板布局 |
| **≤ 1.24.x** | — | 旧的自定义邮件布局 | [UNSUPPORTED] |
## 模板变量参考
各类邮件上下文由官方模板定义。开发时具体的邮件模板和函数和变量请参考官方邮件模板文档 [1](#注释)。
## 翻译键
官方模板引用 `mail.*` 和 `actions.runs.attempt`。基于 AST 的检查覆盖 `.locale.Tr`、`$.locale.Tr` 和 `TrN` 的两个复数键,包括嵌套管道。所有引用键必须存在于锁定的英文目录。
---
### 注释
[1](#注释): [Mail templates | Gitea Documentation](https://docs.gitea.com/administration/mail-templates/)
-76
View File
@@ -1,76 +0,0 @@
# コントリビューション — Gitea メールテンプレート
## 参加方法
### 新しいスタイルの追加
1. ツールでスキャフォールド: `cd tools && go run . create <style-name>` — 全11種類のメールタイプ用のプレースホルダ `.tmpl` ファイルを含む完全なディレクトリ構造を作成します
2. `themes/<style-name>/` 内の各 `.tmpl` ファイルを独自デザインで編集
3. プレビューを再生成: `cd tools && go run . preview all`(ビルドスクリプトが `themes/` 以下の全テーマを自動検出し、テーマセレクターも自動生成されます)
4. スクリーンショット付きで PR を提出(1枚あたり 50 KiB 以下、10–20 KiB 推奨)
### スタイルガイドライン
- 各スタイルに **全11種類のテンプレート** を含める
- Gitea 組み込み関数のみ使用
- 翻訳キーは Gitea 公式ロケール (`mail.*`) から
- **`.DisplayName` を使用しない** (collaborator, transfer, release, workflow_run, assigned, default)
- 600px 幅のメールクライアント向けにデザイン
- Gmail, Outlook, Apple Mail でテスト
### バグレポート
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` をブラウザで開く
### 開発サーバー(ライブリロード)
```bash
cd tools && go run . dev
# → http://localhost:3456
```
`.tmpl` ファイルを編集すると自動的に再構築されブラウザに反映されます。
### 結合テスト
Gitea にデプロイ後、実際のトランザクションメールで検証してください。管理画面のテストメール
(**Site Administration > Configuration > Mailer > Send Test Email**)はカスタム
メールテンプレートを使用しません——組み込みのコードパスを通ります。
最も確実な方法は実際の通知をトリガーすることです。例えば、パスワードリセットフロー:
1. ログアウトし、ログインページで **"Forgot password"** をクリック
2. アカウントのメールアドレスを入力して送信
3. パスワードリセットメールを確認——カスタムメールテンプレートでレンダリングされます
---
## コミット規約
- `style(<name>):` — スタイルのテンプレート変更
- `preview:` — プレビューツールの変更
- `tools:` — Go ビルドスクリプトの変更
- `docs:` — ドキュメントと翻訳
- `fix:` — バグ修正
- `project:` — README, LICENSE, メタファイル
## ライセンス
貢献により、MIT ライセンスに同意したものとみなします。
-49
View File
@@ -1,49 +0,0 @@
# 기여 가이드 — Gitea 메일 템플릿
## 기여 방법
### 새 스타일 추가하기
1. 도구로 스캐폴드: `cd tools && go run . create <style-name>` — 11개 이메일 유형 모두에 대한 플레이스홀더 `.tmpl` 파일이 포함된 완전한 디렉토리 구조를 생성합니다
2. `themes/<style-name>/`에서 각 `.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에서 테스트
### 버그 리포트
1. Go 변수가 올바른지 확인
2. 번역 키가 Gitea 로케일과 일치하는지 확인
3. `.DisplayName`이 오용되지 않았는지 확인
4. 프리뷰 재생성: `cd tools && go run . preview all`
5. 스타일 이름과 메일 유형을 명시하여 이슈 생성
---
## 개발 설정
- **Go 1.21+** 템플릿 렌더링 및 CLI 도구
### 로컬 프리뷰 (정적)
1. 데이터 생성: `cd tools && go run . preview all`
2. `preview/index.html`을 브라우저에서 열기
### 개발 서버 (실시간 리로드)
```bash
cd tools && go run . dev
# → http://localhost:3456
```
`.tmpl` 파일 수정 시 자동 재빌드되어 브라우저에 반영됩니다.
-77
View File
@@ -1,77 +0,0 @@
# Участие в разработке — Gitea Mail Templates
## Способы участия
### Добавление нового стиля
1. Создайте каркас через инструмент: `cd tools && go run . create <имя-стиля>` — создаёт полную структуру директорий с файлами-заготовками `.tmpl` для всех 11 типов писем
2. Отредактируйте каждый `.tmpl` файл в `themes/<имя-стиля>/` с вашим уникальным дизайном
3. Перегенерируйте превью: `cd tools && go run . preview all` (скрипт автоматически находит все темы в `themes/` и динамически генерирует селектор тем)
4. Отправьте PR со скриншотами (≤ 50 КиБ каждый, рекомендуется 10–20 КиБ)
### Рекомендации по стилю
- Каждый стиль должен включать **все 11 типов шаблонов**
- Используйте только встроенные функции шаблонов Gitea
- Ключи перевода из официальных файлов локали Gitea (`mail.*`)
- **Не используйте `.DisplayName`** где его нет в контексте (collaborator, transfer, release, workflow_run, assigned, default)
- Дизайн для 600px email клиентов
- Тестируйте в Gmail, Outlook, Apple Mail
### Баг-репорты
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` в браузере — сервер не нужен
### Сервер разработки (live reload)
```bash
cd tools && go run . dev
# → http://localhost:3456
```
При изменении `.tmpl` файлов автоматически пересобирает и обновляет браузер.
### Интеграционное тестирование
Разверните шаблоны на Gitea и проверяйте с реальными транзакционными письмами.
Тестовое письмо администратора (**Site Administration > Configuration > Mailer >
Send Test Email**) не использует пользовательские шаблоны — оно идет по
встроенному пути.
Самый надежный способ — вызвать реальное уведомление. Например, сброс пароля:
1. Выйдите из системы и нажмите **"Forgot password"** на странице входа
2. Введите email вашей учетной записи и отправьте
3. Проверьте письмо для сброса пароля — оно будет отрендерено с вашими шаблонами
---
## Соглашение о коммитах
- `style(<имя>):` — изменения шаблонов стиля
- `preview:` — инструменты предпросмотра
- `tools:` — Go-скрипт сборки
- `docs:` — документация и переводы
- `fix:` — исправления
- `project:` — README, LICENSE, мета-файлы
## Лицензия
Участвуя, вы соглашаетесь с лицензией MIT.
+345 -61
View File
@@ -1,80 +1,364 @@
# 贡献指南 — Gitea 邮件模板 # 贡献指南
## 参与方式
### 添加新风格
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,注明风格名称、邮件类型及错误描述
### 改进文档
文档改进、预览截图、安装指南和翻译始终欢迎。
--- ---
## 开发环境 [English](../CONTRIBUTING.md)
- **Go 1.21+** 用于模板渲染和 CLI 工具 欢迎改进主题、工具、文档和翻译。本指南介绍开发环境、命令行工具和贡献流程。安装邮件模板和选择发行版本,请参阅 [README](README.zh-CN.md#安装)及[兼容性表](COMPATIBILITY.zh-CN.md#兼容性表)。
### 本地预览(静态) ## 本地开发
1. 先生成预览数据:`cd tools && go run . preview all` 使用 **Go 1.24 或更高版本**。在仓库根目录执行以下命令,准备依赖和锁定的官方文件,运行测试并生成预览:
2. 在浏览器中打开 `preview/index.html` — 无需服务器
### 开发服务器(实时重载)
```bash ```bash
cd tools && go run . dev cd tools
# → http://localhost:3456 go mod download
go run . upstream prepare
go test ./...
go run . preview all
``` ```
修改 `.tmpl` 文件后自动重建并推送至浏览器。 随后在浏览器中打开 [preview/index.html](../preview/index.html),或运行 `go run . dev` 使用实时预览。检查与打包工作流使用 Python 3.11。
项目按共享框架、主题样式和构建工具组织。官方模板提供通知内容、条件、主题行和链接;共享框架负责邮件呈现,主题通过元数据和 CSS 选择布局、定义样式。目录说明见 [README](README.zh-CN.md#项目目录结构)。
### 集成测试 > [!NOTE]
> 下载的官方文件、生成的模板和预览数据不纳入版本控制。贡献时提交相应源码、测试和文档,无需提交 `build/`、`dist/` 或生成的预览数据。
将模板部署到 Gitea 实例后,请通过真实的邮件通知进行验证。管理后台的测试邮件 ## 开发工具
(**Site Administration > Configuration > Mailer > Send Test Email**)不会使用
自定义邮件模板——它走的是内置代码路径。
最可靠的验证方式是触发一次真实的邮件通知,推荐使用密码重置流程: 命令行工具提供官方文件准备、主题管理、模板构建和预览功能。
1. 退出登录,点击登录页的**"忘记密码"** > [!TIP]
2. 输入账户邮箱并提交 > 以下命令均在 `tools/` 中执行;带有位置参数时,将选项放在位置参数之前。示例中的 `my-theme` 表示主题名称,包含空格的路径需要加引号。
3. 查看密码重置邮件——它将使用你的自定义邮件模板渲染
--- | 命令 | 用途 |
|---|---|
| `upstream` | 准备、同步或校验锁定的官方构建输入 |
| `build` | 根据官方输入和共享控件生成可安装的主题 |
| `list` | 列出可用主题及其元数据 |
| `create` | 创建使用共享框架的主题 CSS 和元数据 |
| `delete` | 删除一个或多个主题源码目录 |
| `preview` | 预渲染邮件模板,生成预览和测试数据 |
| `dev` | 启动通过 SSE 刷新页面的热重载开发服务器 |
| `help`、`h` | 显示命令列表或指定命令的帮助 |
## 提交规范 ### upstream
- `style(<名称>):` — 特定风格的模板变更 `upstream` 管理 `gitea.lock.json` 锁定的官方文件,包括邮件模板、语言文件、图标、许可证和已评审的适配参考源码。下载的文件保存到 `build/upstream/`。
- `preview:` — 预览工具变更
- `tools:` — Go 构建脚本变更
- `docs:` — 文档和翻译
- `fix:` — Bug 修复
- `project:` — README、LICENSE、元文件
## 许可协议 | 子命令 | 行为 |
|---|---|
| `prepare` | 读取根锁文件。缓存缺失时下载并校验;已有缓存则校验后使用。不修改根锁文件。 |
| `sync --tag <TAG>` | 下载指定稳定版本的官方文件,校验后替换缓存并更新根锁文件。 |
| `verify` | 对照根锁文件,只读、离线校验已有缓存。 |
参与贡献即表示您同意将您的贡献以 MIT 许可证授权。 首次准备或单独检查输入时执行:
```bash
go run . upstream prepare
go run . upstream verify
```
三个子命令均支持 `--root`,默认值为相对当前工作目录的 `..`。例如:
```powershell
go run . upstream verify --root "C:\Projects\GiteaMailTemplates"
```
根锁文件记录 Gitea 标签、对应提交和文件的 SHA-256。`verify` 检查文件哈希、已评审的适配参考源码,以及官方模板引用的英文翻译键;非英文语言缺少的键会报告警告,并在预览中回退为英文。构建和渲染还会检查框架使用的翻译键及操作链接锚点,缓存校验不能代替这些检查。
#### 更新官方版本
只有需要评审新的官方版本时才使用 `sync`。例如,明确同步到 `v28.1.0`:
```bash
go run . upstream sync --tag v28.1.0
go run . upstream verify
go test ./...
go run . preview all
```
标签必须采用 `vX.Y.Z` 格式,并且是 Gitea 28 或更高版本的稳定标签;不支持 `latest`、预发布标签或旧版本标签。`sync` 通过 `api.github.com` 解析标签,通过 `raw.githubusercontent.com` 下载文件,不接受本地 Gitea 克隆或认证令牌参数。
同步后检查锁文件差异,按需调整适配逻辑和预览数据,完成验证后更新兼容性记录。同步不会自动更新文档、发行标签或兼容性结论。
> [!IMPORTANT]
> 适配参考源码发生变化时,须先评审相关行为,再更新对应参考哈希,不应仅修改哈希以跳过检查。
网络或校验错误发生在缓存替换前时,原有文件保持不变。缓存替换和根锁文件写入是两个操作;文件系统错误中断操作后,重试前应检查二者是否一致。更新输入前先停止 `dev`,避免多个进程同时写入缓存。
### build
`build` 根据锁定的官方文件、共享框架和主题源码生成可安装的邮件模板。必须指定一个或多个主题,或使用 `all` 构建全部主题:
```bash
go run . build aurora
go run . build aurora bloom
go run . build all
```
| 选项 | 默认值 | 说明 |
|---|---|---|
| `--folder` | `../themes` | 主题源码目录;工具将其父目录作为项目根目录 |
使用其他项目路径时,该项目根目录也需要提供锁文件和共享框架:
```powershell
go run . build --folder "C:\Projects\GiteaMailTemplates\themes" aurora
```
结果写入 `build/themes/<名称>/mail/`,同级的 `build.json` 记录构建来源和生成文件的校验值。重新构建会更新所选主题的模板并清理其过时模板,不清理未选中的主题。构建不会部署邮件模板,安装步骤见 [README](README.zh-CN.md#安装主题)。
> [!TIP]
> 需要修改模板时,应编辑主题源码、共享框架或构建适配逻辑。直接修改生成的 `.tmpl` 文件会在后续构建中被覆盖,也会导致打包时的来源校验失败。
### list
`list` 按名称排序,显示主题名称、模式和简短说明:
```bash
go run . list
```
可通过 `--folder` 指定主题目录,默认值为 `../themes`。命令会校验主题元数据和样式,不下载官方文件,也不生成模板。主题格式无效时,应先修复报告的问题。
### create
`create` 为新主题创建 `theme.json` 和 `theme.css`,支持一次创建多个主题:
```bash
go run . create my-theme
go run . create first-theme second-theme
```
可通过 `--folder` 指定主题源码目录,默认值为 `../themes`。命令不会覆盖已存在的目录。主题名称以小写字母开头,仅包含小写字母、数字和连字符;避免使用 `all`,因为构建和预览命令将其作为全部主题的选择参数。
编辑生成的元数据和 CSS,定义主题说明及样式。例如:
```json
{
"name": "my-theme",
"description": "主题的简短说明",
"mode": "framed",
"layout": "standard"
}
```
`name` 必须与目录名称一致。主题目录仅保存这两个源码文件,不保存官方邮件模板或翻译副本。
| 模式 | 展示结构 |
|---|---|
| `framed` | 使用共享控件和布局,由主题 CSS 定义样式。新主题默认使用此模式及 `standard` 布局。 |
| `shared` | 在官方页头片段中加入 CSS,并保留官方页脚片段;不增加框架控件或各类邮件的覆盖模板。 |
`framed` 主题可通过 `layout` 选择 `framework/layouts/` 中的布局预设。页头、操作按钮、备用链接、侧栏和页脚位于 `framework/mail/base/`。多个主题共用的结构调整应放在共享框架中,官方内容与控件之间的适配位于 `tools/builder/framework.go`。
主题 CSS 使用 `email-heading`、`email-text`、`email-subtext` 等现有类定义标题、正文和辅助说明层级。保留官方通知文字、翻译键、变量和功能链接,不通过 CSS 生成文案或隐藏业务内容。样式校验器会拒绝模板表达式、外部资源引用和不受支持的隐藏规则。
完成主题后运行测试并生成预览:
```bash
go test ./...
go run . preview my-theme
```
### delete
`delete` 删除指定主题的源码目录,支持一个或多个名称:
```bash
go run . delete my-theme
go run . delete first-theme second-theme
```
可通过 `--folder` 指定主题目录,默认值为 `../themes`。
> [!WARNING]
> 命令校验主题后直接删除目录,不提供确认提示或回收站恢复。执行前确认名称,并保留需要的修改。
不存在的主题会报告警告;无效主题或符号链接目录会被拒绝。多个主题依次处理,后续失败不会恢复已删除的目录。`all` 不表示删除全部主题。
删除源码不会移除之前生成的构建文件、预览数据或已部署的模板。重新执行 `preview all` 更新预览清单;需要移除部署的主题时,按实际安装路径单独处理。
### preview
`preview` 构建所选主题,并使用示例数据渲染全部锁定的官方语言。必须指定一个或多个主题,或使用 `all`:
```bash
go run . preview aurora
go run . preview aurora bloom
go run . preview all
```
| 选项 | 默认值 | 说明 |
|---|---|---|
| `--folder` | `../themes` | 主题源码目录;其父目录作为项目根目录 |
| `--config` | `./data/templates_config.json` | 邮件类型说明和预览示例数据 |
| `--output` | 项目根目录下的 `preview/rendered.js` | 预览清单路径 |
默认输出包括 `preview/rendered.js` 和 `preview/rendered/<语言>.js`。在浏览器中打开 [preview/index.html](../preview/index.html) 即可查看,无需启动服务器。操作和快捷键见 [README](README.zh-CN.md#控制和快捷键)。
> [!IMPORTANT]
> 每次生成会覆盖输出位置的预览清单,不与先前选择的主题合并。例如,执行 `preview aurora` 后,该清单只包含 Aurora;需要恢复全部主题时执行 `preview all`。
自定义输出示例:
```bash
go run . preview --config ./data/templates_config.json --output ../build/preview-review/rendered.js all
```
> [!NOTE]
> 语言数据写入输出文件同级的 `rendered/` 目录。命令不会复制预览界面或修改其脚本引用;现有 `preview/index.html` 仍读取默认位置的数据。
新增邮件类型或渲染分支时,补充 `tools/data/templates_config.json` 中的说明和示例上下文。JSON 中用于计数、大小等数值的整数应保持整数形式。正文、发布说明和评审内容等 HTML 字段应使用可控的测试内容,避免包含个人信息或真实凭据。
> [!NOTE]
> 预览用于检查模板结构和展示效果。它使用示例上下文,不能代替目标 Gitea 版本的参数兼容性评审;浏览器与邮件客户端对 CSS 的支持也存在差异。
### dev
`dev` 启动本地开发服务器,首次启动时构建全部主题并准备预览数据:
```bash
go run . dev
```
打开 [http://127.0.0.1:3456](http://127.0.0.1:3456)。默认端口为 `3456`,可通过 `--port` 修改:
```bash
go run . dev --port 3457
```
服务器仅监听 `127.0.0.1`。在终端中按 `Ctrl + C` 停止运行。该命令按当前 `tools/` 工作目录定位项目,不提供 `--folder`、`--config` 或 `--root` 选项。
服务器监听主题源码、共享框架、根锁文件、官方缓存及 `tools/data/` 中的预览数据。文件变化后重新构建,通过服务器发送事件(SSE)刷新预览。构建失败时,根据终端输出修复问题,再保存文件。
> [!TIP]
> 修改 `preview/index.html` 后手动刷新页面;修改 Go 工具代码后停止并重新启动服务器。这些文件不属于当前的自动重建监听范围。
### help、h
查看全部命令或指定命令的帮助:
```bash
go run . help
go run . h
go run . help build
go run . preview --help
```
查看 `upstream` 子命令的参数时,将 `--help` 放在子命令后:
```bash
go run . upstream prepare --help
go run . upstream sync --help
go run . upstream verify --help
```
工具诊断信息采用 `[LEVEL] [NAME] Message` 格式,`[I]`、`[W]`、`[E]`、`[F]` 分别表示信息、警告、错误和致命错误。在支持颜色的终端中,等级标记使用不同颜色;设置 `NO_COLOR` 可禁用颜色。命令失败时返回非零退出码,排查时保留完整输出。
## 常见问题
### 缺少锁文件或官方缓存怎么办?
已有克隆缺少 `gitea.lock.json` 时,应恢复受版本控制的根锁文件。缓存中的 `build/upstream/lock.json` 不能代替它。仅缺少缓存时执行 `upstream prepare`,不要为了恢复缓存而同步到其他版本。`build`、`preview` 和 `dev` 会自动准备官方输入。缓存、Go 工具链和依赖齐备后可以离线使用;`upstream verify` 始终只检查本地文件。首次下载遇到网络错误或 GitHub API 限流时,解决网络问题后重试。
### 缓存损坏或与锁文件不匹配怎么办?
`prepare` 不会自动修复损坏的缓存,`sync` 也会拒绝替换非空的无效快照。先停止开发服务器,保留根锁文件,将缓存移至备份位置后重新准备。例如,在**仓库根目录的 PowerShell** 中,使用尚不存在的备份名称:
```powershell
Move-Item -LiteralPath .\build\upstream -Destination .\build\upstream.backup
Set-Location tools
go run . upstream prepare
go run . upstream verify
```
> [!WARNING]
> 不要直接修改缓存中的官方文件,也不要删除 `build/` 中的其他内容。回退官方版本时,恢复此前已评审的根锁文件,再按同样方式重建缓存。
### 为什么同步新版本时提示适配源码发生变化?
项目固定了已评审的邮件渲染和翻译参考源码。上游修改这些实现后,工具会要求重新评审,避免继续使用不匹配的预览函数或变量结构。检查官方改动,调整适配逻辑和相关测试,再更新参考哈希。新增邮件、翻译键、函数或变量路径都可能需要对应修改。验证通过后再更新[兼容性记录](COMPATIBILITY.zh-CN.md),不要把成功下载等同于完成兼容性确认。
### 为什么部分语言使用英文或出现格式警告?
官方英文必须包含所有引用的翻译键;其他语言缺少的键回退为英文,并报告警告。保留官方语言文件,不在缓存中补写译文。
例如 Gitea v28.0.0 的波兰语 `mail.team_invite.text_1` 存在已知占位符缺陷。预览对已评审的完整原文保留官方输出并报告警告,该例外不适用于其他格式错误。新增问题应提供版本、翻译键和复现信息,以便评审和向上游反馈。
### 为什么预览没有显示新主题或最新修改?
静态预览不会自动构建。重新执行 `preview all` 并刷新页面,确认预览清单包含所需主题。使用自定义输出路径时,检查预览界面实际读取的位置;使用 `dev` 时,先查看终端是否报告重建失败。Go 代码变更需要重启服务器,预览界面变更需要手动刷新。默认端口被占用时,使用 `--port` 指定其他端口。
### 构建成功是否意味着支持所有 Gitea 版本?
构建结果针对根锁文件指定的官方输入。不同版本可能修改模板目录、函数或变量结构,不能仅凭构建或预览成功确认向下兼容。部署时按[兼容性表](COMPATIBILITY.zh-CN.md#兼容性表)选择发行包,不将新版本模板直接用于旧版本实例。
### 为什么打包脚本拒绝当前产物?
打包前执行 `preview all`,确保全部主题和语言数据齐备,且生成文件与当前源码一致。版本参数必须与根锁文件中的标签一致。源码或生成文件变化后,应重新构建和预览。脚本不会覆盖已有发行压缩包。重新打包时选择新的输出目录,保留需要对照的旧产物。
## 其它事宜
### 主题与共享框架
使用适合邮件客户端的 CSS。修改已有主题时,保留其配色、字体、边框和布局特征,使标题、正文和辅助说明的层级清晰,并考虑长文本、长 URL 和移动端宽度。布局片段使用展示性标记,支持 `table`、`tbody`、`tr`、`td`、`div`、`span` 及校验器允许的属性。组合后的标签必须完整配对;`__HEADER__` 插入共享页头,`__MAIL_TYPE__` 展开为邮件 ID,页脚中的 `__SIDEBAR__` 插入共享侧栏。业务变量和通知条件的变更应在构建适配逻辑中评审,不放入主题 CSS 或布局预设。
新增主题时更新中英文 README 的画廊。图片使用 PNG,单张不超过 50 KiB,建议为 10–20 KiB;主题、模板、语言和裁切方式按[截图指南](images/README.md)保持一致。
### 提交与拉取请求
围绕一个明确问题组织改动,避免混入无关的格式调整。拉取请求说明修改原因、影响范围和验证结果;涉及视觉变化时,可附上使用示例数据生成的对照图。修改主题、共享框架或 Go 工具时,在 `tools/` 中运行:
```bash
go test ./...
go run . preview all
```
涉及邮件内容、链接、变量或函数适配时,补充对应的回归测试,覆盖受影响的条件分支。现有测试检查生成结果、翻译键及全部主题和语言的通知主题行、正文与链接,包括推送、评审、回复、工作流和附件分支。
修改打包脚本时,在仓库根目录运行:
```bash
python -B -m unittest discover -s .github/scripts -p 'test_*.py'
```
纯文档修改检查文字、命令示例和链接即可。中文文档与英文文档应保持内容一致;翻译时保留命令、路径、变量和版本信息,不改变原意。
提交信息使用简短说明,可参考以下前缀:
| 前缀 | 范围 |
|---|---|
| `style(<名称>):` | 主题展示样式 |
| `preview:` | 预览工具 |
| `tools:` | CLI、构建与快照工具 |
| `docs:` | 文档和翻译 |
| `fix:` | 问题修复 |
| `project:` | 仓库配置和项目结构 |
| `refactor:` | 代码重构 |
| `chore:` | 日常维护 |
### 版本与发布维护
发行标签必须与锁定的 Gitea 标签一致。版本标记、徽章和兼容性记录由维护者手动更新;修改官方输入不会自动触发版本维护。历史发行附件应保留,文档和兼容性记录应在打标签前检查。完成验证并生成全部预览后,可在仓库根目录手动打包:
```bash
python .github/scripts/package_release.py --version v28.1.0 --output build/release-review
```
将示例版本替换为根锁文件中的标签,并使用没有同名发行压缩包的输出目录。脚本检查源码与产物的校验值,打包主题、预览、文档、官方许可证和来源信息,排除残留的旧主题构建。
`.github/workflows/validate.yml` 负责普通推送和拉取请求检查,也供 Release 工作流调用。Release 验证通过后打包归档,并通过发布 Action 上传附件,以标签作为标题,新建 Release 的正文为空,不要求发行说明文件。重新发布已有标签时保留已有正文并替换同名附件;失败后重试前检查发行版及附件状态。
工作流使用 `linux-amd64-docker-small` Runner 标签,并通过安装步骤准备 Go、Python 和发布所需的 Node.js。Action 使用 `actions/...@版本` 引用,由 Runner 的 Action 来源配置解析;维护时应考虑当前 `github_mirror` 配置。发布使用内置 `GITEA_TOKEN`,仓库需具备发行版写入权限。
### 报告问题
问题报告应包含 Gitea 版本、模板发行版或源码提交、主题、邮件类型、语言和复现步骤。构建或预览问题还应提供根锁文件中的标签、相关命令和完整错误输出;显示问题注明邮件客户端,并附上移除个人信息后的截图。
提出改进建议时,说明使用场景、预期效果及涉及的主题或工具。发现官方模板或翻译问题时,保留原始输入,提供能够复现问题的最小示例。
### 许可证
本项目采用 [MIT 许可证](../LICENSE)。贡献应使用能够合法分发的代码、样式和图片。生成和分发派生模板时,保留 Gitea 版权、官方许可证及快照来源信息;发行包已包含相关文件。
-80
View File
@@ -1,80 +0,0 @@
# 貢獻指南 — Gitea 郵件模板
## 參與方式
### 新增風格
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` — 無需伺服器
### 開發伺服器(即時重載)
```bash
cd tools && go run . dev
# → http://localhost:3456
```
修改 `.tmpl` 檔案後自動重建並推送至瀏覽器。
### 整合測試
將模板部署至 Gitea 實例後,請透過真實的郵件通知進行驗證。管理後台的測試郵件
(**Site Administration > Configuration > Mailer > Send Test Email**)不會使用
自訂郵件模板——它走的是內置程式碼路徑。
最可靠的驗證方式是觸發一次真實的郵件通知,推薦使用密碼重設流程:
1. 登出帳號,點擊登入頁的**"忘記密碼"**
2. 輸入帳戶郵箱並提交
3. 查看密碼重設郵件——它將使用你的自訂郵件模板渲染
---
## 提交規範
- `style(<名稱>):` — 特定風格的模板變更
- `preview:` — 預覽工具變更
- `tools:` — Go 建置腳本變更
- `docs:` — 文件與翻譯
- `fix:` — Bug 修復
- `project:` — README、LICENSE、中繼資料檔案
## 授權條款
參與貢獻即表示您同意將您的貢獻以 MIT 授權條款授權。
-66
View File
@@ -1,66 +0,0 @@
# Gitea メールテンプレート
[Gitea](https://about.gitea.com) のためのメールテンプレートコレクション。
> **110 ファイル — 10 スタイル、各11種類**
---
## スタイルギャラリー
| プレビュー | スタイル | 対象 | 特徴 |
|---|---|---|---|
| ![Horizon](images/horizon.png) | **Horizon** | エンタープライズ | Blue accent, slate, centred cards |
| ![Terminal](images/terminal.png) | **Terminal** | 開発者 | Dark mode, monospace, green CLI |
| ![Ember](images/ember.png) | **Ember** | コミュニティ | Warm amber, rounded, inclusive |
| ![Bloom](images/bloom.png) | **Bloom** | クリエイティブ | Frosted glass, soft blue light |
| ![Heritage](images/heritage.png) | **Heritage** | 教育/研究 | Navy and gold, serif, classic |
| ![Neon](images/neon.png) | **Neon** | ゲーム/Web3 | Cyberpunk neon, pink and cyan |
| ![Mono](images/mono.png) | **Mono** | デザイン/編集 | Swiss brutalist, black-white-red |
| ![Terra](images/terra.png) | **Terra** | サステナビリティ | Warm earth tones, organic textures |
| ![Ink](images/ink.png) | **Ink** | 出版/ニュース | Editorial print, navy and gold |
| ![Aurora](images/aurora.png) | **Aurora** | プレミアムSaaS | Ethereal gradients, purple and teal |
> 画像は[ライブプレビュー](../preview/index.html)からの600px幅のスクリーンショットです。キャプチャ手順は [images/README.md](images/README.md) を参照してください。
[**ライブプレビュー**](../preview/index.html)
---
## インストール
```bash
cp -r themes/horizon/mail/* /var/lib/gitea/custom/templates/mail/
systemctl restart gitea
```
### 動作確認
管理画面のテストメールはカスタムテンプレートを使用しません。テンプレートが
有効であることを確認するには、実際のメール通知をトリガーしてください。最も
簡単な方法はパスワードリセットです:ログアウトし、ログインページで
**"Forgot password"** をクリックし、リセットメールを確認してください。
## プレビュー
**静的モード:**
```bash
cd tools && go run . preview all
```
その後 `preview/index.html` を開く。
**開発サーバー(ライブリロード):**
```bash
cd tools && go run . dev
# → http://localhost:3456
```
## 互換性
- **Gitea 1.25+** — v1.25で導入されたメールテンプレートディレクトリ構造
- **最新テスト:** Gitea 1.26.4<!-- TRACKER:LATEST-TESTED -->
- Gitea公式テンプレートと100%互換 — 詳細は [COMPATIBILITY.md](COMPATIBILITY.md)を参照
## ライセンス
MIT — [LICENSE](../LICENSE).
-66
View File
@@ -1,66 +0,0 @@
# Gitea 메일 템플릿
[Gitea](https://about.gitea.com) 인스턴스를 위한 메일 템플릿 컬렉션.
> **110 파일 — 10 스타일, 각 11종류**
---
## 스타일 갤러리
| 미리보기 | 스타일 | 대상 | 특징 |
|---|---|---|---|
| ![Horizon](images/horizon.png) | **Horizon** | 엔터프라이즈 | Blue accent, slate, centred cards |
| ![Terminal](images/terminal.png) | **Terminal** | 개발자 | Dark mode, monospace, green CLI |
| ![Ember](images/ember.png) | **Ember** | 커뮤니티 | Warm amber, rounded, inclusive |
| ![Bloom](images/bloom.png) | **Bloom** | 크리에이티브 | Frosted glass, soft blue light |
| ![Heritage](images/heritage.png) | **Heritage** | 교육/연구 | Navy and gold, serif, classic |
| ![Neon](images/neon.png) | **Neon** | 게임/Web3 | Cyberpunk neon, pink and cyan |
| ![Mono](images/mono.png) | **Mono** | 디자인/편집 | Swiss brutalist, black-white-red |
| ![Terra](images/terra.png) | **Terra** | 지속가능성 | Warm earth tones, organic textures |
| ![Ink](images/ink.png) | **Ink** | 출판/뉴스 | Editorial print, navy and gold |
| ![Aurora](images/aurora.png) | **Aurora** | 프리미엄SaaS | Ethereal gradients, purple and teal |
> 이미지는 [라이브 프리뷰](../preview/index.html)에서 캡처한 600px 너비 스크린샷입니다. 캡처 방법은 [images/README.md](images/README.md)를 참조하세요.
[**라이브 프리뷰**](../preview/index.html)
---
## 설치
```bash
cp -r themes/horizon/mail/* /var/lib/gitea/custom/templates/mail/
systemctl restart gitea
```
### 작동 확인
관리자 테스트 이메일은 커스텀 템플릿을 사용하지 않습니다. 템플릿이 활성화
되었는지 확인하려면 실제 이메일 알림을 트리거하세요. 가장 빠른 방법은 비밀번호
재설정입니다: 로그아웃 후 로그인 페이지에서 **"Forgot password"** 를 클릭하고
재설정 이메일을 확인하세요.
## 프리뷰
**정적 모드:**
```bash
cd tools && go run . preview all
```
그런 다음 `preview/index.html` 열기.
**개발 서버 (실시간 리로드):**
```bash
cd tools && go run . dev
# → http://localhost:3456
```
## 호환성
- **Gitea 1.25+** — v1.25에서 도입된 메일 템플릿 디렉토리 구조 사용
- **최신 테스트:** Gitea 1.26.4<!-- TRACKER:LATEST-TESTED -->
- Gitea 공식 템플릿과 100% 호환 — 자세한 내용은 [COMPATIBILITY.md](../COMPATIBILITY.md) 참조
## 라이선스
MIT — [LICENSE](../LICENSE).
-66
View File
@@ -1,66 +0,0 @@
# Gitea Mail Templates — Шаблоны писем Gitea
Коллекция шаблонов писем для [Gitea](https://about.gitea.com).
> **110 файлов — 10 стилей, по 11 типов писем**
---
## Галерея стилей
| Превью | Стиль | Аудитория | Характер |
|---|---|---|---|
| ![Horizon](images/horizon.png) | **Horizon** | Корпоративный | Синий акцент, slate, centred cards |
| ![Terminal](images/terminal.png) | **Terminal** | Разработчики | Тёмная тема, monospace, зелёный CLI |
| ![Ember](images/ember.png) | **Ember** | Open Source | Тёплый янтарь, rounded, инклюзивный |
| ![Bloom](images/bloom.png) | **Bloom** | Креатив / Стартапы | Матовое стекло, мягкий синий свет |
| ![Heritage](images/heritage.png) | **Heritage** | Образование | Navy и золото, serif, классика |
| ![Neon](images/neon.png) | **Neon** | Игры / Web3 / Креатив | Киберпанк-неон, розовый и циан |
| ![Mono](images/mono.png) | **Mono** | Дизайн / Редакция | Швейцарский брутализм, чёрный-белый-красный |
| ![Terra](images/terra.png) | **Terra** | Устойчивость / Здоровье | Тёплые земляные тона, органические текстуры |
| ![Ink](images/ink.png) | **Ink** | Издательство / Новости | Редакционная печать, navy и золото |
| ![Aurora](images/aurora.png) | **Aurora** | Премиум SaaS | Эфирные градиенты, пурпур и бирюза |
> Изображения шириной 600px из [предпросмотра](../preview/index.html). Инструкции по созданию скриншотов: [images/README.md](images/README.md).
[**Галерея предпросмотра**](../preview/index.html)
---
## Установка
```bash
cp -r themes/horizon/mail/* /var/lib/gitea/custom/templates/mail/
systemctl restart gitea
```
### Проверка работы
Тестовое письмо администратора не использует пользовательские шаблоны. Чтобы
убедиться, что шаблоны активны, вызовите реальное уведомление. Самый быстрый
способ — сброс пароля: выйдите из системы, нажмите **"Forgot password"** на
странице входа и проверьте письмо для сброса.
## Предпросмотр
**Статический режим:**
```bash
cd tools && go run . preview all
```
Затем откройте `preview/index.html`.
**Dev-сервер (live reload):**
```bash
cd tools && go run . dev
# → http://localhost:3456
```
## Совместимость
- **Gitea 1.25+** — структура директорий из v1.25
- **Последняя проверка:** Gitea 1.26.4<!-- TRACKER:LATEST-TESTED -->
- 100% совместимость с официальными шаблонами Gitea — см. [COMPATIBILITY.md](COMPATIBILITY.md)
## Лицензия
MIT — [LICENSE](../LICENSE).
+173 -52
View File
@@ -1,87 +1,208 @@
# Gitea 邮件模板 # Gitea Mail Template
为自托管 [Gitea](https://about.gitea.com) 实例精心设计、面向不同受众的邮件模板集合。
> **110 个模板文件 — 10 种视觉风格,每种 11 种邮件类型**
--- ---
## 设计理念 [English](../README.md)
大多数自托管 Gitea 实例使用默认的纯文本邮件模板。本项目提供了**开箱即用、视觉精美的替代方案**——每种方案都针对特定社区或受众设计,您可以选择最适合您用户的风格。 Gitea Mail Template 为自托管的 [Gitea](https://about.gitea.com) 提供多种可选、适合的邮件模板风格。
每个模板都是直接替换。所有 Go 模板变量、翻译键和 Gitea 数据上下文完全兼容。**无需补丁、无需插件、无需 fork。** > 最新发布版:[v28.1.0](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/latest)
--- 仓库包含数种主题的 Gitea 邮件模板,适合大多数的部署场景与应用范围。安装前,请根据 Gitea 版本查阅[兼容性表](./COMPATIBILITY.zh-CN.md#兼容性表),选择对应的发行包。
## 风格画廊 ## 风格画廊
| 预览 | 风格 | 受众 | 特点 | 这里展示邮件模板通过预览工具呈现的实际渲染风格和效果。
|---|---|---|---|
| ![Horizon](images/horizon.png) | **Horizon** | 企业/公司 | 蓝色强调色、石板灰排版、居中卡片 |
| ![Terminal](images/terminal.png) | **Terminal** | 开发者/技术 | 暗色模式、等宽字体、绿色命令行风格 |
| ![Ember](images/ember.png) | **Ember** | 社区/开源 | 暖琥珀色、圆角、人文主义、包容 |
| ![Bloom](images/bloom.png) | **Bloom** | 创意/初创 | 磨砂玻璃、柔和蓝色光线、虹彩点缀 |
| ![Heritage](images/heritage.png) | **Heritage** | 教育/研究 | 海军蓝与金色、衬线字体、经典、权威 |
| ![Neon](images/neon.png) | **Neon** | 游戏/Web3/创意科技 | 赛博朋克霓虹、粉红与青色、合成波能量 |
| ![Mono](images/mono.png) | **Mono** | 设计工作室/编辑 | 瑞士粗野主义、黑白红强调、零圆角 |
| ![Terra](images/terra.png) | **Terra** | 可持续/健康 | 温暖大地色调、有机质感、人文衬线 |
| ![Ink](images/ink.png) | **Ink** | 出版/新闻/文学 | 编辑印刷、深蓝与金色、报纸排版 |
| ![Aurora](images/aurora.png) | **Aurora** | 高端SaaS/正念 | 空灵光效渐变、深紫与青绿、大气光晕 |
> 图片为 600px 宽截图,来自[在线预览](../preview/index.html)。截图方法参见 [images/README.md](images/README.md)。 | 预览 | 主题 | 样式特点 |
|---|---|---|
| ![Aurora](images/aurora.png) | **Aurora** | 深紫色背景、青绿色强调色、柔和光晕 |
| ![Bloom](images/bloom.png) | **Bloom** | 浅蓝色渐变、圆角卡片与按钮 |
| ![Ember](images/ember.png) | **Ember** | 暖橙色调、衬线标题、圆角按钮 |
| ![Heritage](images/heritage.png) | **Heritage** | 藏蓝与金色、双线边框、衬线字体 |
| ![Horizon](images/horizon.png) | **Horizon** | 蓝色强调色、灰色文字、居中白色卡片 |
| ![Ink](images/ink.png) | **Ink** | 报刊式布局、侧栏、衬线字体与首字下沉 |
| ![Mono](images/mono.png) | **Mono** | 黑白配色、红色强调色、直角边框 |
| ![Neon](images/neon.png) | **Neon** | 深色背景、粉红与青色、发光效果 |
| ![Terminal](images/terminal.png) | **Terminal** | 深色背景、等宽字体、绿色强调色 |
| ![Terra](images/terra.png) | **Terra** | 大地色调、陶土色按钮、衬线字体 |
[**在线预览画廊**](../preview/index.html) 截图仅展示当前默认构建效果,其他邮件类型和语言可通过[预览](#预览)查看;更新截图请参阅[截图指南](images/README.md)。
---
## 安装 ## 安装
选择一种风格,将 `mail/` 目录复制到 Gitea 自定义模板路径: ### 选择安装来源
运行 `gitea --version` 确认部署的 Gitea 实例版本,再按照[兼容性表](COMPATIBILITY.zh-CN.md#兼容性表)选择模板版本。
> [!WARNING]
> 我们不建议直接从源码仓库拉取并构建,因为这些版本还未经过完全测试达到发行可用的水平。如果存在未知的模板参数过时或遗漏可能导致 Gitea 实例启动异常。
> [!TIP]
> 如果仍然需要从源码构建邮件模板,下方同样给出的相应步骤方法。
| 来源 | 邮件模板目录 | 准备步骤 |
|---|---|---|
| 发行版 | `themes/<名称>/mail/` | 下载并解压推荐版本的发行包 |
| 源码 | `build/themes/<名称>/mail/` | 使用 Go 1.24 或更高版本构建 |
从源码构建时,请在仓库根目录执行:
```bash ```bash
cp -r themes/horizon/mail/* /var/lib/gitea/custom/templates/mail/ cd tools
go run . build all
cd ..
```
构建可用模板时需要依赖于 Gitea 官方仓库的资源文件,以确保来源的一致性。因此该命令会自动下载由`gitea.lock.json` 文件锁定的官方文件,后续构建无需再次下载,使用缓存目录资源即可运行。
### 安装主题
将所选主题 `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 systemctl restart gitea
``` ```
切换风格只需覆盖文件,无需更改配置。 > [!TIP]
> 使用源码构建产物时,将复制来源改为 `build/themes/horizon/mail/.`。Docker 和 Windows 部署请使用相应的容器或服务管理方式暂停和重启。
### 切换主题
先备份或移除上一主题安装的文件,选择新的邮件主题依照上述安装步骤重新操作即可。
### 确认生效 ### 确认生效
管理后台的测试邮件不会使用自定义模板。要验证模板是否生效,请触发一次真实的 使用测试账户触发密码重置等邮件通知,检查邮件样式和链接。管理后台的测试邮件按钮不使用自定义邮件模板,因此无法确认邮件模板是否生效。
邮件通知。最快的方式是密码重置:退出登录,点击登录页的**"忘记密码"**,查看
重置邮件即可——它将使用你的自定义样式渲染。
---
## 预览 ## 预览
**静态模式:** 通过仓库中提供的预览工具,可以在部署前预览邮件不同主题,不同模板类型和语言的渲染效果
```bash
cd tools && go run . preview all ### 静态预览
```
然后打开 `preview/index.html`。 首先需要在源码仓库中生成预览数据:
**开发服务器(实时重载):**
```bash ```bash
cd tools && go run . dev cd tools
# → http://localhost:3456 go run . preview all
cd ..
``` ```
| 功能 | 静态 | Dev | 在浏览器中打开 [preview/index.html](../preview/index.html) 即可。无需启动服务器。
|-----------|--------|-----|
| Go 模板渲染 | ✅ | ✅ |
| 主题/模板切换 | ✅ | ✅ |
| 实时重载 | — | ✅ |
--- ### 热重载开发
```bash
cd tools
go run . dev
## 在终端中输入 Ctrl + C 即可终止服务器运行
cd ..
```
打开 [http://127.0.0.1:3456](http://127.0.0.1:3456)。服务器会监听主题文件、共享框架、锁文件与缓存、预览测试数据的变化。一旦监听到文件更改,服务器会将邮件模板重新构建后通过发送事件(SSE)刷新页面,以实现实时热重载。
### 控制和快捷键
| 控件 | 选项或快捷键 |
|---|---|
| 主题、模板、语言和视图 | `←` / `→` 切换选择框,`↑` / `↓` 选择选项 |
| 视图 | **Modern** 显示渲染结果,**Source** 显示生成的 HTML 文本 |
| 视口 | **Desktop**(1386 × 780)、**Mobile**(390 × 780);快捷键 `d` / `m` |
| 信息面板 | `p` 展开或收起面板 |
> [!WARNING]
> 预览使用示例数据渲染模板。浏览器与邮件客户端对 CSS 的支持不同,部署前还需在目标邮件客户端中检查效果。
## 兼容性 ## 兼容性
- **Gitea 1.25+** — v1.25 引入的邮件模板目录结构 - **最新测试:** Gitea 28.1.0
- **最新测试:** Gitea 1.26.4<!-- TRACKER:LATEST-TESTED --> - **最新发布版:** [v28.1.0](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/latest)
- 与 Gitea 官方模板 100% 兼容 — 详见 [COMPATIBILITY.md](COMPATIBILITY.md) - **上游 Gitea 28.1.0:** [PENDING]
## 项目目录结构
项目按共享框架、主题样式和构建工具组织。源码与文档纳入版本控制,下载的官方文件和生成的模板、预览数据不纳入版本控制。
### 版本控制中
```text
.github/ # 工作流及维护脚本
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 # 官方版本标签、提交和文件校验值
```
官方模板提供通知内容、条件、主题行和链接;共享框架负责邮件呈现,主题通过元数据和 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 许可证和快照来源信息。
---
### 注释
[1](#注释):[Mail templates | Gitea Documentation](https://docs.gitea.com/administration/mail-templates/)
本项目与 Gitea 官方无隶属关系。
-82
View File
@@ -1,82 +0,0 @@
# Gitea 郵件模板
為自託管 [Gitea](https://about.gitea.com) 實例精心設計、面向不同受眾的郵件模板集合。
> **110 個模板檔案 — 10 種視覺風格,每種 11 種郵件類型**
---
## 設計理念
大多數自託管 Gitea 實例使用預設的純文字郵件模板。本專案提供了**開箱即用、視覺精美的替代方案**——每種方案都針對特定社群或受眾設計,您可以選擇最適合您使用者的風格。
每個模板都是直接替換。所有 Go 模板變數、翻譯鍵和 Gitea 資料上下文完全相容。**無需修補程式、無需外掛、無需 fork。**
---
## 風格展示
| 預覽 | 風格 | 受眾 | 特點 |
|---|---|---|---|
| ![Horizon](images/horizon.png) | **Horizon** | 企業/公司 | 藍色強調色、石板灰排版、居中卡片 |
| ![Terminal](images/terminal.png) | **Terminal** | 開發者/技術 | 暗色模式、等寬字型、綠色命令列風格 |
| ![Ember](images/ember.png) | **Ember** | 社群/開源 | 暖琥珀色、圓角、人文主義、包容 |
| ![Bloom](images/bloom.png) | **Bloom** | 創意/新創 | 磨砂玻璃、柔和藍色光線、虹彩點綴 |
| ![Heritage](images/heritage.png) | **Heritage** | 教育/研究 | 海軍藍與金色、襯線字型、經典、權威 |
| ![Neon](images/neon.png) | **Neon** | 遊戲/Web3/創意科技 | 賽博龐克霓虹、粉紅與青色、合成波能量 |
| ![Mono](images/mono.png) | **Mono** | 設計工作室/編輯 | 瑞士粗野主義、黑白紅強調、零圓角 |
| ![Terra](images/terra.png) | **Terra** | 永續/健康 | 溫暖大地色調、有機質感、人文襯線 |
| ![Ink](images/ink.png) | **Ink** | 出版/新聞/文學 | 編輯印刷、深藍與金色、報紙排版 |
| ![Aurora](images/aurora.png) | **Aurora** | 高端SaaS/正念 | 空靈光效漸變、深紫與青綠、大氣光暈 |
> 圖片為 600px 寬截圖,來自[線上預覽](../preview/index.html)。截圖方法參見 [images/README.md](images/README.md)。
[**線上預覽展示**](../preview/index.html)
---
## 安裝
選擇一種風格,將 `mail/` 目錄複製到 Gitea 自訂模板路徑:
```bash
cp -r themes/horizon/mail/* /var/lib/gitea/custom/templates/mail/
systemctl restart gitea
```
切換風格只需覆蓋檔案,無需更改設定。
### 確認生效
管理後台的測試郵件不會使用自訂模板。要驗證模板是否生效,請觸發一次真實的
郵件通知。最快的方式是密碼重設:登出帳號,點擊登入頁的**"忘記密碼"**,查看
重設郵件即可——它將使用你的自訂樣式渲染。
---
## 預覽
**靜態模式:**
```bash
cd tools && go run . preview all
```
然後開啟 `preview/index.html`。
**開發伺服器(即時重載):**
```bash
cd tools && go run . dev
# → http://localhost:3456
```
---
## 相容性
- **Gitea 1.25+** — v1.25 引入的郵件模板目錄結構
- **最新測試:** Gitea 1.26.4<!-- TRACKER:LATEST-TESTED -->
- 與 Gitea 官方模板 100% 相容 — 詳見 [COMPATIBILITY.md](COMPATIBILITY.md)
## 授權
MIT — 詳見 [LICENSE](../LICENSE)。
+19 -19
View File
@@ -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
``` Use the same settings for every theme so images remain comparable:
horizon.png terminal.png ember.png bloom.png heritage.png
neon.png mono.png terra.png ink.png aurora.png
```
## 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 ## Manual Capture
- **Recommended:** 30–40 KiB
- **Format:** PNG, optimised — run through `pngquant` or `optipng` before committing
## 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) 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#风格画廊).
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.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

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">
+4
View File
@@ -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__">
+3
View File
@@ -0,0 +1,3 @@
</td></tr>
</table>
@@ -0,0 +1,3 @@
</td></tr>
<tr><td class="email-footer">
+4
View File
@@ -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__">
+3
View File
@@ -0,0 +1,3 @@
</td></tr>
</table>
@@ -0,0 +1,3 @@
</td></tr>
<tr><td class="email-footer">
+4
View File
@@ -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__">
+3
View File
@@ -0,0 +1,3 @@
</td></tr>
</table>
+3
View File
@@ -0,0 +1,3 @@
</td><td width="36%" class="email-sidebar resp-hide">__SIDEBAR__</td></tr>
<tr><td colspan="2" class="email-footer">
+4
View File
@@ -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__">
+3
View File
@@ -0,0 +1,3 @@
</td></tr>
</table>
+3
View File
@@ -0,0 +1,3 @@
</td></tr>
<tr><td class="email-footer">
+4
View File
@@ -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__">
+3
View File
@@ -0,0 +1,3 @@
</td></tr>
</table>
+3
View File
@@ -0,0 +1,3 @@
</td></tr>
<tr><td class="email-footer">
+4
View File
@@ -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__">
+3
View File
@@ -0,0 +1,3 @@
</td></tr>
</table>
@@ -0,0 +1,3 @@
</td></tr>
<tr><td class="email-footer">
+4
View File
@@ -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__">
+7
View File
@@ -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>
+1
View File
@@ -0,0 +1 @@
<p class="email-footer-text"><span class="framework-extra">&copy; </span><a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a></p>
+11
View File
@@ -0,0 +1,11 @@
<div class="framework-extra">
{{if eq .Variant "terminal"}}<span class="email-brand">&gt; {{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>
+6
View File
@@ -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>
+55
View File
@@ -0,0 +1,55 @@
{
"schema": 1,
"repository": "https://github.com/go-gitea/gitea",
"tag": "v28.1.0",
"commit": "4ebd5e319b6a9e629bfd7a04292a6fe85b08dee9",
"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": "3505b23a6285140eb4ff97f7b24846963254013403791189e468967b5c870c95",
"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"
}
}
+356 -119
View File
@@ -8,61 +8,155 @@
:root{ :root{
--bg:#0b1120;--surface:#151d2e;--border:#1e2d45;--text:#d1d9e6;--muted:#6b7d99; --bg:#0b1120;--surface:#151d2e;--border:#1e2d45;--text:#d1d9e6;--muted:#6b7d99;
--accent:#4da6ff;--green:#34d399;--red:#f87171;--warn:#fbbf24; --accent:#4da6ff;--green:#34d399;--red:#f87171;--warn:#fbbf24;
--nav-h:48px;--panel-w:540px;--rad:6px --nav-h:48px;--panel-w:440px;--rad:6px
} }
*{margin:0;padding:0;box-sizing:border-box} *{margin:0;padding:0;box-sizing:border-box}
body{font-family:system-ui,-apple-system,sans-serif;background:var(--bg);color:var(--text);height:100vh;display:flex;flex-direction:column} body{font-family:system-ui,-apple-system,sans-serif;background:var(--bg);color:var(--text);height:100vh;display:flex;flex-direction:column}
/* Top bar */ /* Top bar */
#topbar{height:var(--nav-h);background:var(--surface);border-bottom:1px solid var(--border);display:flex;align-items:center;padding:0 16px;gap:14px;flex-shrink:0;z-index:10} #topbar{min-height:var(--nav-h);background:var(--surface);border-bottom:1px solid var(--border);display:flex;align-items:center;flex-wrap:wrap;padding:6px 12px;gap:8px;row-gap:4px;flex-shrink:0;z-index:30;position:relative}
#topbar .brand{font-weight:700;font-size:14px;color:#fff;white-space:nowrap;letter-spacing:-0.2px} #topbar .brand{font-weight:700;font-size:14px;color:#fff;white-space:nowrap;letter-spacing:-0.2px}
#topbar .brand span{color:var(--accent);font-weight:400;font-size:11px;margin-left:4px;text-transform:uppercase;letter-spacing:1px} #topbar .brand span{color:var(--accent);font-weight:400;font-size:11px;margin-left:4px;text-transform:uppercase;letter-spacing:1px}
#topbar .sep{width:1px;height:20px;background:var(--border)} .ctl-group{display:inline-flex;align-items:center;gap:3px;white-space:nowrap}
#topbar label{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.6px;margin-right:-8px} .ctl-group label{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.6px}
#topbar select{background:var(--bg);color:var(--text);border:1px solid var(--border);padding:6px 10px;border-radius:var(--rad);font-size:12px;cursor:pointer;outline:none;min-width:90px} #topbar .sep{width:1px;height:20px;background:var(--border);flex-shrink:0;align-self:center}
#topbar select{background:var(--bg);color:var(--text);border:1px solid var(--border);padding:5px 8px;border-radius:var(--rad);font-size:12px;cursor:pointer;outline:none;min-width:80px}
#topbar select:focus{border-color:var(--accent)} #topbar select:focus{border-color:var(--accent)}
#topbar .btn-group{display:flex} #topbar .btn-group{display:flex;flex-shrink:0}
#topbar .btn-group button{background:var(--bg);color:var(--muted);border:1px solid var(--border);padding:6px 14px;font-size:12px;font-weight:500;cursor:pointer;transition:all .15s} #topbar .btn-group button{background:var(--bg);color:var(--muted);border:1px solid var(--border);padding:6px 12px;font-size:12px;font-weight:500;cursor:pointer;transition:all .15s}
#topbar .btn-group button:first-child{border-radius:var(--rad) 0 0 var(--rad)} #topbar .btn-group button:first-child{border-radius:var(--rad) 0 0 var(--rad)}
#topbar .btn-group button:last-child{border-radius:0 var(--rad) var(--rad) 0} #topbar .btn-group button:last-child{border-radius:0 var(--rad) var(--rad) 0}
#topbar .btn-group button+button{border-left:none} #topbar .btn-group button+button{border-left:none}
#topbar .btn-group button.on{background:var(--accent);color:#000;border-color:var(--accent);font-weight:600} #topbar .btn-group button.on{background:var(--accent);color:#000;border-color:var(--accent);font-weight:600}
#topbar .hint{font-size:10px;color:var(--muted);opacity:.5} #topbar .hint{font-size:10px;color:var(--muted);opacity:.5;white-space:nowrap}
button:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
/* Topbar responsive — progressively compact as width shrinks */
@media(max-width:900px){#topbar .hint{display:none}}
@media(max-width:700px){
#topbar{gap:5px;row-gap:3px;padding:4px 8px}
#topbar .sep{display:none}
#topbar select{min-width:70px;font-size:11px;padding:4px 6px}
.ctl-group label{font-size:9px}
#topbar .btn-group button{padding:4px 10px;font-size:11px}
}
@media(max-width:500px){
#topbar{gap:4px;row-gap:2px;padding:4px 6px}
#topbar .brand{font-size:12px}
#topbar .brand span{font-size:10px}
#topbar select{min-width:60px;font-size:10px;padding:3px 5px}
#topbar .btn-group button{padding:3px 8px;font-size:10px}
}
/* Main layout */ /* Main layout */
#main{flex:1;display:flex;min-height:0} #main{flex:1;display:flex;position:relative;min-height:0;overflow:hidden}
#preview-area{flex:1;display:flex;align-items:flex-start;justify-content:center;padding:24px;overflow:auto;background:linear-gradient(135deg,#0b1120,#131c30,#0d1525)} #preview-area{flex:1;min-width:0;min-height:0;display:flex;align-items:flex-start;justify-content:center;padding:24px;overflow:auto;background:linear-gradient(135deg,#0b1120,#131c30,#0d1525)}
/* Viewport */ /* Panel and preview share the available width; the handle occupies its own gutter. */
#viewport{border-radius:var(--rad);box-shadow:0 8px 32px rgba(0,0,0,.45);transition:all .3s;flex-shrink:0;overflow:hidden;background:#fff} #panel{
#viewport.desktop{width:1386px;height:780px} --panel-w:min(440px,40vw);
#viewport.mobile{width:390px;height:780px;border-radius:28px;border:3px solid #1e293b;box-shadow:0 8px 32px rgba(0,0,0,.5),inset 0 0 0 1px #0f172a;display:flex;flex-direction:column} position:relative;flex:0 0 var(--panel-w);width:var(--panel-w);min-width:0;
#viewport.mobile::before{content:'';display:block;height:24px;background:#1e293b;flex-shrink:0} margin:12px 12px 12px 40px;
#preview-frame{width:100%;height:100%;border:none;display:block;background:#fff} background:var(--surface);
#viewport.desktop #preview-frame{border-radius:var(--rad)} border:1px solid var(--border);
border-radius:10px;
box-shadow:-4px 4px 32px rgba(0,0,0,.5),0 0 0 1px rgba(255,255,255,.03) inset;
display:flex;flex-direction:column;
transition:flex-basis .3s ease,width .3s ease;
}
#panel.collapsed{
flex-basis:0;width:0;border-width:0;background:transparent;box-shadow:none;
}
#panel.collapsed #panel-body{visibility:hidden}
/* Side panel */ /* Panel drag-handle / collapse tab */
#panel{width:var(--panel-w);background:var(--surface);border-left:1px solid var(--border);overflow-y:auto;flex-shrink:0;display:flex;flex-direction:column} #panel-handle{
#panel section{padding:14px 16px;border-bottom:1px solid var(--border)} position:absolute;left:-40px;top:50%;transform:translateY(-50%);
width:40px;height:80px;
background:var(--surface);
border:1px solid var(--border);
border-right:none;
border-radius:6px 0 0 6px;
cursor:pointer;
display:flex;align-items:center;justify-content:center;
transition:background .15s;
color:var(--muted);
}
#panel-handle:hover{background:var(--bg)}
#panel-handle svg{
width:20px;height:20px;
stroke:var(--muted);
fill:none;stroke-width:2;
transition:transform .35s cubic-bezier(.4,0,.2,1),stroke .15s;
}
#panel-handle:hover svg{stroke:var(--text)}
#panel.collapsed #panel-handle svg{transform:rotate(180deg)}
/* Panel inner scroll */
#panel-body{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;border-radius:10px}
/* Section */
#panel section{padding:12px 14px;border-bottom:1px solid var(--border)}
#panel section:last-child{border-bottom:none} #panel section:last-child{border-bottom:none}
#panel h3{font-size:10px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.8px;margin-bottom:10px}
/* Collapsible section header */
.section-hdr{
display:flex;align-items:center;gap:6px;
cursor:pointer;
user-select:none;
padding:2px 0;
border-radius:4px;
transition:color .15s;
}
.section-hdr:hover{color:#fff}
.section-hdr h3{
font-size:10px;font-weight:600;color:inherit;text-transform:uppercase;letter-spacing:.8px;
flex:1;
}
.section-chevron{
width:12px;height:12px;
stroke:currentColor;fill:none;stroke-width:2;
transition:transform .25s ease;
flex-shrink:0;
opacity:.5;
}
.section-hdr:hover .section-chevron{opacity:1}
section.collapsed .section-chevron{transform:rotate(-90deg)}
/* Collapsible content wrapper */
.section-content{
display:grid;grid-template-rows:1fr;opacity:1;margin-top:10px;
transition:grid-template-rows .3s ease,opacity .25s ease,margin-top .3s ease;
}
.section-content-inner{min-height:0;overflow:hidden}
section.collapsed .section-content{
grid-template-rows:0fr;opacity:0;margin-top:0;
}
#sec-params .section-content-inner{max-height:50vh;overflow:auto;overscroll-behavior:contain}
section.collapsed .section-content-inner{overflow:hidden}
#panel .card{background:var(--bg);border-radius:var(--rad);padding:10px 12px;margin-bottom:8px} #panel .card{background:var(--bg);border-radius:var(--rad);padding:10px 12px;margin-bottom:8px}
#panel .card:last-child{margin-bottom:0}
#panel .card .label{font-size:10px;color:var(--muted);margin-bottom:2px} #panel .card .label{font-size:10px;color:var(--muted);margin-bottom:2px}
#panel .card .value{font-size:12px;color:var(--text)} #panel .card .value{font-size:12px;color:var(--text)}
#panel table{width:100%;border-collapse:collapse} #panel table{width:100%;border-collapse:collapse}
#panel table td{padding:4px 6px;font-size:11px;border-bottom:1px solid rgba(255,255,255,.03);vertical-align:top} #panel table td{padding:4px 6px;font-size:11px;border-bottom:1px solid rgba(255,255,255,.03);vertical-align:top}
#panel table td:first-child{font-family:monospace;color:var(--accent);font-size:10px;width:0;white-space:nowrap} #panel table td:first-child{font-family:monospace;color:var(--accent);font-size:10px;overflow-wrap:anywhere}
#panel table td:last-child{color:var(--muted);text-align:right;font-size:10px;word-break:break-all} #panel table td:nth-child(2){color:var(--muted);font-size:10px;overflow-wrap:anywhere}
#panel table td:last-child{text-align:right;width:1%}
#panel .badge{display:inline-block;font-size:9px;padding:1px 6px;border-radius:3px;font-weight:600;letter-spacing:.2px;white-space:nowrap} #panel .badge{display:inline-block;font-size:9px;padding:1px 6px;border-radius:3px;font-weight:600;letter-spacing:.2px;white-space:nowrap}
.badge-req{background:rgba(52,211,153,.12);color:var(--green)} .badge-req{background:rgba(52,211,153,.12);color:var(--green)}
.badge-opt{background:rgba(107,125,153,.1);color:var(--muted)} .badge-opt{background:rgba(107,125,153,.1);color:var(--muted)}
#panel .stat{font-size:10px;color:var(--muted);margin-top:6px} #panel .stat{font-size:10px;color:var(--muted);margin-top:6px}
/* Client indicators */ /* Viewport */
#panel .indicators{display:flex;flex-wrap:wrap;gap:4px 12px} #viewport{border:0 solid #1e293b;border-radius:var(--rad);box-shadow:0 8px 32px rgba(0,0,0,.45);flex-shrink:0;max-width:100%;overflow:hidden;background:#fff;display:flex;flex-direction:column;transition:width .3s ease,border-radius .3s ease,border-width .3s ease,box-shadow .3s ease}
#panel .indicators span{font-size:11px;color:var(--muted);display:inline-flex;align-items:center;gap:5px} #viewport.desktop{width:100%;max-width:1386px;height:780px}
.dot{width:6px;height:6px;border-radius:50%;display:inline-block;flex-shrink:0} #viewport.mobile{width:390px;height:780px;border-radius:28px;border:3px solid #1e293b;box-shadow:0 8px 32px rgba(0,0,0,.5),inset 0 0 0 1px #0f172a;display:flex;flex-direction:column}
.d-on{background:var(--green)}.d-off{background:var(--red)}.d-warn{background:var(--warn)} #viewport::before{content:'';display:block;height:0;background:#1e293b;flex-shrink:0;transition:height .3s ease}
#viewport.mobile::before{height:24px}
#preview-frame{width:100%;height:100%;flex:1;min-height:0;border:none;display:block;background:#fff}
#viewport.desktop #preview-frame{border-radius:var(--rad)}
/* Loading */ /* Loading */
.loading-wrap{display:flex;align-items:center;justify-content:center;height:100%;color:var(--muted);font-size:13px;flex-direction:column;gap:8px} .loading-wrap{display:flex;align-items:center;justify-content:center;height:100%;color:var(--muted);font-size:13px;flex-direction:column;gap:8px}
@@ -76,43 +170,38 @@ body{font-family:system-ui,-apple-system,sans-serif;background:var(--bg);color:v
.message code{background:rgba(0,0,0,0.12);padding:1px 6px;border-radius:3px} .message code{background:rgba(0,0,0,0.12);padding:1px 6px;border-radius:3px}
/* Scrollbar — neutral gray, consistent across all browsers */ /* Scrollbar — neutral gray, consistent across all browsers */
/* Standards-track: Firefox 64+ / Chrome 121+ / Edge 121+ */ html,body,#preview-area,#panel-body,#sec-params .section-content-inner{
html,body,#preview-area,#panel{
scrollbar-width:thin; scrollbar-width:thin;
scrollbar-color:rgba(107,125,153,.25) transparent; scrollbar-color:rgba(107,125,153,.25) transparent;
} }
/* IE / Legacy Edge */ #preview-area,#panel-body{
#preview-area,#panel{
-ms-overflow-style:-ms-autohiding-scrollbar; -ms-overflow-style:-ms-autohiding-scrollbar;
} }
/* WebKit scrollbars — scoped to actual scrollable regions only */
#preview-area::-webkit-scrollbar, #preview-area::-webkit-scrollbar,
#panel::-webkit-scrollbar { width:6px; height:6px } #sec-params .section-content-inner::-webkit-scrollbar,
#panel-body::-webkit-scrollbar { width:6px; height:6px }
#preview-area::-webkit-scrollbar-track, #preview-area::-webkit-scrollbar-track,
#panel::-webkit-scrollbar-track { background:transparent } #sec-params .section-content-inner::-webkit-scrollbar-track,
#panel-body::-webkit-scrollbar-track { background:transparent }
#preview-area::-webkit-scrollbar-thumb, #preview-area::-webkit-scrollbar-thumb,
#panel::-webkit-scrollbar-thumb { #sec-params .section-content-inner::-webkit-scrollbar-thumb,
#panel-body::-webkit-scrollbar-thumb {
background-color:rgba(107,125,153,.25); background-color:rgba(107,125,153,.25);
border-radius:3px; border-radius:3px;
border:1px solid rgba(255,255,255,.06); border:1px solid rgba(255,255,255,.06);
} }
#preview-area::-webkit-scrollbar-thumb:hover, #preview-area::-webkit-scrollbar-thumb:hover,
#panel::-webkit-scrollbar-thumb:hover { background-color:rgba(107,125,153,.45) } #sec-params .section-content-inner::-webkit-scrollbar-thumb:hover,
#panel-body::-webkit-scrollbar-thumb:hover { background-color:rgba(107,125,153,.45) }
#preview-area::-webkit-scrollbar-corner, #preview-area::-webkit-scrollbar-corner,
#panel::-webkit-scrollbar-corner { background:transparent } #sec-params .section-content-inner::-webkit-scrollbar-corner,
/* The panel's inner parameter section (flex:1;overflow-y:auto) */ #panel-body::-webkit-scrollbar-corner { background:transparent }
#panel section::-webkit-scrollbar { width:6px; height:6px }
#panel section::-webkit-scrollbar-track { background:transparent }
#panel section::-webkit-scrollbar-thumb {
background-color:rgba(107,125,153,.25);
border-radius:3px;
border:1px solid rgba(255,255,255,.06);
}
#panel section::-webkit-scrollbar-thumb:hover { background-color:rgba(107,125,153,.45) }
#panel section::-webkit-scrollbar-corner { background:transparent }
#panel section{scrollbar-width:thin;scrollbar-color:rgba(107,125,153,.25) transparent}
@media(max-width:850px){#panel{display:none}#topbar .hint{display:none}} @media(max-width:700px){
#panel{--panel-w:50vw;margin-top:6px;margin-bottom:6px;margin-right:6px}
#preview-area{padding:12px}
}
@media(prefers-reduced-motion:reduce){#panel,#panel-handle svg,.section-content,.section-chevron,#viewport,#viewport::before{transition:none}}
</style> </style>
</head> </head>
<body> <body>
@@ -120,23 +209,34 @@ html,body,#preview-area,#panel{
<nav id="topbar"> <nav id="topbar">
<span class="brand">Gitea Mail Templates <span>Preview</span></span> <span class="brand">Gitea Mail Templates <span>Preview</span></span>
<span class="sep"></span> <span class="sep"></span>
<span class="ctl-group">
<label>Theme</label> <label>Theme</label>
<select id="sel-theme" onchange="switchTheme(this.value)"></select> <select id="sel-theme" onchange="switchTheme(this.value)"></select>
</span>
<span class="sep"></span> <span class="sep"></span>
<span class="ctl-group">
<label>Template</label> <label>Template</label>
<select id="sel-template" onchange="switchTemplate(this.value)"></select> <select id="sel-template" onchange="switchTemplate(this.value)"></select>
</span>
<span class="sep"></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> <label>View</label>
<select id="sel-client" onchange="switchClient(this.value)"> <select id="sel-client" onchange="switchClient(this.value)">
<option value="modern">Modern</option> <option value="modern">Modern</option>
<option value="source">Source</option> <option value="source">Source</option>
</select> </select>
</span>
<span class="sep"></span> <span class="sep"></span>
<div class="btn-group"> <div class="btn-group">
<button id="btn-desk" class="on" onclick="setViewport('desktop')">Desktop</button> <button id="btn-desk" class="on" onclick="setViewport('desktop')">Desktop</button>
<button id="btn-mob" onclick="setViewport('mobile')">Mobile</button> <button id="btn-mob" onclick="setViewport('mobile')">Mobile</button>
</div> </div>
<span class="hint">keys: d/m &nbsp; &larr;&rarr; tab &nbsp; &uarr;&darr; select</span> <span class="hint">keys: d/m &nbsp; &larr;&rarr; tab &nbsp; &uarr;&darr; select &nbsp; p toggle panel</span>
</nav> </nav>
<div id="messages"></div> <div id="messages"></div>
@@ -153,8 +253,19 @@ html,body,#preview-area,#panel{
</div> </div>
<aside id="panel"> <aside id="panel">
<section> <!-- Collapse handle tab on the left edge -->
<button id="panel-handle" type="button" title="Collapse panel" aria-label="Collapse panel" aria-expanded="true" aria-controls="panel-body" onclick="togglePanel()">
<svg viewBox="0 0 24 24" aria-hidden="true"><polyline points="9 18 15 12 9 6"/></svg>
</button>
<div id="panel-body">
<section id="sec-info">
<div class="section-hdr" onclick="toggleSection('sec-info')">
<h3>Current Template</h3> <h3>Current Template</h3>
<svg class="section-chevron" viewBox="0 0 24 24"><polyline points="6 9 12 15 18 9"/></svg>
</div>
<div class="section-content">
<div class="section-content-inner">
<div class="card"> <div class="card">
<div class="label">Type</div> <div class="label">Type</div>
<div class="value" id="panel-name">-</div> <div class="value" id="panel-name">-</div>
@@ -164,15 +275,34 @@ html,body,#preview-area,#panel{
<div class="value" id="panel-desc" style="font-size:11px;line-height:1.5">-</div> <div class="value" id="panel-desc" style="font-size:11px;line-height:1.5">-</div>
</div> </div>
<div class="stat" id="panel-stats"></div> <div class="stat" id="panel-stats"></div>
</div>
</div>
</section> </section>
<section style="flex:1;overflow-y:auto">
<section id="sec-params">
<div class="section-hdr" onclick="toggleSection('sec-params')">
<h3>Parameters</h3> <h3>Parameters</h3>
<svg class="section-chevron" viewBox="0 0 24 24"><polyline points="6 9 12 15 18 9"/></svg>
</div>
<div class="section-content">
<div class="section-content-inner">
<table id="panel-params"><tr><td colspan="2" style="color:var(--muted)">-</td></tr></table> <table id="panel-params"><tr><td colspan="2" style="color:var(--muted)">-</td></tr></table>
</div>
</div>
</section> </section>
<section>
<section id="sec-source">
<div class="section-hdr" onclick="toggleSection('sec-source')">
<h3>Source</h3> <h3>Source</h3>
<svg class="section-chevron" viewBox="0 0 24 24"><polyline points="6 9 12 15 18 9"/></svg>
</div>
<div class="section-content">
<div class="section-content-inner">
<div class="card" style="font-family:monospace;font-size:10px;color:var(--muted);word-break:break-all" id="panel-path">-</div> <div class="card" style="font-family:monospace;font-size:10px;color:var(--muted);word-break:break-all" id="panel-path">-</div>
</div>
</div>
</section> </section>
</div>
</aside> </aside>
</div> </div>
@@ -181,10 +311,14 @@ html,body,#preview-area,#panel{
// //
// State — REGISTRY and PARAMS are loaded from rendered.js (single source of truth) // 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 REGISTRY = {};
let PARAMS = {}; 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 let devMode = false; // true once SSE connects
// //
@@ -196,21 +330,31 @@ function transform(html, mode) {
const e = html.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); const e = html.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
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 '<!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 // Render preview
// //
function render() { function render() {
const request=++renderRequest;
clearTimeout(renderTimer);
const frame = document.getElementById('preview-frame'); const frame = document.getElementById('preview-frame');
const loading = document.getElementById('loading'); const loading = document.getElementById('loading');
if(!rendered) return; // no data yet — init handles the error display if(!rendered) return;
// Sandbox only in dev mode; file: protocol rejects sandboxed srcdoc
if (location.protocol !== 'file:') {
frame.setAttribute('sandbox', ''); frame.setAttribute('sandbox', '');
}
frame.style.display = 'none'; frame.style.display = 'none';
loading.style.display = 'flex'; loading.style.display = 'flex';
@@ -218,31 +362,50 @@ function render() {
const src = rendered; const src = rendered;
const raw = src[state.theme]?.[state.template]; 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>'; let content = raw ? transform(raw, state.client) : '<p style=\"color:red;padding:20px\">Not found: '+state.theme+'/'+state.template+'</p>';
// Inject invisible scrollbar into iframe content for immersive preview 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>');
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>');
let done = false; let done = false;
function finish() { function finish() {
if (done) return; done = true; if (done || request!==renderRequest) return; done = true;
clearTimeout(renderTimer);
loading.style.display = 'none'; loading.style.display = 'none';
frame.style.display = 'block'; frame.style.display = 'block';
frame.onload = null; frame.onload = null;
} }
frame.onload = finish; frame.onload = finish;
frame.srcdoc = content; frame.srcdoc = content;
// Safety timeout — force-hide loading if onload stalls (max 5 s) renderTimer=setTimeout(finish, 5000);
setTimeout(finish, 5000);
updatePanel(); updatePanel();
} }
// //
// Panel updates // Panel — collapse/expand
//
function togglePanel() {
const panel = document.getElementById('panel');
const collapsed = panel.classList.toggle('collapsed');
const handle = document.getElementById('panel-handle');
handle.setAttribute('aria-expanded', String(!collapsed));
handle.setAttribute('aria-label', collapsed ? 'Expand panel' : 'Collapse panel');
handle.title = collapsed ? 'Expand panel' : 'Collapse panel';
document.getElementById('panel-body').inert = collapsed;
}
function toggleSection(id) {
const sec = document.getElementById(id);
const collapsed = sec.classList.toggle('collapsed');
sec.querySelector('.section-content').inert = collapsed;
}
//
// Panel content updates
// //
function updatePanel() { function updatePanel() {
const escape = v => String(v).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
const r = REGISTRY[state.template]; const r = REGISTRY[state.template];
document.getElementById('panel-name').textContent = r ? r.name : state.template; document.getElementById('panel-name').textContent = r ? r.name : state.template;
document.getElementById('panel-desc').textContent = r ? r.desc : ''; 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 // Template stats
const themes = rendered ? Object.keys(rendered).length : 0; const themes = rendered ? Object.keys(rendered).length : 0;
@@ -255,13 +418,13 @@ function updatePanel() {
} }
} }
document.getElementById('panel-stats').textContent = 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] || {}; const p = PARAMS[state.template] || {};
let rows = ''; let rows = '';
for(const [k,v] of Object.entries(p)){ 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'); 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>'; document.getElementById('panel-params').innerHTML = rows || '<tr><td colspan="3" style="color:var(--muted)">-</td></tr>';
} }
@@ -270,14 +433,14 @@ function updatePanel() {
// Events // Events
// //
function switchTheme(v) { state.theme=v; render(); } 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 switchClient(v) { state.client=v; render(); }
function setViewport(v) { function setViewport(v) {
if (state.viewport === v) return;
state.viewport=v; state.viewport=v;
document.getElementById('btn-desk').classList.toggle('on',v==='desktop'); document.getElementById('btn-desk').classList.toggle('on',v==='desktop');
document.getElementById('btn-mob').classList.toggle('on',v==='mobile'); document.getElementById('btn-mob').classList.toggle('on',v==='mobile');
document.getElementById('viewport').className=v; document.getElementById('viewport').className=v;
render();
} }
// //
@@ -287,65 +450,140 @@ document.addEventListener('keydown',e=>{
// Viewport shortcuts (no modifiers) // Viewport shortcuts (no modifiers)
if(e.key==='d'&&!e.ctrlKey&&!e.metaKey&&!e.altKey){ setViewport('desktop'); return; } if(e.key==='d'&&!e.ctrlKey&&!e.metaKey&&!e.altKey){ setViewport('desktop'); return; }
if(e.key==='m'&&!e.ctrlKey&&!e.metaKey&&!e.altKey){ setViewport('mobile'); return; } if(e.key==='m'&&!e.ctrlKey&&!e.metaKey&&!e.altKey){ setViewport('mobile'); return; }
// 'p' to toggle the side panel
if(e.key==='p'&&!e.ctrlKey&&!e.metaKey&&!e.altKey){ togglePanel(); return; }
// ← → cycle focus between Theme / Template / Client selects // ← → cycle focus between Theme / Template / Client selects
// When a select dropdown is open the browser captures keys, so this only runs when closed const selects=['sel-theme','sel-template','sel-locale','sel-client'];
const selects=['sel-theme','sel-template','sel-client'];
const activeId=document.activeElement?.id; const activeId=document.activeElement?.id;
const cur=selects.indexOf(activeId); const cur=selects.indexOf(activeId);
if(e.key==='ArrowRight'||e.key==='ArrowLeft'){ if(e.key==='ArrowRight'||e.key==='ArrowLeft'){
e.preventDefault(); e.preventDefault();
if(cur>=0){ if(cur>=0){
// Cycle to next/previous select
const dir=e.key==='ArrowRight'?1:-1; const dir=e.key==='ArrowRight'?1:-1;
const next=(cur+dir+selects.length)%selects.length; const next=(cur+dir+selects.length)%selects.length;
document.getElementById(selects[next])?.focus(); document.getElementById(selects[next])?.focus();
} else { } else {
// First press — jump to template (middle)
document.getElementById('sel-template')?.focus(); document.getElementById('sel-template')?.focus();
} }
} }
// ↑ ↓ are handled natively by the focused <select> — onchange fires → render()
}); });
// //
// Init — load everything from rendered.js (single source of truth) // Manifest and language bundles
// function fillSelect(id, entries, selected) {
(function(){ const select = document.getElementById(id);
// Load REGISTRY and PARAMS from rendered.js select.replaceChildren();
if(window.__REGISTRY__) REGISTRY = window.__REGISTRY__; for (const [value,label] of entries) {
if(window.__PARAMS__) PARAMS = window.__PARAMS__; const option = document.createElement('option');
option.value=value; option.textContent=label; select.appendChild(option);
if(window.__RENDERED__){
rendered = window.__RENDERED__;
// Populate theme selector from rendered data
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] || '';
// Populate template selector from REGISTRY
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);
} }
state.template = Object.keys(REGISTRY)[0] || ''; select.value=selected;
} else { }
document.getElementById('loading').style.display='none';
showMessage('rendered.js not found — run <code>cd tools &amp;&amp; go run . preview all</code> to generate', 'error'); // Stable native names also cover browsers with incomplete ICU locale data.
const LOCALE_NAMES = {
'cs-CZ':'čeština (Česko)', 'de-DE':'Deutsch (Deutschland)',
'el-GR':'Ελληνικά (Ελλάδα)', 'en-US':'English (United States)',
'es-ES':'español (España)', 'fa-IR':'فارسی (ایران)',
'fi-FI':'suomi (Suomi)', 'fr-FR':'français (France)',
'ga-IE':'Gaeilge (Éire)', 'hu-HU':'magyar (Magyarország)',
'id-ID':'Indonesia (Indonesia)', 'is-IS':'íslenska (Ísland)',
'it-IT':'italiano (Italia)', 'ja-JP':'日本語 (日本)',
'ko-KR':'한국어 (대한민국)', 'lv-LV':'latviešu (Latvija)',
'nl-NL':'Nederlands (Nederland)', 'pl-PL':'polski (Polska)',
'pt-BR':'português (Brasil)', 'pt-PT':'português (Portugal)',
'ru-RU':'русский (Россия)', 'si-LK':'සිංහල (ශ්‍රී ලංකාව)',
'sk-SK':'slovenčina (Slovensko)',
'sv-SE':'svenska (Sverige)', 'tr-TR':'Türkçe (Türkiye)',
'uk-UA':'українська (Україна)', 'zh-CN':'中文 (简体,中国)',
'zh-TW':'中文 (繁體,台灣)'
};
function localeLabel(locale) {
let name = LOCALE_NAMES[locale];
if (!name) {
try {
if (Intl.DisplayNames.supportedLocalesOf([locale]).length) {
name = new Intl.DisplayNames([locale], {type:'language', fallback:'none'}).of(locale);
}
} catch (_) { /* Unknown languages remain selectable by their locale code. */ }
}
return name && name !== locale ? locale + ' — ' + name : locale;
}
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=>[locale,localeLabel(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(); 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 &amp;&amp; go run . preview all</code> to generate','error');
}
})(); })();
// Message helpers // Message helpers
@@ -361,15 +599,12 @@ function clearMessages() {
document.getElementById('messages').innerHTML = ''; document.getElementById('messages').innerHTML = '';
} }
// Dev-mode detection
// The SSE open handler will call setDevMode() which clears any stale messages.
function setDevMode(on) { function setDevMode(on) {
devMode = on; devMode = on;
if (on) { if (on) {
clearMessages(); clearMessages();
showMessage( 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' 'info'
).id = 'msg-dev-disclaimer'; ).id = 'msg-dev-disclaimer';
} }
@@ -377,6 +612,7 @@ function setDevMode(on) {
// Dev-server live-reload (SSE) // Dev-server live-reload (SSE)
(function(){ (function(){
if (location.protocol === 'file:') return;
let es; let es;
try { try {
es = new EventSource('/events'); es = new EventSource('/events');
@@ -390,14 +626,15 @@ function setDevMode(on) {
const script = document.createElement('script'); const script = document.createElement('script');
script.src = 'rendered.js?v=' + Date.now(); script.src = 'rendered.js?v=' + Date.now();
script.onload = function() { script.onload = function() {
if (window.__RENDERED__) rendered = window.__RENDERED__; script.remove();
if (window.__REGISTRY__) REGISTRY = window.__REGISTRY__; window.__MAIL_PREVIEW_DATA__ = {};
if (window.__PARAMS__) PARAMS = window.__PARAMS__; localeLoads.clear();
render(); applyManifest(window.__MAIL_PREVIEW__);
switchLocale(state.locale);
}; };
document.head.appendChild(script); document.head.appendChild(script);
} else if (msg.type === 'error') { } else if (msg.type === 'error') {
showMessage(msg.message, 'error'); showMessage('', 'error').textContent = msg.message;
} }
} catch(_) {} } catch(_) {}
}; };
-142
View File
@@ -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:rgba(255,255,255,.4);letter-spacing:4px;text-transform:uppercase }
.email-header { padding:0 0 32px;text-align:center }
/* Card */
.email-card { padding:48px 40px;background:rgba(25,20,50,.8);border:1px solid rgba(13,148,136,.2);box-shadow:0 0 60px rgba(13,148,136,.08),0 0 120px rgba(99,102,241,.04) }
.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:rgba(210,252,245,.88);font-weight:300 }
.email-subtext { margin:0;font-size:13px;line-height:1.8;color:rgba(180,195,215,.95);font-weight:300 }
/* Divider */
.email-divider { height:1px;background:linear-gradient(90deg,transparent,rgba(13,148,136,.3),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:rgba(13,148,136,.18);border:1px solid rgba(13,148,136,.3);letter-spacing:1px }
.email-btn-table { margin:0 0 32px }
/* Fallback link */
.email-link-box { border:1px solid rgba(13,148,136,.18);background:rgba(13,148,136,.03) }
.email-link-label { margin:0 0 8px;font-size:11px;color:rgba(180,252,238,.75);font-weight:300 }
.email-link-url { margin:0;font-size:11px;font-family:monospace;color:#99f6e4;background:rgba(0,0,0,.2);padding:10px 12px;border:1px solid rgba(13,148,136,.15);word-break:break-all }
/* Footer */
.email-footer { padding:32px 0 0;text-align:center }
.email-footer-text { margin:0;font-size:11px;color:rgba(160,175,200,.55);font-weight:300 }
.email-footer-link { color:rgba(94,234,212,.5);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:rgba(210,252,245,.88)!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:rgba(160,175,200,.5);">
{{.locale.Tr "mail.team_invite.text_3" .Invite.Email}}
</p>
</td>
</tr>
<!-- Footer -->
<tr>
<td class="email-footer">
<p class="email-footer-text">
&copy;
<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:rgba(255,255,255,.4);letter-spacing:4px;text-transform:uppercase }
.email-header { padding:0 0 32px;text-align:center }
/* Card */
.email-card { padding:48px 40px;background:rgba(25,20,50,.8);border:1px solid rgba(13,148,136,.2);box-shadow:0 0 60px rgba(13,148,136,.08),0 0 120px rgba(99,102,241,.04) }
.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:rgba(210,252,245,.88);font-weight:300 }
.email-subtext { margin:0;font-size:13px;line-height:1.8;color:rgba(180,195,215,.95);font-weight:300 }
/* Divider */
.email-divider { height:1px;background:linear-gradient(90deg,transparent,rgba(13,148,136,.3),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:rgba(13,148,136,.18);border:1px solid rgba(13,148,136,.3);letter-spacing:1px }
.email-btn-table { margin:0 0 32px }
/* Fallback link */
.email-link-box { border:1px solid rgba(13,148,136,.18);background:rgba(13,148,136,.03) }
.email-link-label { margin:0 0 8px;font-size:11px;color:rgba(180,252,238,.75);font-weight:300 }
.email-link-url { margin:0;font-size:11px;font-family:monospace;color:#99f6e4;background:rgba(0,0,0,.2);padding:10px 12px;border:1px solid rgba(13,148,136,.15);word-break:break-all }
/* Footer */
.email-footer { padding:32px 0 0;text-align:center }
.email-footer-text { margin:0;font-size:11px;color:rgba(160,175,200,.55);font-weight:300 }
.email-footer-link { color:rgba(94,234,212,.5);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:rgba(210,252,245,.88)!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">
&copy;
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
</p>
</td>
</tr>
</table>
</center>
</body>
</html>
-134
View File
@@ -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:rgba(255,255,255,.4);letter-spacing:4px;text-transform:uppercase }
.email-header { padding:0 0 32px;text-align:center }
/* Card */
.email-card { padding:48px 40px;background:rgba(25,20,50,.8);border:1px solid rgba(13,148,136,.2);box-shadow:0 0 60px rgba(13,148,136,.08),0 0 120px rgba(99,102,241,.04) }
.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:rgba(210,252,245,.88);font-weight:300 }
.email-subtext { margin:0;font-size:13px;line-height:1.8;color:rgba(180,195,215,.95);font-weight:300 }
/* Divider */
.email-divider { height:1px;background:linear-gradient(90deg,transparent,rgba(13,148,136,.3),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:rgba(13,148,136,.18);border:1px solid rgba(13,148,136,.3);letter-spacing:1px }
.email-btn-table { margin:0 0 32px }
/* Fallback link */
.email-link-box { border:1px solid rgba(13,148,136,.18);background:rgba(13,148,136,.03) }
.email-link-label { margin:0 0 8px;font-size:11px;color:rgba(180,252,238,.75);font-weight:300 }
.email-link-url { margin:0;font-size:11px;font-family:monospace;color:#99f6e4;background:rgba(0,0,0,.2);padding:10px 12px;border:1px solid rgba(13,148,136,.15);word-break:break-all }
/* Footer */
.email-footer { padding:32px 0 0;text-align:center }
.email-footer-text { margin:0;font-size:11px;color:rgba(160,175,200,.55);font-weight:300 }
.email-footer-link { color:rgba(94,234,212,.5);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:rgba(210,252,245,.88)!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">
&copy;
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
</p>
</td>
</tr>
</table>
</center>
</body>
</html>
-140
View File
@@ -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:rgba(255,255,255,.4);letter-spacing:4px;text-transform:uppercase }
.email-header { padding:0 0 32px;text-align:center }
/* Card */
.email-card { padding:48px 40px;background:rgba(25,20,50,.8);border:1px solid rgba(13,148,136,.2);box-shadow:0 0 60px rgba(13,148,136,.08),0 0 120px rgba(99,102,241,.04) }
.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:rgba(210,252,245,.88);font-weight:300 }
.email-subtext { margin:0;font-size:13px;line-height:1.8;color:rgba(180,195,215,.95);font-weight:300 }
/* Divider */
.email-divider { height:1px;background:linear-gradient(90deg,transparent,rgba(13,148,136,.3),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:rgba(13,148,136,.18);border:1px solid rgba(13,148,136,.3);letter-spacing:1px }
.email-btn-table { margin:0 0 32px }
/* Fallback link */
.email-link-box { border:1px solid rgba(13,148,136,.18);background:rgba(13,148,136,.03) }
.email-link-label { margin:0 0 8px;font-size:11px;color:rgba(180,252,238,.75);font-weight:300 }
.email-link-url { margin:0;font-size:11px;font-family:monospace;color:#99f6e4;background:rgba(0,0,0,.2);padding:10px 12px;border:1px solid rgba(13,148,136,.15);word-break:break-all }
/* Footer */
.email-footer { padding:32px 0 0;text-align:center }
.email-footer-text { margin:0;font-size:11px;color:rgba(160,175,200,.55);font-weight:300 }
.email-footer-link { color:rgba(94,234,212,.5);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:rgba(210,252,245,.88)!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">
&copy;
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
</p>
</td>
</tr>
</table>
</center>
</body>
</html>
-209
View File
@@ -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:rgba(255,255,255,.4);letter-spacing:4px;text-transform:uppercase }
.email-header { padding:0 0 32px;text-align:center }
/* Card */
.email-card { padding:48px 40px;background:rgba(25,20,50,.8);border:1px solid rgba(13,148,136,.2);box-shadow:0 0 60px rgba(13,148,136,.08),0 0 120px rgba(99,102,241,.04) }
.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:rgba(210,252,245,.88);font-weight:300 }
.email-subtext { margin:0;font-size:13px;line-height:1.8;color:rgba(180,195,215,.95);font-weight:300 }
/* Divider */
.email-divider { height:1px;background:linear-gradient(90deg,transparent,rgba(13,148,136,.3),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:rgba(13,148,136,.18);border:1px solid rgba(13,148,136,.3);letter-spacing:1px }
.email-btn-table { margin:0 0 32px }
/* Fallback link */
.email-link-box { border:1px solid rgba(13,148,136,.18);background:rgba(13,148,136,.03) }
.email-link-label { margin:0 0 8px;font-size:11px;color:rgba(180,252,238,.75);font-weight:300 }
.email-link-url { margin:0;font-size:11px;font-family:monospace;color:#99f6e4;background:rgba(0,0,0,.2);padding:10px 12px;border:1px solid rgba(13,148,136,.15);word-break:break-all }
/* Footer */
.email-footer { padding:32px 0 0;text-align:center }
.email-footer-text { margin:0;font-size:11px;color:rgba(160,175,200,.55);font-weight:300 }
.email-footer-link { color:rgba(94,234,212,.5);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:rgba(210,252,245,.88)!important }
/* Content */
.aurora-content,.aurora-content * { color:rgba(210,252,245,.88)!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:rgba(0,0,0,.3)!important;border-color:rgba(13,148,136,.2)!important }
.aurora-content blockquote { color:rgba(255,255,255,.6)!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:rgba(13,148,136,.3)!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}}
&nbsp;{{.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/{{.ID}}">{{ShortSha .ID.String}}</a> - {{.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">
&copy;
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
</p>
</td>
</tr>
</table>
</center>
</body>
</html>
-166
View File
@@ -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:rgba(255,255,255,.4);letter-spacing:4px;text-transform:uppercase }
.email-header { padding:0 0 32px;text-align:center }
/* Card */
.email-card { padding:48px 40px;background:rgba(25,20,50,.8);border:1px solid rgba(13,148,136,.2);box-shadow:0 0 60px rgba(13,148,136,.08),0 0 120px rgba(99,102,241,.04) }
.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:rgba(210,252,245,.88);font-weight:300 }
.email-subtext { margin:0;font-size:13px;line-height:1.8;color:rgba(180,195,215,.95);font-weight:300 }
/* Divider */
.email-divider { height:1px;background:linear-gradient(90deg,transparent,rgba(13,148,136,.3),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:rgba(13,148,136,.18);border:1px solid rgba(13,148,136,.3);letter-spacing:1px }
.email-btn-table { margin:0 0 32px }
/* Fallback link */
.email-link-box { border:1px solid rgba(13,148,136,.18);background:rgba(13,148,136,.03) }
.email-link-label { margin:0 0 8px;font-size:11px;color:rgba(180,252,238,.75);font-weight:300 }
.email-link-url { margin:0;font-size:11px;font-family:monospace;color:#99f6e4;background:rgba(0,0,0,.2);padding:10px 12px;border:1px solid rgba(13,148,136,.15);word-break:break-all }
/* Footer */
.email-footer { padding:32px 0 0;text-align:center }
.email-footer-text { margin:0;font-size:11px;color:rgba(160,175,200,.55);font-weight:300 }
.email-footer-link { color:rgba(94,234,212,.5);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:rgba(210,252,245,.88)!important }
/* Content */
.aurora-content,.aurora-content * { color:rgba(210,252,245,.88)!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:rgba(0,0,0,.3)!important;border-color:rgba(13,148,136,.2)!important }
.aurora-content blockquote { color:rgba(255,255,255,.6)!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:rgba(13,148,136,.3)!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>{{.locale.Tr "mail.release.note"}}</p>
<div class="aurora-content" style="font-size:14px;line-height:2;">{{.Release.RenderedNote}}</div>
{{end}}
<p>{{.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">
&copy;
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
</p>
</td>
</tr>
</table>
</center>
</body>
</html>
-135
View File
@@ -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:rgba(255,255,255,.4);letter-spacing:4px;text-transform:uppercase }
.email-header { padding:0 0 32px;text-align:center }
/* Card */
.email-card { padding:48px 40px;background:rgba(25,20,50,.8);border:1px solid rgba(13,148,136,.2);box-shadow:0 0 60px rgba(13,148,136,.08),0 0 120px rgba(99,102,241,.04) }
.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:rgba(210,252,245,.88);font-weight:300 }
.email-subtext { margin:0;font-size:13px;line-height:1.8;color:rgba(180,195,215,.95);font-weight:300 }
/* Divider */
.email-divider { height:1px;background:linear-gradient(90deg,transparent,rgba(13,148,136,.3),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:rgba(13,148,136,.18);border:1px solid rgba(13,148,136,.3);letter-spacing:1px }
.email-btn-table { margin:0 0 32px }
/* Fallback link */
.email-link-box { border:1px solid rgba(13,148,136,.18);background:rgba(13,148,136,.03) }
.email-link-label { margin:0 0 8px;font-size:11px;color:rgba(180,252,238,.75);font-weight:300 }
.email-link-url { margin:0;font-size:11px;font-family:monospace;color:#99f6e4;background:rgba(0,0,0,.2);padding:10px 12px;border:1px solid rgba(13,148,136,.15);word-break:break-all }
/* Footer */
.email-footer { padding:32px 0 0;text-align:center }
.email-footer-text { margin:0;font-size:11px;color:rgba(160,175,200,.55);font-weight:300 }
.email-footer-link { color:rgba(94,234,212,.5);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:rgba(210,252,245,.88)!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">
&copy;
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
</p>
</td>
</tr>
</table>
</center>
</body>
</html>
-136
View File
@@ -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:rgba(255,255,255,.4);letter-spacing:4px;text-transform:uppercase }
.email-header { padding:0 0 32px;text-align:center }
/* Card */
.email-card { padding:48px 40px;background:rgba(25,20,50,.8);border:1px solid rgba(13,148,136,.2);box-shadow:0 0 60px rgba(13,148,136,.08),0 0 120px rgba(99,102,241,.04) }
.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:rgba(210,252,245,.88);font-weight:300 }
.email-subtext { margin:0;font-size:13px;line-height:1.8;color:rgba(180,195,215,.95);font-weight:300 }
/* Divider */
.email-divider { height:1px;background:linear-gradient(90deg,transparent,rgba(13,148,136,.3),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:rgba(13,148,136,.18);border:1px solid rgba(13,148,136,.3);letter-spacing:1px }
.email-btn-table { margin:0 0 32px }
/* Fallback link */
.email-link-box { border:1px solid rgba(13,148,136,.18);background:rgba(13,148,136,.03) }
.email-link-label { margin:0 0 8px;font-size:11px;color:rgba(180,252,238,.75);font-weight:300 }
.email-link-url { margin:0;font-size:11px;font-family:monospace;color:#99f6e4;background:rgba(0,0,0,.2);padding:10px 12px;border:1px solid rgba(13,148,136,.15);word-break:break-all }
/* Footer */
.email-footer { padding:32px 0 0;text-align:center }
.email-footer-text { margin:0;font-size:11px;color:rgba(160,175,200,.55);font-weight:300 }
.email-footer-link { color:rgba(94,234,212,.5);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:rgba(210,252,245,.88)!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">
&copy;
<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:rgba(255,255,255,.4);letter-spacing:4px;text-transform:uppercase }
.email-header { padding:0 0 32px;text-align:center }
/* Card */
.email-card { padding:48px 40px;background:rgba(25,20,50,.8);border:1px solid rgba(13,148,136,.2);box-shadow:0 0 60px rgba(13,148,136,.08),0 0 120px rgba(99,102,241,.04) }
.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:rgba(210,252,245,.88);font-weight:300 }
.email-subtext { margin:0;font-size:13px;line-height:1.8;color:rgba(180,195,215,.95);font-weight:300 }
/* Divider */
.email-divider { height:1px;background:linear-gradient(90deg,transparent,rgba(13,148,136,.3),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:rgba(13,148,136,.18);border:1px solid rgba(13,148,136,.3);letter-spacing:1px }
.email-btn-table { margin:0 0 32px }
/* Fallback link */
.email-link-box { border:1px solid rgba(13,148,136,.18);background:rgba(13,148,136,.03) }
.email-link-label { margin:0 0 8px;font-size:11px;color:rgba(180,252,238,.75);font-weight:300 }
.email-link-url { margin:0;font-size:11px;font-family:monospace;color:#99f6e4;background:rgba(0,0,0,.2);padding:10px 12px;border:1px solid rgba(13,148,136,.15);word-break:break-all }
/* Footer */
.email-footer { padding:32px 0 0;text-align:center }
.email-footer-text { margin:0;font-size:11px;color:rgba(160,175,200,.55);font-weight:300 }
.email-footer-link { color:rgba(94,234,212,.5);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:rgba(210,252,245,.88)!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">
&copy;
<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:rgba(255,255,255,.4);letter-spacing:4px;text-transform:uppercase }
.email-header { padding:0 0 32px;text-align:center }
/* Card */
.email-card { padding:48px 40px;background:rgba(25,20,50,.8);border:1px solid rgba(13,148,136,.2);box-shadow:0 0 60px rgba(13,148,136,.08),0 0 120px rgba(99,102,241,.04) }
.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:rgba(210,252,245,.88);font-weight:300 }
.email-subtext { margin:0;font-size:13px;line-height:1.8;color:rgba(180,195,215,.95);font-weight:300 }
/* Divider */
.email-divider { height:1px;background:linear-gradient(90deg,transparent,rgba(13,148,136,.3),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:rgba(13,148,136,.18);border:1px solid rgba(13,148,136,.3);letter-spacing:1px }
.email-btn-table { margin:0 0 32px }
/* Fallback link */
.email-link-box { border:1px solid rgba(13,148,136,.18);background:rgba(13,148,136,.03) }
.email-link-label { margin:0 0 8px;font-size:11px;color:rgba(180,252,238,.75);font-weight:300 }
.email-link-url { margin:0;font-size:11px;font-family:monospace;color:#99f6e4;background:rgba(0,0,0,.2);padding:10px 12px;border:1px solid rgba(13,148,136,.15);word-break:break-all }
/* Footer */
.email-footer { padding:32px 0 0;text-align:center }
.email-footer-text { margin:0;font-size:11px;color:rgba(160,175,200,.55);font-weight:300 }
.email-footer-link { color:rgba(94,234,212,.5);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:rgba(210,252,245,.88)!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">
&copy;
<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:rgba(255,255,255,.4);letter-spacing:4px;text-transform:uppercase }
.email-header { padding:0 0 32px;text-align:center }
/* Card */
.email-card { padding:48px 40px;background:rgba(25,20,50,.8);border:1px solid rgba(13,148,136,.2);box-shadow:0 0 60px rgba(13,148,136,.08),0 0 120px rgba(99,102,241,.04) }
.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:rgba(210,252,245,.88);font-weight:300 }
.email-subtext { margin:0;font-size:13px;line-height:1.8;color:rgba(180,195,215,.95);font-weight:300 }
/* Divider */
.email-divider { height:1px;background:linear-gradient(90deg,transparent,rgba(13,148,136,.3),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:rgba(13,148,136,.18);border:1px solid rgba(13,148,136,.3);letter-spacing:1px }
.email-btn-table { margin:0 0 32px }
/* Fallback link */
.email-link-box { border:1px solid rgba(13,148,136,.18);background:rgba(13,148,136,.03) }
.email-link-label { margin:0 0 8px;font-size:11px;color:rgba(180,252,238,.75);font-weight:300 }
.email-link-url { margin:0;font-size:11px;font-family:monospace;color:#99f6e4;background:rgba(0,0,0,.2);padding:10px 12px;border:1px solid rgba(13,148,136,.15);word-break:break-all }
/* Footer */
.email-footer { padding:32px 0 0;text-align:center }
.email-footer-text { margin:0;font-size:11px;color:rgba(160,175,200,.55);font-weight:300 }
.email-footer-link { color:rgba(94,234,212,.5);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:rgba(210,252,245,.88)!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">
&copy;
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
</p>
</td>
</tr>
</table>
</center>
</body>
</html>
+86
View File
@@ -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; }
+6
View File
@@ -0,0 +1,6 @@
{
"name": "aurora",
"description": "Ethereal / Dreamlike",
"mode": "framed",
"layout": "aurora"
}
-99
View File
@@ -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 rgba(59,130,246,0.1);text-align:center }
/* Card */
.email-card { max-width:600px;margin:24px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 4px rgba(0,0,0,0.06),0 8px 24px rgba(0,0,0,0.06),0 0 0 1px rgba(0,0,0,0.04);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 rgba(59,130,246,0.1);border-radius:12px;padding:16px 18px;margin:24px 0 16px;box-shadow:inset 0 1px 0 rgba(255,255,255,0.9) }
.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 rgba(0,0,0,0.06);word-break:break-all;font-family:monospace }
/* Footer */
.email-footer { padding:18px 32px 22px;background:linear-gradient(135deg,#f8f9fc,#f4f6fb);border-top:1px solid rgba(0,0,0,0.06);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">
&copy;
<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 rgba(59,130,246,0.1);text-align:center }
/* Card */
.email-card { max-width:600px;margin:24px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 4px rgba(0,0,0,0.06),0 8px 24px rgba(0,0,0,0.06),0 0 0 1px rgba(0,0,0,0.04);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 rgba(0,0,0,0.06);border-radius:10px;padding:12px 16px;margin-bottom:8px;box-shadow:inset 0 1px 0 rgba(255,255,255,0.7) }
.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 rgba(59,130,246,0.1);border-radius:12px;padding:16px 18px;margin:24px 0 16px;box-shadow:inset 0 1px 0 rgba(255,255,255,0.9) }
.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 rgba(0,0,0,0.06);word-break:break-all;font-family:monospace }
/* Footer */
.email-footer { padding:18px 32px 22px;background:linear-gradient(135deg,#f8f9fc,#f4f6fb);border-top:1px solid rgba(0,0,0,0.06);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}} &mdash; {{.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}} &mdash; {{$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">
&copy;
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
</p>
</div>
</div>
</center>
</body>
</html>
-88
View File
@@ -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 rgba(59,130,246,0.1);text-align:center }
/* Card */
.email-card { max-width:600px;margin:24px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 4px rgba(0,0,0,0.06),0 8px 24px rgba(0,0,0,0.06),0 0 0 1px rgba(0,0,0,0.04);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 rgba(59,130,246,0.12) }
/* 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 rgba(59,130,246,0.1);border-radius:12px;padding:16px 18px;margin:24px 0 16px;box-shadow:inset 0 1px 0 rgba(255,255,255,0.9) }
.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 rgba(0,0,0,0.06);word-break:break-all;font-family:monospace }
/* Footer */
.email-footer { padding:18px 32px 22px;background:linear-gradient(135deg,#f8f9fc,#f4f6fb);border-top:1px solid rgba(0,0,0,0.06);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">
&copy;
<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 rgba(59,130,246,0.1);text-align:center }
/* Card */
.email-card { max-width:600px;margin:24px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 4px rgba(0,0,0,0.06),0 8px 24px rgba(0,0,0,0.06),0 0 0 1px rgba(0,0,0,0.04);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 rgba(59,130,246,0.1);border-radius:12px;padding:16px 18px;margin:24px 0 16px;box-shadow:inset 0 1px 0 rgba(255,255,255,0.9) }
.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 rgba(0,0,0,0.06);word-break:break-all;font-family:monospace }
/* Footer */
.email-footer { padding:18px 32px 22px;background:linear-gradient(135deg,#f8f9fc,#f4f6fb);border-top:1px solid rgba(0,0,0,0.06);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">
&copy;
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
</p>
</div>
</div>
</center>
</body>
</html>
-162
View File
@@ -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 rgba(59,130,246,0.1);text-align:center }
/* Card */
.email-card { max-width:600px;margin:24px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 4px rgba(0,0,0,0.06),0 8px 24px rgba(0,0,0,0.06),0 0 0 1px rgba(0,0,0,0.04);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 rgba(0,0,0,0.06) }
hr { border:none;border-top:1px solid rgba(0,0,0,0.06);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 rgba(59,130,246,0.1);border-radius:12px;padding:16px 18px;margin:24px 0 16px;box-shadow:inset 0 1px 0 rgba(255,255,255,0.9) }
.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 rgba(0,0,0,0.06);word-break:break-all;font-family:monospace }
/* Footer */
.email-footer { padding:18px 32px 22px;background:linear-gradient(135deg,#f8f9fc,#f4f6fb);border-top:1px solid rgba(0,0,0,0.06);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}}&nbsp;{{.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/{{.ID}}" class="commit-link">
{{ShortSha .ID.String}}
</a> &mdash; {{.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">
&copy;
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
</p>
</div>
</div>
</center>
</body>
</html>
-122
View File
@@ -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 rgba(59,130,246,0.1);text-align:center }
/* Card */
.email-card { max-width:600px;margin:24px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 4px rgba(0,0,0,0.06),0 8px 24px rgba(0,0,0,0.06),0 0 0 1px rgba(0,0,0,0.04);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 rgba(59,130,246,0.1);border-radius:12px;padding:16px 18px;margin:24px 0 16px;box-shadow:inset 0 1px 0 rgba(255,255,255,0.9) }
.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 rgba(0,0,0,0.06);word-break:break-all;font-family:monospace }
/* Footer */
.email-footer { padding:18px 32px 22px;background:linear-gradient(135deg,#f8f9fc,#f4f6fb);border-top:1px solid rgba(0,0,0,0.06);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">
&copy;
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
</p>
</div>
</div>
</center>
</body>
</html>
-85
View File
@@ -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 rgba(59,130,246,0.1);text-align:center }
/* Card */
.email-card { max-width:600px;margin:24px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 4px rgba(0,0,0,0.06),0 8px 24px rgba(0,0,0,0.06),0 0 0 1px rgba(0,0,0,0.04);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 rgba(59,130,246,0.1);border-radius:12px;padding:16px 18px;margin:24px 0 16px;box-shadow:inset 0 1px 0 rgba(255,255,255,0.9) }
.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 rgba(0,0,0,0.06);word-break:break-all;font-family:monospace }
/* Footer */
.email-footer { padding:18px 32px 22px;background:linear-gradient(135deg,#f8f9fc,#f4f6fb);border-top:1px solid rgba(0,0,0,0.06);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">
&copy;
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
</p>
</div>
</div>
</center>
</body>
</html>
-92
View File
@@ -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 rgba(59,130,246,0.1);text-align:center }
/* Card */
.email-card { max-width:600px;margin:24px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 4px rgba(0,0,0,0.06),0 8px 24px rgba(0,0,0,0.06),0 0 0 1px rgba(0,0,0,0.04);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 rgba(59,130,246,0.1);border-radius:12px;padding:16px 18px;margin:24px 0 16px;box-shadow:inset 0 1px 0 rgba(255,255,255,0.9) }
.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 rgba(0,0,0,0.06);word-break:break-all;font-family:monospace }
/* Footer */
.email-footer { padding:18px 32px 22px;background:linear-gradient(135deg,#f8f9fc,#f4f6fb);border-top:1px solid rgba(0,0,0,0.06);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">
&copy;
<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 rgba(59,130,246,0.1);text-align:center }
/* Card */
.email-card { max-width:600px;margin:24px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 4px rgba(0,0,0,0.06),0 8px 24px rgba(0,0,0,0.06),0 0 0 1px rgba(0,0,0,0.04);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 rgba(59,130,246,0.1);border-radius:12px;padding:16px 18px;margin:24px 0 16px;box-shadow:inset 0 1px 0 rgba(255,255,255,0.9) }
.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 rgba(0,0,0,0.06);word-break:break-all;font-family:monospace }
/* Footer */
.email-footer { padding:18px 32px 22px;background:linear-gradient(135deg,#f8f9fc,#f4f6fb);border-top:1px solid rgba(0,0,0,0.06);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">
&copy;
<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 rgba(59,130,246,0.1);text-align:center }
/* Card */
.email-card { max-width:600px;margin:24px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 4px rgba(0,0,0,0.06),0 8px 24px rgba(0,0,0,0.06),0 0 0 1px rgba(0,0,0,0.04);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 rgba(59,130,246,0.1);border-radius:12px;padding:16px 18px;margin:24px 0 16px;box-shadow:inset 0 1px 0 rgba(255,255,255,0.9) }
.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 rgba(0,0,0,0.06);word-break:break-all;font-family:monospace }
/* Footer */
.email-footer { padding:18px 32px 22px;background:linear-gradient(135deg,#f8f9fc,#f4f6fb);border-top:1px solid rgba(0,0,0,0.06);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">
&copy;
<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 rgba(59,130,246,0.1);text-align:center }
/* Card */
.email-card { max-width:600px;margin:24px auto;background:#ffffff;border-radius:16px;box-shadow:0 2px 4px rgba(0,0,0,0.06),0 8px 24px rgba(0,0,0,0.06),0 0 0 1px rgba(0,0,0,0.04);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 rgba(59,130,246,0.1);border-radius:12px;padding:16px 18px;margin:24px 0 16px;box-shadow:inset 0 1px 0 rgba(255,255,255,0.9) }
.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 rgba(0,0,0,0.06);word-break:break-all;font-family:monospace }
/* Footer */
.email-footer { padding:18px 32px 22px;background:linear-gradient(135deg,#f8f9fc,#f4f6fb);border-top:1px solid rgba(0,0,0,0.06);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">
&copy;
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
</p>
</div>
</div>
</center>
</body>
</html>
+67
View File
@@ -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; }
+6
View File
@@ -0,0 +1,6 @@
{
"name": "bloom",
"description": "Creative / Startup",
"mode": "framed",
"layout": "bloom"
}
-106
View File
@@ -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 rgba(234,88,12,0.08);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;">&#x1f44b;</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">
&copy;
<a href="{{AppUrl}}" class="email-footer-link">{{AppName}}</a>
</p>
</div>
</div>
</center>
</body>
</html>
Loaded 100 of 235 files, more files were not shown because too many files have changed in this diff. Show more