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
208 lines
6.0 KiB
Plaintext
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>
|