Compact export toolbar and inline HTML assets

This commit is contained in:
Codex
2026-07-08 06:10:27 +00:00
parent 0c57525807
commit 746fe2a65d
3 changed files with 104 additions and 20 deletions
+52 -20
View File
@@ -14,6 +14,13 @@ function escapeHtml(value: string): string {
.replace(/"/g, """);
}
const icons = {
download: `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4v10m0 0 4-4m-4 4-4-4"/><path d="M5 18.5h14"/></svg>`,
image: `<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="4" y="5" width="16" height="14" rx="2"/><path d="m7 16 4-4 3 3 2-2 3 3"/><circle cx="8.5" cy="8.5" r="1.2"/></svg>`,
share: `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8.5 12.5 15.5 8.5M8.5 11.5 15.5 15.5"/><circle cx="6.5" cy="12" r="2.5"/><circle cx="17.5" cy="7.5" r="2.5"/><circle cx="17.5" cy="16.5" r="2.5"/></svg>`,
chevron: `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m8 10 4 4 4-4"/></svg>`
};
function css(): string {
return `
:root {
@@ -95,9 +102,19 @@ h1 { margin: 0; font-size: clamp(28px, 5vw, 44px); line-height: 1.12; letter-spa
.content a[data-citation] { color: var(--muted); font-size: 0.92em; white-space: nowrap; text-decoration-thickness: 1px; text-underline-offset: 2px; }
.content a[data-citation] img { width: 14px; height: 14px; max-width: 14px; max-height: 14px; border-radius: 3px; object-fit: cover; vertical-align: -2px; margin: 0 3px 0 4px; }
.downloads { border-top: 1px solid var(--border); background: var(--soft); }
.downloads-inner { max-width: 880px; margin: 0 auto; padding: 22px 20px; display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.button, select { min-height: 38px; border: 1px solid var(--border); border-radius: 8px; background: var(--panel); color: var(--text); padding: 8px 12px; font: inherit; font-size: 14px; text-decoration: none; }
.button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.downloads-inner { max-width: 880px; margin: 0 auto; padding: 14px 20px; display: flex; flex-wrap: nowrap; gap: 8px; align-items: center; overflow-x: auto; }
.icon-button { width: 36px; height: 36px; border: 1px solid var(--border); border-radius: 8px; background: var(--panel); color: var(--text); padding: 0; display: inline-grid; place-items: center; flex: 0 0 auto; cursor: pointer; text-decoration: none; }
.icon-button svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.icon-button:hover { background: color-mix(in srgb, var(--panel) 88%, var(--text) 12%); }
.shot-control { display: inline-flex; align-items: stretch; flex: 0 0 auto; position: relative; }
.shot-control .shot-main { border-radius: 8px 0 0 8px; border-right: 0; }
.shot-control details { position: relative; }
.shot-control summary { list-style: none; border-radius: 0 8px 8px 0; width: 28px; }
.shot-control summary::-webkit-details-marker { display: none; }
.shot-control summary svg { width: 15px; height: 15px; }
.shot-menu { position: absolute; right: 0; bottom: calc(100% + 8px); z-index: 4; min-width: 150px; padding: 6px; border: 1px solid var(--border); border-radius: 8px; background: var(--panel); box-shadow: 0 12px 30px rgba(0,0,0,.12); }
.shot-menu button { width: 100%; border: 0; border-radius: 6px; background: transparent; color: var(--text); padding: 8px 10px; font: inherit; font-size: 13px; text-align: left; cursor: pointer; }
.shot-menu button:hover, .shot-menu button[data-current="true"] { background: var(--soft); }
.source { max-width: 880px; margin: 0 auto; padding: 18px 20px 34px; color: var(--muted); font-size: 12px; }
.source a { color: var(--muted); }
.failure { max-width: 720px; margin: 0 auto; padding: 80px 20px; }
@@ -113,14 +130,21 @@ h1 { margin: 0; font-size: clamp(28px, 5vw, 44px); line-height: 1.12; letter-spa
.message.user .content { border-radius: 18px; padding: 9px 14px; }
.avatar, .assistant-icon { width: 26px; height: 26px; }
.content ul, .content ol { padding-left: 1.25em; }
.downloads-inner { padding: 18px 16px; align-items: stretch; }
.button, select { width: 100%; }
.downloads-inner { padding: 12px 16px; }
}`;
}
function pageScript(slug: string): string {
return `
(function () {
function currentVariant() {
var device = matchMedia("(max-width: 640px)").matches ? "mobile" : "desktop";
var theme = matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
return device + "-" + theme;
}
function downloadShot(variant) {
location.href = "/aishare/${slug}/screenshots/" + variant + ".png";
}
var params = new URLSearchParams(location.search);
var theme = params.get("theme");
if (theme === "light" || theme === "dark") document.documentElement.dataset.theme = theme;
@@ -140,13 +164,15 @@ function pageScript(slug: string): string {
setTimeout(function () { button.textContent = "Copy"; }, 1400);
});
});
var form = document.querySelector("[data-shot-form]");
if (form) form.addEventListener("submit", function (event) {
event.preventDefault();
var device = form.querySelector("[name=device]").value;
var selectedTheme = form.querySelector("[name=theme]").value;
if (selectedTheme === "system") selectedTheme = matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
location.href = "/aishare/${slug}/screenshots/" + device + "-" + selectedTheme + ".png";
var defaultShot = document.querySelector("[data-shot-current]");
if (defaultShot) defaultShot.addEventListener("click", function () {
downloadShot(currentVariant());
});
document.querySelectorAll("[data-shot-variant]").forEach(function (button) {
if (button.dataset.shotVariant === currentVariant()) button.dataset.current = "true";
button.addEventListener("click", function () {
downloadShot(button.dataset.shotVariant);
});
});
})();`;
}
@@ -189,14 +215,20 @@ export function renderConversationPage(slug: string, conversation: NormalizedCon
</main>
<section class="downloads">
<div class="downloads-inner">
<a class="button primary" href="/aishare/${slug}/download.zip">Download HTML ZIP</a>
<a class="button" href="/aishare/${slug}/download.html">Download HTML</a>
<form data-shot-form style="display: contents">
<select name="device" aria-label="Screenshot size"><option value="desktop">Desktop</option><option value="mobile">Mobile</option></select>
<select name="theme" aria-label="Screenshot theme"><option value="system">Current theme</option><option value="light">Light</option><option value="dark">Dark</option></select>
<button class="button" type="submit">Download screenshot</button>
</form>
<button class="button" type="button" data-copy>Copy link</button>
<a class="icon-button" href="/aishare/${slug}/download" aria-label="Download export" title="Download export">${icons.download}</a>
<div class="shot-control">
<button class="icon-button shot-main" type="button" data-shot-current aria-label="Download screenshot" title="Download screenshot">${icons.image}</button>
<details>
<summary class="icon-button" aria-label="Choose screenshot variant" title="Choose screenshot variant">${icons.chevron}</summary>
<div class="shot-menu">
<button type="button" data-shot-variant="desktop-light">Desktop light</button>
<button type="button" data-shot-variant="desktop-dark">Desktop dark</button>
<button type="button" data-shot-variant="mobile-light">Mobile light</button>
<button type="button" data-shot-variant="mobile-dark">Mobile dark</button>
</div>
</details>
</div>
<button class="icon-button" type="button" data-copy aria-label="Copy link" title="Copy link">${icons.share}</button>
</div>
</section>
<footer class="source">