Files
unique/src/components/EditConfirm.astro
T
rzen 7e1b200bea site: editorial tooling, grid masonry fix, new reviews + finds
UI / dev tooling
- /find/ index: sort by file birthtime desc (sub-day chronology even when
  many finds share the same frontmatter date); render the captured-at on
  its own line under the host (~0.65rem mono muted)
- New dev-only filter at the top of /find/: All / Not in lists, persisted
  in localStorage; "not in lists" hides finds referenced by any superpost
- New editorial-tags chip strip on each find card (dev-only): selected
  tags visible by default, full picker on hover/focus-within with a "…"
  button for arbitrary tag entry; clicks copy /edit-find-tag <slug> <tag>
  to the clipboard via the existing EditConfirm singleton
- New optional editorialTags: string[] field on the find collection
- New src/data/editorial-tags.json — master list of available tags (initial
  set: ["delete"]); display order follows file order

/grid/ now uses the JS Masonry component instead of CSS column-count, so
the middle column no longer "dips"; tile hover updated to match the home
page's accent-glow / no-translate style

Content
- New review: apex-markdown-processor (graduated from find of same slug;
  hero reused from the find folder)
- New review: sindre-sorhus-older-mac-apps (graduated; intentionally
  hero-less per the source page's text-only design)
- New review: superkey (added by user; hero wired in src/lib/hero.ts)
- 24 new finds captured by /daily-finds across travel, jokes, quotes,
  product picks, and indie tools
- 2 new finds superposts: 2026-05-04-a-small-atlas, 2026-05-04-off-until-needed

Misc
- daily-finds.log.md and candidate-sources.md updated with the day's runs
- build-finds skill prompt iterated by user
2026-05-04 18:57:04 -04:00

208 lines
6.0 KiB
Plaintext

---
// Singleton confirm dialog + toast for editorial actions.
// Included once in BaseLayout when import.meta.env.DEV is true.
// Listens for clicks on any [data-edit-action] element, presents a confirm
// modal, then copies the command to the clipboard on go.
---
<div id="edit-confirm" class="edit-confirm" hidden>
<div class="edit-confirm-overlay" data-close></div>
<div
class="edit-confirm-modal"
role="dialog"
aria-modal="true"
aria-labelledby="edit-confirm-title"
>
<h3 id="edit-confirm-title">Confirm</h3>
<p class="edit-confirm-description"></p>
<pre class="edit-confirm-command"></pre>
<div class="edit-confirm-actions">
<button type="button" class="edit-confirm-cancel" data-close>Cancel</button>
<button type="button" class="edit-confirm-go">Copy command</button>
</div>
</div>
</div>
<div id="edit-toast" class="edit-toast" hidden></div>
<script>
const dialog = document.getElementById('edit-confirm');
const desc = dialog?.querySelector('.edit-confirm-description');
const cmdEl = dialog?.querySelector('.edit-confirm-command');
const goBtn = dialog?.querySelector('.edit-confirm-go') as HTMLButtonElement | null;
const toast = document.getElementById('edit-toast');
let pendingCommand = '';
let toastTimer: ReturnType<typeof setTimeout> | undefined;
function showToast(text: string) {
if (!toast) return;
toast.textContent = text;
toast.hidden = false;
clearTimeout(toastTimer);
toastTimer = setTimeout(() => { toast.hidden = true; }, 2400);
}
function openModal({ command, description }: { command: string; description: string }) {
if (!dialog || !desc || !cmdEl) return;
pendingCommand = command;
desc.textContent = description;
cmdEl.textContent = command;
dialog.hidden = false;
setTimeout(() => goBtn?.focus(), 0);
}
function closeModal() {
if (!dialog) return;
dialog.hidden = true;
pendingCommand = '';
}
dialog?.addEventListener('click', (e) => {
const t = e.target as HTMLElement;
if (t.closest('[data-close]')) closeModal();
});
goBtn?.addEventListener('click', async () => {
const cmd = pendingCommand;
closeModal();
try {
await navigator.clipboard.writeText(cmd);
showToast('Copied — paste into Claude');
} catch {
showToast('Copy failed — see console');
console.error('clipboard write failed; command was:', cmd);
}
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && dialog && !dialog.hidden) closeModal();
});
document.addEventListener('click', (e) => {
const t = e.target as HTMLElement;
const btn = t.closest<HTMLElement>('[data-edit-action]');
if (!btn) return;
e.preventDefault();
let command = btn.dataset.command || '';
let description = btn.dataset.confirm || '';
if (btn.dataset.editAction === 'finds-collection') {
const checked = Array.from(
document.querySelectorAll<HTMLInputElement>('[data-find-checkbox]:checked')
);
if (checked.length === 0) {
showToast('Select at least one find first.');
return;
}
const slugs = checked.map((cb) => cb.dataset.slug).filter(Boolean).join(',');
command = `/build-finds ${slugs}`;
description = `Build a finds collection from ${checked.length} selected find${
checked.length === 1 ? '' : 's'
}.`;
}
if (btn.dataset.editAction === 'tag-prompt') {
const slug = btn.dataset.slug || '';
const name = btn.dataset.name || slug;
const raw = window.prompt(`Custom tag for "${name}":`);
if (!raw) return;
const tag = raw.trim().toLowerCase().replace(/\s+/g, '-');
if (!tag) return;
command = `/edit-find-tag ${slug} ${tag}`;
description = `Tag "${name}" with "${tag}".`;
}
openModal({ command, description });
});
</script>
<style>
.edit-confirm[hidden] { display: none; }
.edit-confirm {
position: fixed;
inset: 0;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
}
.edit-confirm-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.5);
}
.edit-confirm-modal {
position: relative;
background: var(--bg);
border: 1px solid var(--rule);
border-radius: 10px;
padding: 1.25rem 1.4rem 1.1rem;
max-width: 28rem;
width: 100%;
box-shadow: 0 20px 60px -10px rgba(0, 0, 0, 0.4);
font-family: ui-sans-serif, system-ui, -apple-system, 'Helvetica Neue', sans-serif;
}
.edit-confirm-modal h3 {
margin: 0 0 0.5rem;
font-family: inherit;
font-size: 1.05rem;
}
.edit-confirm-description {
margin: 0 0 0.85rem;
color: var(--muted);
font-size: 0.92rem;
line-height: 1.45;
}
.edit-confirm-command {
margin: 0 0 1rem;
padding: 0.55rem 0.7rem;
background: var(--rule);
border-radius: 6px;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 0.85rem;
color: var(--fg);
white-space: pre-wrap;
word-break: break-all;
}
.edit-confirm-actions {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
}
.edit-confirm-actions button {
padding: 0.45rem 0.95rem;
font-family: inherit;
font-size: 0.9rem;
font-weight: 600;
border-radius: 6px;
cursor: pointer;
border: 1px solid var(--rule);
background: var(--bg);
color: var(--fg);
}
.edit-confirm-actions .edit-confirm-go {
background: var(--accent);
color: var(--bg);
border-color: var(--accent);
}
.edit-confirm-actions .edit-confirm-go:hover { opacity: 0.92; }
.edit-toast[hidden] { display: none; }
.edit-toast {
position: fixed;
bottom: 1.5rem;
left: 50%;
transform: translateX(-50%);
z-index: 1001;
padding: 0.6rem 1rem;
background: var(--fg);
color: var(--bg);
border-radius: 999px;
font-family: ui-sans-serif, system-ui, -apple-system, 'Helvetica Neue', sans-serif;
font-size: 0.85rem;
box-shadow: 0 4px 14px -4px rgba(0, 0, 0, 0.3);
}
</style>