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
This commit is contained in:
@@ -6,9 +6,22 @@ import SourceBadge from './SourceBadge.astro';
|
||||
|
||||
interface Props {
|
||||
item: FindItem;
|
||||
inList?: boolean;
|
||||
capturedAt?: Date;
|
||||
availableTags?: string[];
|
||||
}
|
||||
const { item } = Astro.props;
|
||||
const { item, inList, capturedAt, availableTags = [] } = Astro.props;
|
||||
const hero = findHero(item.id);
|
||||
const showInListAttr = import.meta.env.DEV && inList !== undefined;
|
||||
const selectedTags: string[] = item.data.editorialTags ?? [];
|
||||
const selectedSet = new Set(selectedTags);
|
||||
|
||||
const fmtCaptured = new Intl.DateTimeFormat('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
});
|
||||
|
||||
function host(url: string) {
|
||||
try {
|
||||
@@ -19,7 +32,10 @@ function host(url: string) {
|
||||
}
|
||||
---
|
||||
|
||||
<article class:list={['find-card', { 'has-hero': !!hero }]}>
|
||||
<article
|
||||
class:list={['find-card', { 'has-hero': !!hero }]}
|
||||
data-in-list={showInListAttr ? String(inList) : undefined}
|
||||
>
|
||||
{import.meta.env.DEV && (
|
||||
<label class="edit-checkbox" aria-label={`Select ${item.data.name}`}>
|
||||
<input type="checkbox" data-find-checkbox data-slug={item.id} />
|
||||
@@ -46,6 +62,54 @@ function host(url: string) {
|
||||
<span class="ext-label">→</span>
|
||||
<span class="host">{host(item.data.link)}</span>
|
||||
</p>
|
||||
{capturedAt && capturedAt.valueOf() > 0 && (
|
||||
<p class="captured-at-row">
|
||||
<time datetime={capturedAt.toISOString()} class="captured-at">
|
||||
{fmtCaptured.format(capturedAt)}
|
||||
</time>
|
||||
</p>
|
||||
)}
|
||||
|
||||
{import.meta.env.DEV && (
|
||||
<div class="edit-tags">
|
||||
{selectedTags.length > 0 && (
|
||||
<div class="edit-tags-selected" aria-hidden="true">
|
||||
{selectedTags.map((tag) => (
|
||||
<span class={`edit-tag is-selected ${tag === 'delete' ? 'is-delete' : ''}`}>{tag}</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div class="edit-tags-picker" role="group" aria-label="Editorial tags">
|
||||
{availableTags.map((tag) => (
|
||||
<button
|
||||
type="button"
|
||||
class:list={[
|
||||
'edit-tag',
|
||||
{ 'is-selected': selectedSet.has(tag), 'is-delete': tag === 'delete' },
|
||||
]}
|
||||
data-edit-action="copy"
|
||||
data-command={`/edit-find-tag ${item.id} ${tag}`}
|
||||
data-confirm={selectedSet.has(tag)
|
||||
? `Remove "${tag}" from "${item.data.name}".`
|
||||
: `Tag "${item.data.name}" with "${tag}".`}
|
||||
>
|
||||
{tag}
|
||||
</button>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
class="edit-tag edit-tag-prompt"
|
||||
data-edit-action="tag-prompt"
|
||||
data-slug={item.id}
|
||||
data-name={item.data.name}
|
||||
aria-label="Add custom tag"
|
||||
title="Add custom tag"
|
||||
>
|
||||
…
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</article>
|
||||
|
||||
@@ -124,6 +188,81 @@ function host(url: string) {
|
||||
}
|
||||
.ext-label { color: var(--accent); font-family: ui-sans-serif, system-ui, sans-serif; }
|
||||
|
||||
.captured-at-row {
|
||||
margin: 0.25rem 0 0;
|
||||
line-height: 1;
|
||||
}
|
||||
.captured-at {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
font-size: 0.65rem;
|
||||
color: var(--muted);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Editorial tags (dev-only) */
|
||||
.edit-tags {
|
||||
margin-top: 0.55rem;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
.edit-tags-selected,
|
||||
.edit-tags-picker {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
.edit-tags-picker { display: none; }
|
||||
.find-card:hover .edit-tags-selected,
|
||||
.find-card:focus-within .edit-tags-selected { display: none; }
|
||||
.find-card:hover .edit-tags-picker,
|
||||
.find-card:focus-within .edit-tags-picker { display: flex; }
|
||||
|
||||
.edit-tag {
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, 'Helvetica Neue', sans-serif;
|
||||
font-size: 0.68rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
line-height: 1;
|
||||
padding: 0.28rem 0.55rem;
|
||||
margin: 0;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--rule);
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
cursor: pointer;
|
||||
transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
|
||||
}
|
||||
button.edit-tag { font: inherit; font-size: 0.68rem; font-weight: 600; }
|
||||
.edit-tag:hover {
|
||||
color: var(--accent);
|
||||
border-color: color-mix(in srgb, var(--accent) 40%, var(--rule));
|
||||
}
|
||||
.edit-tag.is-selected {
|
||||
color: var(--bg);
|
||||
background: var(--fg);
|
||||
border-color: var(--fg);
|
||||
}
|
||||
.edit-tag.is-selected:hover {
|
||||
background: color-mix(in srgb, var(--fg) 85%, var(--accent));
|
||||
color: var(--bg);
|
||||
border-color: color-mix(in srgb, var(--fg) 85%, var(--accent));
|
||||
}
|
||||
.edit-tag.is-delete {
|
||||
color: #c0392b;
|
||||
border-color: color-mix(in srgb, #c0392b 35%, var(--rule));
|
||||
}
|
||||
.edit-tag.is-delete.is-selected {
|
||||
background: #c0392b;
|
||||
color: #fff;
|
||||
border-color: #c0392b;
|
||||
}
|
||||
.edit-tag-prompt {
|
||||
font-size: 0.85rem;
|
||||
padding: 0.1rem 0.6rem 0.18rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.edit-checkbox {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
|
||||
Reference in New Issue
Block a user