Something went wrong. Try again.
Website hub for the atmosphere community, aggregating posts, events, regional, and more
Something went wrong. Try again.
14 kB · 502 lines
Astro
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503---import type { ShareCandidateList } from '../../lib/community/share-candidates';import { actions } from 'astro:actions';import SafeExternalLink from '../SafeExternalLink.astro';import ShareableContentIcon from './ShareableContentIcon.astro';
interface Props { candidates: ShareCandidateList | null; repo: string; error?: boolean; initialVisible?: number; loadStep?: number;}
const { candidates, repo, error = false, initialVisible = 5, loadStep = 5,} = Astro.props;
const candidateList = candidates && (candidates.events.length > 0 || candidates.documents.length > 0) ? candidates : null;const items = candidateList ? [...candidateList.events, ...candidateList.documents] .sort((a, b) => (b.date?.getTime() ?? 0) - (a.date?.getTime() ?? 0)) : [];
function formatDate(date: Date | undefined): string { if (!date) return ''; return new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric', year: 'numeric', }).format(date);}
---
{error && ( <p class="shareable-content__notice" role="status"> Couldn’t load shareable records right now. </p>)}
{!error && candidateList && ( <div class="shareable-content__panel" data-share-picker data-initial-visible={initialVisible} data-load-step={loadStep} > <div class="shareable-content__header"> <div class="shareable-content__summary"> <span data-share-picker-count> {candidateList.events.length + candidateList.documents.length} found </span> <span>Events and Standard Site documents</span> </div> <div class="shareable-content__toolbar" aria-label="Filter shareable records"> <button class="shareable-content__filter" type="button" data-filter="all" aria-pressed="true"> All </button> <button class="shareable-content__filter" type="button" data-filter="event" aria-pressed="false"> Events </button> <button class="shareable-content__filter" type="button" data-filter="document" aria-pressed="false"> Content </button> </div> </div> <ul class="shareable-content__list" data-share-picker-list> {items.map((candidate, index) => { const rowUrl = candidate.previewUrl ?? candidate.uri; return ( <li class="share-row" data-share-row data-kind={candidate.kind} hidden={index >= initialVisible}> <ShareableContentIcon kind={candidate.kind} /> <div class="share-row__main"> <SafeExternalLink href={rowUrl} class="share-row__title" announceNewTab> {candidate.title} </SafeExternalLink> <p class="share-row__meta"> {candidate.date ? ( <time datetime={candidate.date.toISOString()}> {candidate.kind === 'event' ? 'Held' : 'Published'} {formatDate(candidate.date)} </time> ) : ( <span>No date</span> )} <span aria-hidden="true">/</span> <span>{candidate.uri}</span> </p> </div> <div class="share-row__actions"> <SafeExternalLink href={rowUrl} class="share-row__preview" fallbackTag="none" announceNewTab > View </SafeExternalLink> <form method="POST" action={actions.shareAtmosphereContent}> <input type="hidden" name="sourceRepo" value={repo} /> <input type="hidden" name="candidateUri" value={candidate.uri} /> <button class="share-row__share" type="submit"> Share </button> </form> </div> </li> ); })} </ul> <p class="shareable-content__empty-filter" data-share-picker-empty hidden> No records match this filter. </p> <button class="shareable-content__load" type="button" data-share-picker-load hidden={items.length <= initialVisible} > Load more </button> </div>)}
{!error && !candidateList && ( <div class="shareable-content__panel shareable-content__panel--empty"> <p class="shareable-content__notice shareable-content__notice--warning" role="status"> No shareable events or Standard Site documents were found for {repo}. You can still share an AT URI. </p> </div>)}
<script> type ShareFilter = 'all' | 'event' | 'document';
document.querySelectorAll<HTMLElement>('[data-share-picker]').forEach((picker) => { const rows = Array.from(picker.querySelectorAll<HTMLElement>('[data-share-row]')); const filters = Array.from(picker.querySelectorAll<HTMLButtonElement>('[data-filter]')); const loadButton = picker.querySelector<HTMLButtonElement>('[data-share-picker-load]'); const count = picker.querySelector<HTMLElement>('[data-share-picker-count]'); const empty = picker.querySelector<HTMLElement>('[data-share-picker-empty]'); const initialVisible = Number(picker.dataset.initialVisible ?? 5); const loadStep = Number(picker.dataset.loadStep ?? 5); let activeFilter: ShareFilter = 'all'; let visibleLimit = initialVisible;
const matchesFilter = (row: HTMLElement) => activeFilter === 'all' || row.dataset.kind === activeFilter;
const sync = () => { let matchCount = 0; let shownCount = 0; rows.forEach((row) => { const match = matchesFilter(row); if (match) matchCount++; const show = match && shownCount < visibleLimit; row.hidden = !show; if (show) shownCount++; });
if (count) { const noun = matchCount === 1 ? 'record' : 'records'; count.textContent = `${shownCount} of ${matchCount} ${noun}`; }
if (empty) empty.hidden = matchCount > 0; if (loadButton) loadButton.hidden = visibleLimit >= matchCount; };
filters.forEach((button) => { button.addEventListener('click', () => { activeFilter = (button.dataset.filter ?? 'all') as ShareFilter; visibleLimit = initialVisible; filters.forEach((item) => { item.setAttribute('aria-pressed', String(item === button)); }); sync(); }); });
loadButton?.addEventListener('click', () => { visibleLimit += loadStep; sync(); });
sync(); });</script>
<style> .shareable-content__panel { display: grid; gap: var(--space-md); padding-block-start: var(--space-md); }
.shareable-content__panel--empty { display: flex; align-items: center; justify-content: space-between; gap: var(--space-sm); flex-wrap: wrap; }
.shareable-content__notice { margin: 0; color: var(--foreground-secondary); font-size: var(--text-xs); line-height: var(--leading-normal); }
.shareable-content__notice--warning { padding: var(--space-sm) var(--space-md); background: color-mix(in oklch, var(--muted) 55%, white); border: 1px solid var(--border); border-radius: var(--radius-sm); color: var(--foreground-muted); }
.shareable-content__header { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--space-md); flex-wrap: wrap; }
.shareable-content__summary { display: flex; align-items: center; gap: var(--space-xs); min-width: 0; flex: 1 1 auto; color: var(--foreground-secondary); font-size: 0.6875rem; font-weight: 700; line-height: var(--leading-normal); text-transform: uppercase; letter-spacing: 0.04em; }
.shareable-content__toolbar { display: inline-flex; align-items: center; gap: 3px; padding: 3px; background: linear-gradient( 135deg, color-mix(in oklch, var(--primary) 8%, var(--card)), var(--card) 72% ); border: 1px solid color-mix(in oklch, var(--primary) 20%, var(--border)); border-radius: var(--radius-pill); box-shadow: 0 1px 0 color-mix(in oklch, var(--primary) 10%, transparent); }
.shareable-content__filter { min-height: 30px; border: 0; border-radius: var(--radius-pill); padding: var(--space-xs) var(--space-md); background: transparent; color: var(--foreground-secondary); font: inherit; font-size: var(--text-xs); font-weight: 800; line-height: 1; cursor: pointer; transition: background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease; }
.shareable-content__filter[aria-pressed='true'] { background: var(--primary); color: var(--primary-foreground); box-shadow: 0 1px 2px color-mix(in oklch, var(--primary) 20%, transparent), inset 0 1px 0 color-mix(in oklch, var(--primary-foreground) 22%, transparent); }
.shareable-content__filter:not([aria-pressed='true']):hover { background: color-mix(in oklch, var(--primary) 10%, transparent); color: var(--primary); }
.shareable-content__list { display: grid; gap: 1px; overflow: hidden; list-style: none; background: var(--border); border: 1px solid var(--border); border-radius: var(--radius-sm); }
.share-row { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: var(--space-sm); min-width: 0; padding: var(--space-sm); background: var(--card); transition: background 0.15s ease; }
.share-row:hover { background: color-mix(in oklch, var(--primary) 5%, var(--card)); }
.share-row__main { min-width: 0; }
.share-row__title { display: block; overflow: hidden; color: var(--card-foreground); font-size: var(--text-sm); font-weight: 700; line-height: var(--leading-tight); text-overflow: ellipsis; white-space: nowrap; }
.share-row__title:hover { color: var(--primary); }
.share-row__meta { display: flex; align-items: center; gap: 6px; min-width: 0; overflow: hidden; color: var(--foreground-muted); font-size: 0.6875rem; line-height: 1.4; }
.share-row__meta span:last-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.share-row__actions { display: inline-flex; align-items: center; gap: var(--space-xs); }
.share-row__preview, .share-row__share, .shareable-content__load { min-height: 30px; display: inline-flex; align-items: center; justify-content: center; border-radius: var(--radius-sm); font: inherit; font-size: var(--text-xs); font-weight: 800; line-height: 1; white-space: nowrap; }
.share-row__preview { padding-inline: var(--space-sm); color: var(--primary); }
.share-row__preview:hover { background: var(--primary-muted); }
.share-row__share { min-width: 4rem; border: 0; padding: var(--space-xs) var(--space-sm); background: var(--primary); color: var(--primary-foreground); cursor: pointer; transition: background 0.15s ease; }
.share-row__share:hover { background: var(--primary-hover); }
.share-row__share:focus-visible, .shareable-content__filter:focus-visible, .shareable-content__load:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.shareable-content__empty-filter { margin: 0; padding: var(--space-sm); color: var(--foreground-secondary); font-size: var(--text-xs); line-height: var(--leading-normal); }
.shareable-content__load { justify-self: center; min-height: 34px; border: 1px solid color-mix(in oklch, var(--primary) 28%, var(--border)); border-radius: var(--radius-pill); padding: var(--space-xs) var(--space-base); background: linear-gradient( 135deg, color-mix(in oklch, var(--primary) 9%, var(--card)), var(--card) ); color: var(--primary); cursor: pointer; transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease; }
.shareable-content__load:hover { border-color: var(--primary); background: var(--card); box-shadow: 0 1px 2px color-mix(in oklch, var(--primary) 16%, transparent); }
.share-row__share:disabled, .shareable-content__load:disabled { opacity: 0.55; cursor: not-allowed; }
.share-row__share:disabled:hover, .shareable-content__load:disabled:hover { background: var(--primary); }
@media (max-width: 760px) { .share-row { grid-template-columns: auto minmax(0, 1fr); }
.share-row__actions { grid-column: 1 / -1; justify-content: flex-end; } }
@media (max-width: 520px) { .shareable-content__header { align-items: flex-start; flex-direction: column; gap: var(--space-sm); }
.shareable-content__toolbar { align-self: flex-start; }
.shareable-content__panel--empty { align-items: flex-start; flex-direction: column; }
.share-row { align-items: start; grid-template-columns: minmax(0, 1fr); }
.share-row__mark { width: 2.5rem; height: 2.5rem; padding: 0.25rem; }
.share-row__actions { justify-content: stretch; }
.share-row__preview, .share-row__share, .shareable-content__load { width: 100%; } }</style>