- merge.py: keep private activities in _merged/index.json instead of
stripping them; privacy filtering is now done client-side
- ActivityFeed: detect logged-in user via bincio:me event; show private
activities only when viewing your own profile; private cards get a lock
badge
This commit is contained in:
@@ -152,7 +152,6 @@ def merge_one(data_dir: Path, activity_id: str) -> None:
|
|||||||
s = _apply_sidecar_summary(s, fm)
|
s = _apply_sidecar_summary(s, fm)
|
||||||
activities.append(s)
|
activities.append(s)
|
||||||
|
|
||||||
activities = [a for a in activities if a.get("privacy") != "private"]
|
|
||||||
activities.sort(key=lambda a: a.get("started_at", ""), reverse=True)
|
activities.sort(key=lambda a: a.get("started_at", ""), reverse=True)
|
||||||
activities.sort(key=lambda a: 0 if a.get("custom", {}).get("highlight") else 1)
|
activities.sort(key=lambda a: 0 if a.get("custom", {}).get("highlight") else 1)
|
||||||
|
|
||||||
@@ -262,9 +261,9 @@ def merge_all(data_dir: Path) -> int:
|
|||||||
activities.append(s)
|
activities.append(s)
|
||||||
|
|
||||||
# Drop private activities from the published feed
|
# Drop private activities from the published feed
|
||||||
activities = [a for a in activities if a.get("privacy") != "private"]
|
|
||||||
|
|
||||||
# Sort: newest first, then bring highlighted activities to the top
|
# Sort: newest first, then bring highlighted activities to the top
|
||||||
|
# Private activities are kept in the index so the owner can see them;
|
||||||
|
# the feed UI filters them out for non-owners client-side.
|
||||||
activities.sort(key=lambda a: a.get("started_at", ""), reverse=True)
|
activities.sort(key=lambda a: a.get("started_at", ""), reverse=True)
|
||||||
activities.sort(key=lambda a: 0 if a.get("custom", {}).get("highlight") else 1)
|
activities.sort(key=lambda a: 0 if a.get("custom", {}).get("highlight") else 1)
|
||||||
|
|
||||||
|
|||||||
@@ -43,8 +43,20 @@
|
|||||||
let loading = true;
|
let loading = true;
|
||||||
let error = '';
|
let error = '';
|
||||||
let mounted = false;
|
let mounted = false;
|
||||||
|
/** Logged-in handle — resolved async via bincio:me event. */
|
||||||
|
let me: string = '';
|
||||||
|
|
||||||
$: filtered = sport === 'all' ? all : all.filter(a => a.sport === sport);
|
// Show private activities only to their owner.
|
||||||
|
// On a profile page (filterHandle set): show private if me === filterHandle.
|
||||||
|
// On the global feed: show private only for the logged-in user's own activities.
|
||||||
|
$: isOwner = filterHandle !== '' && me === filterHandle;
|
||||||
|
$: withPrivacy = all.filter(a => {
|
||||||
|
if (a.privacy === 'private') {
|
||||||
|
return filterHandle ? isOwner : (me !== '' && (a as any).handle === me);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
$: filtered = sport === 'all' ? withPrivacy : withPrivacy.filter(a => a.sport === sport);
|
||||||
$: visible = filtered.slice(0, shown);
|
$: visible = filtered.slice(0, shown);
|
||||||
$: hasMore = shown < filtered.length;
|
$: hasMore = shown < filtered.length;
|
||||||
|
|
||||||
@@ -60,18 +72,26 @@
|
|||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
sport = (new URLSearchParams(window.location.search).get('sport') as Sport | 'all') ?? 'all';
|
sport = (new URLSearchParams(window.location.search).get('sport') as Sport | 'all') ?? 'all';
|
||||||
mounted = true;
|
mounted = true;
|
||||||
|
|
||||||
|
// Resolve the logged-in handle so we can show the owner their private activities.
|
||||||
|
if ((window as any).__bincioMe !== undefined) {
|
||||||
|
me = (window as any).__bincioMe;
|
||||||
|
} else {
|
||||||
|
window.addEventListener('bincio:me', (e: Event) => { me = (e as CustomEvent).detail; }, { once: true });
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const indexUrl = profileIndexUrl
|
const indexUrl = profileIndexUrl
|
||||||
? `${base}data/${profileIndexUrl}`
|
? `${base}data/${profileIndexUrl}`
|
||||||
: `${base}data/index.json`;
|
: `${base}data/index.json`;
|
||||||
const index = await loadIndex(base, indexUrl);
|
const index = await loadIndex(base, indexUrl);
|
||||||
let activities = index.activities.filter(a => a.privacy !== 'private');
|
let activities = index.activities;
|
||||||
// filterHandle only applies when loading the root manifest (multi-user feed).
|
// filterHandle only applies when loading the root manifest (multi-user feed).
|
||||||
// When profileIndexUrl is set we already loaded the right user's shard directly —
|
// When profileIndexUrl is set we already loaded the right user's shard directly —
|
||||||
// activities from a direct shard fetch have no handle tag, so the filter would
|
// activities from a direct shard fetch have no handle tag, so the filter would
|
||||||
// remove everything.
|
// remove everything.
|
||||||
if (filterHandle && !profileIndexUrl) {
|
if (filterHandle && !profileIndexUrl) {
|
||||||
activities = activities.filter(a => a.handle === filterHandle);
|
activities = activities.filter(a => (a as any).handle === filterHandle);
|
||||||
}
|
}
|
||||||
all = activities;
|
all = activities;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@@ -140,10 +160,13 @@
|
|||||||
>@{a.handle}</a>{/if}
|
>@{a.handle}</a>{/if}
|
||||||
</p>
|
</p>
|
||||||
<!-- stretched link covers the whole card; sits below the handle link -->
|
<!-- stretched link covers the whole card; sits below the handle link -->
|
||||||
<h3 class="font-semibold text-white truncate group-hover:text-[--accent] transition-colors">
|
<h3 class="font-semibold text-white truncate group-hover:text-[--accent] transition-colors flex items-center gap-1.5">
|
||||||
|
{#if a.privacy === 'private'}
|
||||||
|
<span class="text-zinc-500 shrink-0" title="Private">🔒</span>
|
||||||
|
{/if}
|
||||||
<a
|
<a
|
||||||
href={a.detail_url ? `${import.meta.env.BASE_URL}activity/${a.id}/` : `${import.meta.env.BASE_URL}activity/local/?id=${a.id}`}
|
href={a.detail_url ? `${import.meta.env.BASE_URL}activity/${a.id}/` : `${import.meta.env.BASE_URL}activity/local/?id=${a.id}`}
|
||||||
class="before:absolute before:inset-0 before:content-['']"
|
class="before:absolute before:inset-0 before:content-[''] truncate"
|
||||||
>{a.title}</a>
|
>{a.title}</a>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user