preview: remove screenshot export and restore UI animations
Validate / Validate (push) Successful in 2m52s

This commit is contained in:
KenanZhu committed 2026-10-11 16:59:00 +08:00
1 parent 6733e81356
commit bcf1b87e82
8 files changed
+60 -227

No files matched your search

-3
View File
@@ -21,9 +21,6 @@ REQUIRED_FILES = (
"docs/README.zh-CN.md", "docs/README.zh-CN.md",
"docs/CONTRIBUTING.zh-CN.md", "docs/CONTRIBUTING.zh-CN.md",
"preview/index.html", "preview/index.html",
"preview/lib/html2canvas/html2canvas.min.js",
"preview/lib/html2canvas/LICENSE",
"preview/lib/html2canvas/README.md",
) )
OPTIONAL_ROOT_DOCUMENTS = ("AGENTS.md", "THIRD_PARTY_NOTICES.md") OPTIONAL_ROOT_DOCUMENTS = ("AGENTS.md", "THIRD_PARTY_NOTICES.md")
DOCUMENTATION_DIRECTORIES = ("docs", ".github/release-notes") DOCUMENTATION_DIRECTORIES = ("docs", ".github/release-notes")
-10
View File
@@ -30,9 +30,6 @@ class PackagingTests(unittest.TestCase):
"docs/README.zh-CN.md", "docs/README.zh-CN.md",
"docs/CONTRIBUTING.zh-CN.md", "docs/CONTRIBUTING.zh-CN.md",
"preview/index.html", "preview/index.html",
"preview/lib/html2canvas/html2canvas.min.js",
"preview/lib/html2canvas/LICENSE",
"preview/lib/html2canvas/README.md",
"docs/images/README.md", "docs/images/README.md",
) )
@@ -133,15 +130,8 @@ class PackagingTests(unittest.TestCase):
self.assertEqual(expected_members, zipped) self.assertEqual(expected_members, zipped)
self.assertEqual(expected_members, tarred) self.assertEqual(expected_members, tarred)
self.assertIn(prefix + "themes/demo/mail/base/head.tmpl", zipped) self.assertIn(prefix + "themes/demo/mail/base/head.tmpl", zipped)
for name in ("html2canvas.min.js", "LICENSE", "README.md"):
self.assertIn(prefix + "preview/lib/html2canvas/" + name, zipped)
self.assertFalse(any("stale" in name or "/build/" in name for name in zipped)) self.assertFalse(any("stale" in name or "/build/" in name for name in zipped))
def test_missing_screenshot_library_is_rejected(self):
(self.root / "preview/lib/html2canvas/html2canvas.min.js").unlink()
with self.assertRaisesRegex(ValueError, "Expected regular file"):
PACKER.collect(self.root, self.VERSION)
def test_existing_archives_are_not_overwritten(self): def test_existing_archives_are_not_overwritten(self):
PACKER.package(self.root, self.VERSION, self.output) PACKER.package(self.root, self.VERSION, self.output)
original_archives = { original_archives = {
-1
View File
@@ -203,7 +203,6 @@ Contributions to themes, tools, documentation and translations are welcome. The
This project is licensed under [MIT](LICENSE). Generated release archives retain Gitea's license and snapshot provenance. This project is licensed under [MIT](LICENSE). Generated release archives retain Gitea's license and snapshot provenance.
--- ---
### Notes ### Notes
[1](#notes): [Mail templates | Gitea Documentation](https://docs.gitea.com/administration/mail-templates/) [1](#notes): [Mail templates | Gitea Documentation](https://docs.gitea.com/administration/mail-templates/)
+1 -1
View File
@@ -199,7 +199,7 @@ preview/rendered/<LOCALE>.js # 各语言的预览数据
## 许可证 ## 许可证
本项目采用 [MIT 许可证](../LICENSE)。生成的发行包保留 Gitea 许可证和快照来源信息,详见[第三方声明](../THIRD_PARTY_NOTICES.md)。 本项目采用 [MIT 许可证](../LICENSE)。生成的发行包保留 Gitea 许可证和快照来源信息。
--- ---
### 注释 ### 注释
+59 -159
View File
@@ -29,10 +29,6 @@ body{font-family:system-ui,-apple-system,sans-serif;background:var(--bg);color:v
#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;white-space:nowrap} #topbar .hint{font-size:10px;color:var(--muted);opacity:.5;white-space:nowrap}
#btn-screenshot{display:inline-flex;align-items:center;gap:6px;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:var(--rad);padding:6px 12px;font-size:12px;cursor:pointer}
#btn-screenshot svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.8;flex-shrink:0}
#btn-screenshot:hover:not(:disabled){border-color:var(--accent)}
#btn-screenshot:disabled{opacity:.45;cursor:wait}
button:focus-visible{outline:2px solid var(--accent);outline-offset:3px} button:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
/* Topbar responsive — progressively compact as width shrinks */ /* Topbar responsive — progressively compact as width shrinks */
@@ -129,12 +125,15 @@ section.collapsed .section-chevron{transform:rotate(-90deg)}
/* Collapsible content wrapper */ /* Collapsible content wrapper */
.section-content{ .section-content{
margin-top:10px; 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{ section.collapsed .section-content{
display:none; grid-template-rows:0fr;opacity:0;margin-top:0;
} }
#sec-params .section-content{max-height:50vh;overflow:auto;overscroll-behavior:contain} #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:last-child{margin-bottom:0}
@@ -151,12 +150,12 @@ section.collapsed .section-content{
#panel .stat{font-size:10px;color:var(--muted);margin-top:6px} #panel .stat{font-size:10px;color:var(--muted);margin-top:6px}
/* Viewport */ /* Viewport */
#viewport{border-radius:var(--rad);box-shadow:0 8px 32px rgba(0,0,0,.45);flex-shrink:0;max-width:100%;overflow:hidden;background:#fff} #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}
#viewport.desktop{width:100%;max-width:1386px;height:780px} #viewport.desktop{width:100%;max-width:1386px;height:780px}
#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} #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}
#viewport.mobile #preview-frame{flex:1;min-height:0} #viewport::before{content:'';display:block;height:0;background:#1e293b;flex-shrink:0;transition:height .3s ease}
#viewport.mobile::before{content:'';display:block;height:24px;background:#1e293b;flex-shrink:0} #viewport.mobile::before{height:24px}
#preview-frame{width:100%;height:100%;border:none;display:block;background:#fff} #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)} #viewport.desktop #preview-frame{border-radius:var(--rad)}
/* Loading */ /* Loading */
@@ -171,7 +170,7 @@ section.collapsed .section-content{
.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 */
html,body,#preview-area,#panel-body,#sec-params .section-content{ html,body,#preview-area,#panel-body,#sec-params .section-content-inner{
scrollbar-width:thin; scrollbar-width:thin;
scrollbar-color:rgba(107,125,153,.25) transparent; scrollbar-color:rgba(107,125,153,.25) transparent;
} }
@@ -179,30 +178,30 @@ html,body,#preview-area,#panel-body,#sec-params .section-content{
-ms-overflow-style:-ms-autohiding-scrollbar; -ms-overflow-style:-ms-autohiding-scrollbar;
} }
#preview-area::-webkit-scrollbar, #preview-area::-webkit-scrollbar,
#sec-params .section-content::-webkit-scrollbar, #sec-params .section-content-inner::-webkit-scrollbar,
#panel-body::-webkit-scrollbar { width:6px; height:6px } #panel-body::-webkit-scrollbar { width:6px; height:6px }
#preview-area::-webkit-scrollbar-track, #preview-area::-webkit-scrollbar-track,
#sec-params .section-content::-webkit-scrollbar-track, #sec-params .section-content-inner::-webkit-scrollbar-track,
#panel-body::-webkit-scrollbar-track { background:transparent } #panel-body::-webkit-scrollbar-track { background:transparent }
#preview-area::-webkit-scrollbar-thumb, #preview-area::-webkit-scrollbar-thumb,
#sec-params .section-content::-webkit-scrollbar-thumb, #sec-params .section-content-inner::-webkit-scrollbar-thumb,
#panel-body::-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,
#sec-params .section-content::-webkit-scrollbar-thumb:hover, #sec-params .section-content-inner::-webkit-scrollbar-thumb:hover,
#panel-body::-webkit-scrollbar-thumb:hover { background-color:rgba(107,125,153,.45) } #panel-body::-webkit-scrollbar-thumb:hover { background-color:rgba(107,125,153,.45) }
#preview-area::-webkit-scrollbar-corner, #preview-area::-webkit-scrollbar-corner,
#sec-params .section-content::-webkit-scrollbar-corner, #sec-params .section-content-inner::-webkit-scrollbar-corner,
#panel-body::-webkit-scrollbar-corner { background:transparent } #panel-body::-webkit-scrollbar-corner { background:transparent }
@media(max-width:700px){ @media(max-width:700px){
#panel{--panel-w:50vw;margin-top:6px;margin-bottom:6px;margin-right:6px} #panel{--panel-w:50vw;margin-top:6px;margin-bottom:6px;margin-right:6px}
#preview-area{padding:12px} #preview-area{padding:12px}
} }
@media(prefers-reduced-motion:reduce){#panel,#panel-handle svg{transition:none}} @media(prefers-reduced-motion:reduce){#panel,#panel-handle svg,.section-content,.section-chevron,#viewport,#viewport::before{transition:none}}
</style> </style>
</head> </head>
<body> <body>
@@ -237,10 +236,6 @@ html,body,#preview-area,#panel-body,#sec-params .section-content{
<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>
<button id="btn-screenshot" type="button" onclick="capturePreview()" disabled>
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5l1.5-2h5L16 5h4a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2z"/><circle cx="12" cy="13" r="4"/></svg>
<span id="screenshot-label">Screenshot</span>
</button>
<span class="hint">keys: d/m &nbsp; &larr;&rarr; tab &nbsp; &uarr;&darr; select &nbsp; p toggle panel</span> <span class="hint">keys: d/m &nbsp; &larr;&rarr; tab &nbsp; &uarr;&darr; select &nbsp; p toggle panel</span>
</nav> </nav>
@@ -270,6 +265,7 @@ html,body,#preview-area,#panel-body,#sec-params .section-content{
<svg class="section-chevron" viewBox="0 0 24 24"><polyline points="6 9 12 15 18 9"/></svg> <svg class="section-chevron" viewBox="0 0 24 24"><polyline points="6 9 12 15 18 9"/></svg>
</div> </div>
<div class="section-content"> <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>
@@ -280,6 +276,7 @@ html,body,#preview-area,#panel-body,#sec-params .section-content{
</div> </div>
<div class="stat" id="panel-stats"></div> <div class="stat" id="panel-stats"></div>
</div> </div>
</div>
</section> </section>
<section id="sec-params"> <section id="sec-params">
@@ -288,8 +285,10 @@ html,body,#preview-area,#panel-body,#sec-params .section-content{
<svg class="section-chevron" viewBox="0 0 24 24"><polyline points="6 9 12 15 18 9"/></svg> <svg class="section-chevron" viewBox="0 0 24 24"><polyline points="6 9 12 15 18 9"/></svg>
</div> </div>
<div class="section-content"> <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>
</div>
</section> </section>
<section id="sec-source"> <section id="sec-source">
@@ -298,14 +297,15 @@ html,body,#preview-area,#panel-body,#sec-params .section-content{
<svg class="section-chevron" viewBox="0 0 24 24"><polyline points="6 9 12 15 18 9"/></svg> <svg class="section-chevron" viewBox="0 0 24 24"><polyline points="6 9 12 15 18 9"/></svg>
</div> </div>
<div class="section-content"> <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>
</div>
</section> </section>
</div> </div>
</aside> </aside>
</div> </div>
<script src="lib/html2canvas/html2canvas.min.js"></script>
<script src="rendered.js"></script> <script src="rendered.js"></script>
<script> <script>
// //
@@ -320,122 +320,6 @@ let localeRequest = 0;
let renderRequest = 0, renderTimer; let renderRequest = 0, renderTimer;
const localeLoads = new Map(); const localeLoads = new Map();
let devMode = false; // true once SSE connects let devMode = false; // true once SSE connects
let previewReady = false;
let capturing = false;
function updateCaptureButton() {
document.getElementById('btn-screenshot').disabled = !previewReady || capturing;
}
// Capture only the visible viewport, including the mobile frame when selected.
async function capturePreview() {
if (!previewReady || capturing) return;
const label = document.getElementById('screenshot-label');
const selection = { ...state };
const request = renderRequest;
const frame = document.getElementById('preview-frame');
const viewport = document.getElementById('viewport');
capturing = true;
label.textContent = 'Capturing…';
updateCaptureButton();
document.getElementById('capture-message')?.remove();
try {
if (typeof html2canvas !== 'function') throw new Error('Screenshot library unavailable; restore preview/lib/html2canvas.');
const doc = frame.contentDocument;
if (!doc) throw new Error('Preview document is not accessible.');
if (doc.fonts) await doc.fonts.ready;
await Promise.all(Array.from(doc.images, image => image.decode().catch(() => {})));
if (request !== renderRequest || !previewReady) throw new Error('Preview changed; capture again after it finishes loading.');
const width = viewport.offsetWidth;
const height = viewport.offsetHeight;
const frameWidth = frame.clientWidth;
const frameHeight = frame.clientHeight;
const scrollX = frame.contentWindow.scrollX;
const scrollY = frame.contentWindow.scrollY;
const viewportBounds = viewport.getBoundingClientRect();
const frameBounds = frame.getBoundingClientRect();
const frameLeft = frameBounds.left - viewportBounds.left;
const frameTop = frameBounds.top - viewportBounds.top;
const scale = Math.min(window.devicePixelRatio || 1, 2);
// Render in the mail document's own coordinates. Rendering nested iframes
// with the outer document's context can shift centered tables and text.
const mailCanvas = await html2canvas(doc.documentElement, {
backgroundColor: '#fff',
scale,
width: frameWidth,
height: frameHeight,
windowWidth: frameWidth,
windowHeight: frameHeight,
x: scrollX,
y: scrollY,
scrollX,
scrollY,
useCORS: true,
logging: false
});
const canvas = await html2canvas(viewport, {
backgroundColor: null,
scale,
width,
height,
useCORS: true,
logging: false,
ignoreElements: element => element.id === 'preview-frame',
onclone: clone => {
const clonedViewport = clone.getElementById('viewport');
clonedViewport.style.width = width + 'px';
clonedViewport.style.maxWidth = 'none';
clonedViewport.style.height = height + 'px';
}
});
if (request !== renderRequest || !previewReady) throw new Error('Preview changed; capture again after it finishes loading.');
const context = canvas.getContext('2d');
if (!context) throw new Error('Could not create the screenshot canvas.');
const style = getComputedStyle(viewport);
const border = parseFloat(style.borderLeftWidth) || 0;
const radius = Math.max(0, (parseFloat(style.borderTopLeftRadius) || 0) - border);
// Clip the composite to the container's inner rounded border, including
// the mobile frame. No top bar or sidebar enters the exported image.
const left = border, top = border;
const right = width - border, bottom = height - border;
const rounding = Math.min(radius, (right - left) / 2, (bottom - top) / 2);
context.save();
context.scale(scale, scale);
context.beginPath();
context.moveTo(left + rounding, top);
context.arcTo(right, top, right, bottom, rounding);
context.arcTo(right, bottom, left, bottom, rounding);
context.arcTo(left, bottom, left, top, rounding);
context.arcTo(left, top, right, top, rounding);
context.closePath();
context.clip();
context.drawImage(mailCanvas, frameLeft, frameTop, frameWidth, frameHeight);
context.restore();
const blob = await new Promise((resolve, reject) => canvas.toBlob(
value => value ? resolve(value) : reject(new Error('Could not encode the screenshot.')),
'image/png'
));
const filename = [selection.theme, selection.template, selection.locale]
.map(value => value.replace(/[<>:"/\\|?*\x00-\x1f]/g, '-')).join('_') + '.png';
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
link.remove();
// Give the browser time to start reading the download before releasing it.
setTimeout(() => URL.revokeObjectURL(url), 60000);
} catch (error) {
const message = showMessage('', 'error');
message.id = 'capture-message';
message.textContent = 'Screenshot failed: ' + String(error.message || error);
} finally {
capturing = false;
label.textContent = 'Screenshot';
updateCaptureButton();
}
}
// //
// Client transforms // Client transforms
@@ -470,10 +354,7 @@ function render() {
const loading = document.getElementById('loading'); const loading = document.getElementById('loading');
if(!rendered) return; if(!rendered) return;
// Same-origin DOM access is required for capture; scripts remain disabled. frame.setAttribute('sandbox', '');
frame.setAttribute('sandbox', 'allow-same-origin');
previewReady = false;
updateCaptureButton();
frame.style.display = 'none'; frame.style.display = 'none';
loading.style.display = 'flex'; loading.style.display = 'flex';
@@ -490,8 +371,6 @@ function render() {
loading.style.display = 'none'; loading.style.display = 'none';
frame.style.display = 'block'; frame.style.display = 'block';
frame.onload = null; frame.onload = null;
previewReady = true;
updateCaptureButton();
} }
frame.onload = finish; frame.onload = finish;
frame.srcdoc = content; frame.srcdoc = content;
@@ -514,7 +393,8 @@ function togglePanel() {
function toggleSection(id) { function toggleSection(id) {
const sec = document.getElementById(id); const sec = document.getElementById(id);
sec.classList.toggle('collapsed'); const collapsed = sec.classList.toggle('collapsed');
sec.querySelector('.section-content').inert = collapsed;
} }
// //
@@ -556,11 +436,11 @@ function switchTheme(v) { state.theme=v; render(); }
function switchTemplate(v) { state.template=v; switchLocale(state.locale); } 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();
} }
// //
@@ -602,6 +482,37 @@ function fillSelect(id, entries, selected) {
select.value=selected; select.value=selected;
} }
// 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) { function applyManifest(data) {
if (!data || data.schema !== 1 || !data.languages.length || !data.themes.length) { if (!data || data.schema !== 1 || !data.languages.length || !data.themes.length) {
throw new Error('Invalid preview manifest; regenerate with go run . preview all'); throw new Error('Invalid preview manifest; regenerate with go run . preview all');
@@ -612,16 +523,7 @@ function applyManifest(data) {
if (!data.languages.includes(state.locale)) state.locale=data.languages.includes('en-US') ? 'en-US' : data.languages[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-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-template',Object.entries(REGISTRY).map(([id,t])=>[id,t.name]),state.template);
fillSelect('sel-locale',data.languages.map(locale=>{ fillSelect('sel-locale',data.languages.map(locale=>[locale,localeLabel(locale)]),state.locale);
let name;
try {
// Show each language in its own language, independent of the browser UI.
if (Intl.DisplayNames.supportedLocalesOf([locale]).length) {
name=new Intl.DisplayNames([locale],{type:'language',fallback:'none'}).of(locale);
}
} catch (_) { /* Older browsers can still select languages by locale code. */ }
return [locale,name && name!==locale ? locale+' — '+name : locale];
}),state.locale);
document.querySelectorAll('#sel-locale option').forEach(option=>{ document.querySelectorAll('#sel-locale option').forEach(option=>{
option.lang=option.value; option.lang=option.value;
option.dir='auto'; option.dir='auto';
@@ -650,8 +552,6 @@ function loadLocale(lang) {
async function switchLocale(lang) { async function switchLocale(lang) {
if (!manifest?.languages.includes(lang)) return; if (!manifest?.languages.includes(lang)) return;
state.locale=lang; rendered=null; state.locale=lang; rendered=null;
previewReady=false;
updateCaptureButton();
const request=++localeRequest; const request=++localeRequest;
document.getElementById('preview-frame').style.display='none'; document.getElementById('preview-frame').style.display='none';
document.getElementById('loading').style.display='flex'; document.getElementById('loading').style.display='flex';
-22
View File
@@ -1,22 +0,0 @@
Copyright (c) 2012 Niklas von Hertzen
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
-11
View File
@@ -1,11 +0,0 @@
# html2canvas
Version: **1.4.1**, distributed under the [MIT license](LICENSE).
The preview uses this local copy to export the visible email container as PNG without a CDN connection. Release archives include the library and its license.
- Project: https://github.com/niklasvh/html2canvas
- Distribution: https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js
- SHA-256: `e87e550794322e574a1fda0c1549a3c70dae5a93d9113417a429016838eab8cb`
Capture uses DOM rendering, so CSS support and cross-origin images follow [html2canvas's documented limitations](https://html2canvas.hertzen.com/documentation). The iframe permits same-origin DOM access while keeping scripts disabled. Keep the version, license and checksum together when updating the library.
-20
View File
@@ -1,20 +0,0 @@
/*!
* html2canvas 1.4.1 <https://html2canvas.hertzen.com>
* Copyright (c) 2022 Niklas von Hertzen <https://hertzen.com>
* Released under MIT License
*/
!function(A,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(A="undefined"!=typeof globalThis?globalThis:A||self).html2canvas=e()}(this,function(){"use strict";
/*! *****************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */var r=function(A,e){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(A,e){A.__proto__=e}||function(A,e){for(var t in e)Object.prototype.hasOwnProperty.call(e,t)&&(A[t]=e[t])})(A,e)};function A(A,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function t(){this.constructor=A}r(A,e),A.prototype=null===e?Object.create(e):(t.prototype=e.prototype,new t)}var h=function(){return(h=Object.assign||function(A){for(var e,t=1,r=arguments.length;t<r;t++)for(var B in e=arguments[t])Object.prototype.hasOwnProperty.call(e,B)&&(A[B]=e[B]);return A}).apply(this,arguments)};function a(A,s,o,i){return new(o=o||Promise)(function(t,e){function r(A){try{n(i.next(A))}catch(A){e(A)}}function B(A){try{n(i.throw(A))}catch(A){e(A)}}function n(A){var e;A.done?t(A.value):((e=A.value)instanceof o?e:new o(function(A){A(e)})).then(r,B)}n((i=i.apply(A,s||[])).next())})}function H(t,r){var B,n,s,o={label:0,sent:function(){if(1&s[0])throw s[1];return s[1]},trys:[],ops:[]},A={next:e(0),throw:e(1),return:e(2)};return"function"==typeof Symbol&&(A[Symbol.iterator]=function(){return this}),A;function e(e){return function(A){return function(e){if(B)throw new TypeError("Generator is already executing.");for(;o;)try{if(B=1,n&&(s=2&e[0]?n.return:e[0]?n.throw||((s=n.return)&&s.call(n),0):n.next)&&!(s=s.call(n,e[1])).done)return s;switch(n=0,(e=s?[2&e[0],s.value]:e)[0]){case 0:case 1:s=e;break;case 4:return o.label++,{value:e[1],done:!1};case 5:o.label++,n=e[1],e=[0];continue;case 7:e=o.ops.pop(),o.trys.pop();continue;default:if(!(s=0<(s=o.trys).length&&s[s.length-1])&&(6===e[0]||2===e[0])){o=0;continue}if(3===e[0]&&(!s||e[1]>s[0]&&e[1]<s[3])){o.label=e[1];break}if(6===e[0]&&o.label<s[1]){o.label=s[1],s=e;break}if(s&&o.label<s[2]){o.label=s[2],o.ops.push(e);break}s[2]&&o.ops.pop(),o.trys.pop();continue}e=r.call(t,o)}catch(A){e=[6,A],n=0}finally{B=s=0}if(5&e[0])throw e[1];return{value:e[0]?e[1]:void 0,done:!0}}([e,A])}}}function t(A,e,t){if(t||2===arguments.length)for(var r,B=0,n=e.length;B<n;B++)!r&&B in e||((r=r||Array.prototype.slice.call(e,0,B))[B]=e[B]);return A.concat(r||e)}var d=(B.prototype.add=function(A,e,t,r){return new B(this.left+A,this.top+e,this.width+t,this.height+r)},B.fromClientRect=function(A,e){return new B(e.left+A.windowBounds.left,e.top+A.windowBounds.top,e.width,e.height)},B.fromDOMRectList=function(A,e){e=Array.from(e).find(function(A){return 0!==A.width});return e?new B(e.left+A.windowBounds.left,e.top+A.windowBounds.top,e.width,e.height):B.EMPTY},B.EMPTY=new B(0,0,0,0),B);function B(A,e,t,r){this.left=A,this.top=e,this.width=t,this.height=r}for(var f=function(A,e){return d.fromClientRect(A,e.getBoundingClientRect())},Q=function(A){for(var e=[],t=0,r=A.length;t<r;){var B,n=A.charCodeAt(t++);55296<=n&&n<=56319&&t<r?56320==(64512&(B=A.charCodeAt(t++)))?e.push(((1023&n)<<10)+(1023&B)+65536):(e.push(n),t--):e.push(n)}return e},g=function(){for(var A=[],e=0;e<arguments.length;e++)A[e]=arguments[e];if(String.fromCodePoint)return String.fromCodePoint.apply(String,A);var t=A.length;if(!t)return"";for(var r=[],B=-1,n="";++B<t;){var s=A[B];s<=65535?r.push(s):(s-=65536,r.push(55296+(s>>10),s%1024+56320)),(B+1===t||16384<r.length)&&(n+=String.fromCharCode.apply(String,r),r.length=0)}return n},e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",n="undefined"==typeof Uint8Array?[]:new Uint8Array(256),s=0;s<e.length;s++)n[e.charCodeAt(s)]=s;for(var o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",c="undefined"==typeof Uint8Array?[]:new Uint8Array(256),i=0;i<o.length;i++)c[o.charCodeAt(i)]=i;function w(A,e,t){return A.slice?A.slice(e,t):new Uint16Array(Array.prototype.slice.call(A,e,t))}var U=(l.prototype.get=function(A){var e;if(0<=A){if(A<55296||56319<A&&A<=65535)return e=this.index[A>>5],this.data[e=(e<<2)+(31&A)];if(A<=65535)return e=this.index[2048+(A-55296>>5)],this.data[e=(e<<2)+(31&A)];if(A<this.highStart)return e=this.index[e=2080+(A>>11)],e=this.index[e+=A>>5&63],this.data[e=(e<<2)+(31&A)];if(A<=1114111)return this.data[this.highValueIndex]}return this.errorValue},l);function l(A,e,t,r,B,n){this.initialValue=A,this.errorValue=e,this.highStart=t,this.highValueIndex=r,this.index=B,this.data=n}for(var C="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",u="undefined"==typeof Uint8Array?[]:new Uint8Array(256),F=0;F<C.length;F++)u[C.charCodeAt(F)]=F;function p(A,e,t,r){var B=r[t];if(Array.isArray(A)?-1!==A.indexOf(B):A===B)for(var n=t;n<=r.length;){if((o=r[++n])===e)return 1;if(o!==D)break}if(B===D)for(n=t;0<n;){var s=r[--n];if(Array.isArray(A)?-1!==A.indexOf(s):A===s)for(var o,i=t;i<=r.length;){if((o=r[++i])===e)return 1;if(o!==D)break}if(s!==D)break}}function E(A,e){for(var t=A;0<=t;){var r=e[t];if(r!==D)return r;t--}return 0}function I(t,A){var e=(B=function(A,r){void 0===r&&(r="strict");var B=[],n=[],sLine truncated