- 新增 internal/i18n 包:以原始中文字符串为 key 的翻译目录(zh 恒等, en 兜底,ja 缺译回退 en),t/tf 模板函数与 Accept-Language 解析 (按 q 值排序,兜底英语) - User 模型新增 language 字段(auto|en|zh|ja,默认 auto), AutoMigrate 自动加列;UserStore 新增 UpdateLanguage - 语言解析:登录用户按偏好(auto 时取浏览器 Accept-Language), 登录/封禁页按浏览器语言;所有 c.HTML 渲染注入 Lang 模板数据 - 全部页面模板(用户页 + 管理后台 12 页)接入翻译:导航、文件夹、 邮件列表/阅读/写信、设置、登录、封禁页、表单、表格、JS 确认框等; html lang 属性随语言切换;日期函数(shortDate/time12/time12m) 与 folderLabel 改为语言感知(上午/下午 vs AM/PM vs 午前/午後) - 设置页新增「界面语言」选择(自动/English/中文/日本語), /settings 表单支持单独提交语言偏好;管理后台用户表单可预设语言 - 封禁原因等入库中文文案在展示时经 t 翻译(未知 key 原样回退) - 测试:i18n 单测(解析/回退/译文)、render 测试补 Lang 数据、 时间格式化测试适配新签名;全量 go test 通过
131 lines
6.1 KiB
HTML
131 lines
6.1 KiB
HTML
{{define "compose"}}
|
||
<!DOCTYPE html>
|
||
<html lang="{{htmlLang .Lang}}">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||
<title>{{t .Lang "写信"}} - MailGo</title>
|
||
<link href="/static/vendor/quill/quill.snow.css" rel="stylesheet">
|
||
{{template "styles" .}}
|
||
</head>
|
||
<body class="page-compose">
|
||
{{template "navbar" .}}
|
||
<div class="app-body">
|
||
{{template "sidebar" .}}
|
||
<main class="mail-main">
|
||
<div class="compose-toolbar">
|
||
<button type="submit" form="compose-form" class="btn btn-primary">
|
||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px;"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
|
||
{{t .Lang "发送"}}
|
||
</button>
|
||
<label class="tb-btn" style="cursor:pointer;">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
|
||
{{t .Lang "附件"}}
|
||
<input type="file" name="attachments" id="attach-input" multiple style="display:none;">
|
||
</label>
|
||
<a href="/inbox" class="tb-btn">{{t .Lang "取消"}}</a>
|
||
<div class="toolbar-spacer"></div>
|
||
<span class="page-info">{{t .Lang "撰写新邮件"}}</span>
|
||
</div>
|
||
|
||
{{if .error}}<div class="alert alert-error" style="margin:12px 18px 0;">{{.error}}</div>{{end}}
|
||
|
||
<form id="compose-form" class="compose-form" method="POST" action="/compose" enctype="multipart/form-data">
|
||
<div class="compose-field">
|
||
<label>{{t .Lang "收件人"}}</label>
|
||
<input type="email" name="to" required value="{{.to}}" placeholder="{{t .Lang "输入收件人邮箱地址"}}">
|
||
</div>
|
||
<div class="compose-field">
|
||
<label>{{t .Lang "抄送"}}</label>
|
||
<input type="text" name="cc" value="{{.cc}}" placeholder="{{t .Lang "多个地址用逗号分隔(可选)"}}">
|
||
</div>
|
||
<div class="compose-field">
|
||
<label>{{t .Lang "主题"}}</label>
|
||
<input type="text" name="subject" value="{{.subject}}" placeholder="{{t .Lang "输入邮件主题"}}">
|
||
</div>
|
||
|
||
<div id="attach-chips" class="attach-chips" style="{{if not .attachments}}display:none;{{end}}"></div>
|
||
|
||
<div class="editor-wrap">
|
||
<div id="editor" data-placeholder="{{t .Lang "请输入邮件内容..."}}"></div>
|
||
<input type="hidden" name="body" id="body-hidden">
|
||
<input type="hidden" name="html_body" id="html-body-hidden">
|
||
</div>
|
||
|
||
<div class="compose-footer">
|
||
<span>{{t .Lang "附件配额"}}</span>
|
||
<span class="quota-bar" id="quota-bar" data-used="{{.usedBytes}}" data-quota="{{.quotaBytes}}"><i></i></span>
|
||
<span id="quota-text">{{formatBytes .usedBytes}} / {{formatBytes .quotaBytes}}</span>
|
||
</div>
|
||
</form>
|
||
</main>
|
||
</div>
|
||
<script src="/static/vendor/quill/quill.min.js"></script>
|
||
<script>
|
||
var quill = new Quill('#editor', {
|
||
theme: 'snow',
|
||
placeholder: document.getElementById('editor').dataset.placeholder || '{{t .Lang "请输入邮件内容..."}}',
|
||
modules: {
|
||
toolbar: [
|
||
[{ 'header': [1, 2, 3, false] }],
|
||
['bold', 'italic', 'underline', 'strike'],
|
||
[{ 'color': [] }, { 'background': [] }],
|
||
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
|
||
['link', 'image'],
|
||
['clean']
|
||
]
|
||
}
|
||
});
|
||
{{if .bodyContent}}
|
||
quill.root.innerHTML = {{.bodyContent | jsonify}};
|
||
{{end}}
|
||
|
||
document.getElementById('compose-form').addEventListener('submit', function () {
|
||
document.getElementById('body-hidden').value = quill.getText();
|
||
document.getElementById('html-body-hidden').value = quill.root.innerHTML;
|
||
});
|
||
|
||
// 附件选择预览
|
||
var attachInput = document.getElementById('attach-input');
|
||
var chipsBox = document.getElementById('attach-chips');
|
||
var files = [];
|
||
function renderChips() {
|
||
chipsBox.innerHTML = '';
|
||
chipsBox.style.display = files.length ? 'flex' : 'none';
|
||
files.forEach(function (f, i) {
|
||
var chip = document.createElement('span');
|
||
chip.className = 'attach-chip';
|
||
chip.innerHTML = '📎 ' + f.name + ' (' + (f.size / 1024).toFixed(1) + ' KB)' +
|
||
'<button type="button" class="chip-del" data-i="' + i + '" title="{{t .Lang "移除"}}">✕</button>';
|
||
chipsBox.appendChild(chip);
|
||
});
|
||
}
|
||
attachInput.addEventListener('change', function () {
|
||
files = Array.prototype.slice.call(attachInput.files);
|
||
renderChips();
|
||
});
|
||
chipsBox.addEventListener('click', function (e) {
|
||
var del = e.target.closest('.chip-del');
|
||
if (!del) return;
|
||
files.splice(parseInt(del.dataset.i, 10), 1);
|
||
var dt = new DataTransfer();
|
||
files.forEach(function (f) { dt.items.add(f); });
|
||
attachInput.files = dt.files;
|
||
renderChips();
|
||
});
|
||
|
||
// 配额进度条
|
||
var bar = document.getElementById('quota-bar');
|
||
if (bar) {
|
||
var used = parseInt(bar.dataset.used, 10) || 0;
|
||
var quota = parseInt(bar.dataset.quota, 10) || 1;
|
||
var pct = Math.min(100, Math.round(used / quota * 100));
|
||
bar.querySelector('i').style.width = pct + '%';
|
||
if (pct >= 90) bar.classList.add('warn');
|
||
if (pct >= 100) bar.classList.add('over');
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|
||
{{end}}
|