From 4a7bfe3eab1e8e404283b8899271a656aee56439 Mon Sep 17 00:00:00 2001 From: Kieran Klukas Date: Wed, 29 Jul 2026 01:34:22 +0000 Subject: [PATCH] feat: tweak the ui a bit --- public/favicon.svg | 3 +++ src/editor.ts | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------- src/index.ts | 32 ++++++++++++++++++++++++-------- src/render.ts | 16 +++++++++++++--- src/styles.ts | 58 +++++++++++++++++++++++++++++++++++++++++----------------- 5 file(s) changed, 144 insertion(s)(+), 42 deletion(s)(-) diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/editor.ts b/src/editor.ts --- a/src/editor.ts +++ b/src/editor.ts @@ -7,7 +7,7 @@ const CSS = ` :root{ --paper:#F3EFE6; --paper-2:#FBF9F3; --ink:#1C1B19; --ink-soft:#37342F; --muted:#77726A; --rule:#D9D2C5; --rule-soft:#E7E1D5; - --blue:#2C6DF5; --radius:10px; + --blue:#2559F4; --blue-hover:#1242D4; --radius:10px; } *{ box-sizing:border-box; } body{ margin:0; background:var(--paper); color:var(--ink-soft); font-family:"Inter",system-ui,sans-serif; font-size:15px; line-height:1.5; } @@ -18,7 +18,7 @@ .actions{ display:flex; align-items:center; gap:6px; } .icon-btn{ width:38px; height:38px; display:inline-flex; align-items:center; justify-content:center; - border:0; border-radius:8px; + border:0; border-radius:6px; background:transparent; color:var(--muted); cursor:pointer; transition:background .15s ease, color .15s ease; @@ -103,7 +103,7 @@ .page-card.filled .page-preview input[type="file"]{ display:none; } .btn{ font:inherit; font-size:.92rem; font-weight:500; border:0; cursor:pointer; - border-radius:8px; padding:9px 16px; display:inline-flex; align-items:center; gap:8px; + border-radius:6px; padding:9px 16px; display:inline-flex; align-items:center; gap:8px; color:#fff; background:var(--ink); transition:filter .15s ease, transform .1s ease, background .2s ease; } @@ -113,16 +113,18 @@ .btn.copied{ background:var(--blue); pointer-events:none; } .btn.copied svg{ transform:scale(1.15); } .btn svg{ display:block; transition:transform .2s ease; } .btn.secondary{ background:var(--muted); } + .btn.publish{ background:var(--blue); } + .btn.publish:hover{ background:var(--blue-hover); } .btn.ghost{ background:transparent; color:var(--ink); - border:1px solid var(--rule); + border:0; } - .btn.ghost:hover, - .btn.ghost:active{ border-color:color-mix(in srgb, var(--ink) 18%, var(--rule)); } + .btn.ghost:hover{ background:rgba(28,27,25,.05); } + .btn.ghost:active{ background:transparent; } .toast{ position:fixed; left:50%; bottom:28px; transform:translateX(-50%) translateY(12px); - background:var(--ink); color:#fff; font-size:.88rem; padding:9px 16px; border-radius:8px; + background:var(--ink); color:#fff; font-size:.88rem; padding:9px 16px; border-radius:6px; opacity:0; pointer-events:none; transition:opacity .2s ease, transform .2s ease; } .toast.show{ opacity:1; transform:translateX(-50%) translateY(0); } @@ -164,6 +166,7 @@ } async function uploadFiles(files, startIndex) { let index = startIndex; + const dateEl = document.getElementById("date"); for (const file of files) { while (index >= pages.length) { pages.push({ markdown: "", image_key: "" }); @@ -175,6 +178,11 @@ pages.push({ markdown: "", image_key: "" }); } } await uploadFile(file, index); + // Prefill date from first file's lastModified + if (!dateEl.value && file.lastModified) { + const d = new Date(file.lastModified); + dateEl.value = d.toISOString().slice(0, 10); + } index++; } if (pages.length === 0 || pages[pages.length - 1].image_key) { @@ -183,9 +191,40 @@ } render(); } +function parseAndDistribute(text, startIndex) { + let md = text.trim(); + const titleEl = document.getElementById("title"); + + // Extract # Title from the top + const titleMatch = md.match(/^#\\s+(.+?)(\\n|$)/); + if (titleMatch) { + if (!titleEl.value) titleEl.value = titleMatch[1].trim(); + md = md.slice(titleMatch[0].length).trim(); + } + + // Split on --- delimiters + const sections = md.split(/^---\\s*$/m).map(s => s.trim()).filter(Boolean); + + // Distribute to existing pages with images + let pageIdx = startIndex; + for (const section of sections) { + // Find the next page with an image starting from pageIdx + while (pageIdx < pages.length && !pages[pageIdx].image_key) pageIdx++; + if (pageIdx >= pages.length) break; // drop extras + pages[pageIdx].markdown = section; + pageIdx++; + } + + render(); + toast("Distributed " + sections.length + " page" + (sections.length > 1 ? "s" : "")); +} + function render(){ - document.getElementById("title").value = DOC?.title || ""; - document.getElementById("date").value = DOC?.date || ""; + const titleEl = document.getElementById("title"); + const dateEl = document.getElementById("date"); + // Only prefill from DOC if the fields are empty (first load) + if (!titleEl.value && DOC?.title) titleEl.value = DOC.title; + if (!dateEl.value && DOC?.date) dateEl.value = DOC.date; const container = document.getElementById("pages"); container.innerHTML = pages.map((p, i) => { @@ -222,6 +261,12 @@ container.querySelectorAll("textarea").forEach(t => { t.addEventListener("input", e => { pages[+e.target.dataset.index].markdown = e.target.value; + }); + t.addEventListener("paste", e => { + const text = e.clipboardData?.getData("text"); + if (!text || !text.includes("---")) return; + e.preventDefault(); + parseAndDistribute(text, +t.dataset.index); }); }); container.querySelectorAll('[data-action="remove"]').forEach(b => { @@ -369,7 +414,7 @@ Copy prompt ${viewLink} - +
@@ -389,14 +434,18 @@

Pages

-
diff --git a/src/index.ts b/src/index.ts --- a/src/index.ts +++ b/src/index.ts @@ -66,14 +66,30 @@ }, 2000); } catch { toast("Copy failed"); delete btn.dataset.busy; } }); document.getElementById("reader").addEventListener("click", (e) => { - const btn = e.target.closest("[data-dl]"); - if(!btn) return; - const img = btn.closest(".scan__canvas").querySelector("img"); - if(!img) { toast("No scan on this page"); return; } - const a = document.createElement("a"); - a.href = img.src; - a.download = img.src.split("/").pop() || "page.svg"; - a.click(); + // Toggle menu + const menuBtn = e.target.closest(".scan__menu-btn"); + if (menuBtn) { + const dropdown = menuBtn.nextElementSibling; + document.querySelectorAll(".scan__menu-dropdown.open").forEach(d => { + if (d !== dropdown) d.classList.remove("open"); + }); + dropdown.classList.toggle("open"); + return; + } + // Download action + const dlBtn = e.target.closest("[data-dl]"); + if (dlBtn) { + const img = dlBtn.closest(".scan__canvas").querySelector("img"); + if (!img) { toast("No scan on this page"); return; } + const a = document.createElement("a"); + a.href = img.src; + a.download = img.src.split("/").pop() || "page.svg"; + a.click(); + dlBtn.closest(".scan__menu-dropdown").classList.remove("open"); + return; + } + // Close menus on outside click + document.querySelectorAll(".scan__menu-dropdown.open").forEach(d => d.classList.remove("open")); }); diff --git a/src/render.ts b/src/render.ts --- a/src/render.ts +++ b/src/render.ts @@ -57,6 +57,8 @@ const content = renderMarkdown(page.markdown); const imageUrl = `/i/${page.image_key}`; + const ext = page.image_key?.split(".").pop()?.toUpperCase() || "Image"; + return `
@@ -66,9 +68,17 @@
diff --git a/src/styles.ts b/src/styles.ts --- a/src/styles.ts +++ b/src/styles.ts @@ -8,9 +8,9 @@ --muted:#77726A; --rule:#D9D2C5; --rule-soft:#E7E1D5; --dot:#DBD5C8; - --blue:#2C6DF5; + --blue:#2559F4; --blue-hover:#1242D4; --radius:8px; - --measure:58ch; + --measure:52ch; } *{ box-sizing:border-box; } @@ -60,7 +60,7 @@ font-size:.92rem; font-weight:500; border:0; cursor:pointer; - border-radius:8px; + border-radius:6px; padding:9px 16px; display:inline-flex; align-items:center; @@ -86,7 +86,7 @@ .icon-btn{ width:38px; height:38px; display:inline-flex; align-items:center; justify-content:center; - border:0; border-radius:8px; + border:0; border-radius:6px; background:transparent; color:var(--muted); cursor:pointer; transition:background .15s ease, color .15s ease; @@ -103,7 +103,7 @@ } .spread{ display:grid; - grid-template-columns:minmax(0,1.06fr) minmax(0,0.94fr); + grid-template-columns:minmax(0,1.15fr) minmax(0,0.85fr); gap:56px; align-items:start; padding-block:40px; @@ -147,7 +147,8 @@ .scan__meta{ display:flex; align-items:center; justify-content:space-between; margin:0 2px 10px; } - .scan__page{ font-size:.82rem; color:var(--muted); letter-spacing:.02em; } + .scan__page{ font-size:.82rem; color:var(--muted); letter-spacing:.02em; opacity:0; transition:opacity .15s ease; } + .scan:hover .scan__page{ opacity:1; } .scan__canvas{ position:relative; @@ -157,25 +158,48 @@ background-position:14px 14px; border:1px solid var(--rule); border-radius:6px; box-shadow:0 1px 1px rgba(30,25,15,.03), 0 10px 26px -22px rgba(40,32,15,.22); - overflow:hidden; + overflow:visible; } .scan__canvas svg, .scan__canvas img{ width:100%; height:auto; display:block; } - .scan__dl{ + .scan__menu{ position:absolute; top:10px; right:10px; + z-index:2; + } + .scan__menu-btn{ width:32px; height:32px; - border:1px solid var(--rule-soft); - background:color-mix(in srgb, var(--paper-2) 82%, transparent); + display:inline-flex; align-items:center; justify-content:center; + border:0; + background:transparent; color:var(--muted); - box-shadow:0 1px 2px rgba(30,25,15,.06); + border-radius:6px; + cursor:pointer; opacity:0; transform:translateY(-3px); transition:opacity .16s ease, transform .16s ease, background .15s ease, color .15s ease; } - .scan__canvas:hover .scan__dl, - .scan__dl:focus-visible{ opacity:1; transform:none; } - .scan__dl:hover{ background:#fff; color:var(--ink); } - @media (hover:none){ .scan__dl{ opacity:1; transform:none; } } + .scan__canvas:hover .scan__menu-btn, + .scan__menu-btn:focus-visible{ opacity:1; transform:none; } + .scan__menu-btn:hover{ background:rgba(0,0,0,.05); color:var(--ink); } + .scan__menu-dropdown{ + position:absolute; top:100%; right:0; margin-top:4px; + background:var(--paper-2); border:1px solid var(--rule); + border-radius:6px; + box-shadow:0 4px 16px rgba(30,25,15,.1); + min-width:140px; max-width:240px; + display:none; + } + .scan__menu-dropdown.open{ display:block; } + .scan__menu-dropdown button{ + display:flex; align-items:center; gap:8px; + width:100%; padding:9px 14px; white-space:nowrap; + border:0; background:transparent; color:var(--ink-soft); + font:inherit; font-size:.85rem; cursor:pointer; + transition:background .12s ease; + } + .scan__menu-dropdown button:hover{ background:rgba(0,0,0,.04); } + .scan__menu-dropdown button svg{ flex-shrink:0; width:14px; height:14px; } + @media (hover:none){ .scan__menu-btn{ opacity:1; transform:none; } } .prose pre{ font-family:"JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace; @@ -211,7 +235,7 @@ .toast{ position:fixed; left:50%; bottom:28px; transform:translateX(-50%) translateY(12px); background:var(--ink); color:#fff; - font-size:.88rem; padding:9px 16px; border-radius:8px; + font-size:.88rem; padding:9px 16px; border-radius:6px; opacity:0; pointer-events:none; transition:opacity .2s ease, transform .2s ease; } .toast.show{ opacity:1; transform:translateX(-50%) translateY(0); } @@ -219,7 +243,7 @@ @media (max-width:880px){ .reader{ padding:28px 20px 90px; } .spread{ grid-template-columns:1fr; gap:26px; } - .scan{ position:static; } + .scan{ position:static; max-width:480px; } .title{ margin-bottom:24px; } } -- tangled.sh