integrate edit button into astro site

This commit is contained in:
Davide Scaini
2026-03-29 15:39:11 +02:00
parent 1d3848f85e
commit b0ab7fbe3f
6 changed files with 409 additions and 55 deletions
+59 -12
View File
@@ -1,17 +1,26 @@
<script lang="ts">
import { onMount } from 'svelte';
import { marked } from 'marked';
import type { ActivitySummary, ActivityDetail } from '../lib/types';
import { formatDistance, formatDuration, formatElevation, formatSpeed, formatDate, formatTime, sportIcon, sportLabel, sportColor } from '../lib/format';
import ActivityMap from './ActivityMap.svelte';
import ActivityCharts from './ActivityCharts.svelte';
import EditDrawer from './EditDrawer.svelte';
export let activity: ActivitySummary;
export let base: string = '/';
const editUrl = import.meta.env.PUBLIC_EDIT_URL;
let detail: ActivityDetail | null = null;
let error = '';
// Linked hover index shared between map and charts
let hoveredIdx: number | null = null;
let editOpen = false;
// Local overrides applied immediately after a save (no re-fetch needed)
let localTitle = '';
let localDescription = '';
$: displayTitle = localTitle || activity.title;
onMount(async () => {
if (!activity.detail_url) return;
@@ -24,22 +33,48 @@
}
});
function onSaved(e: CustomEvent<{ title: string; description: string }>) {
editOpen = false;
localTitle = e.detail.title;
localDescription = e.detail.description;
}
$: trackUrl = activity.track_url ? `${base}data/${activity.track_url}` : null;
$: color = sportColor(activity.sport);
const stat = (label: string, value: string) => ({ label, value });
$: rawDescription = localDescription || detail?.description || '';
$: descriptionHtml = (() => {
if (!rawDescription) return '';
const imageBase = `${base}data/activities/images/${activity.id}/`;
const renderer = new marked.Renderer();
renderer.image = ({ href, title, text }) => {
const src = href && !href.startsWith('http') && !href.startsWith('/') && !href.startsWith('data:')
? imageBase + href
: href ?? '';
const titleAttr = title ? ` title="${title}"` : '';
return `<img src="${src}" alt="${text}"${titleAttr} class="rounded-lg max-w-full my-2">`;
};
return marked(rawDescription, { renderer }) as string;
})();
const stat = (label: string, value: string, key?: string) => ({ label, value, key });
$: hiddenStats = new Set<string>((detail?.custom as any)?.hide_stats ?? []);
$: stats = [
stat('Distance', formatDistance(activity.distance_m)),
stat('Moving time', formatDuration(activity.moving_time_s ?? activity.duration_s)),
stat('Elevation ↑', formatElevation(activity.elevation_gain_m)),
stat('Avg speed', formatSpeed(activity.avg_speed_kmh)),
stat('Max speed', formatSpeed(activity.max_speed_kmh)),
stat('Avg HR', activity.avg_hr_bpm ? `${activity.avg_hr_bpm} bpm` : '—'),
stat('Max HR', activity.max_hr_bpm ? `${activity.max_hr_bpm} bpm` : '—'),
stat('Cadence', activity.avg_cadence_rpm ? `${activity.avg_cadence_rpm} rpm` : '—'),
];
stat('Elevation ↑', formatElevation(activity.elevation_gain_m), 'elevation'),
stat('Avg speed', formatSpeed(activity.avg_speed_kmh), 'speed'),
stat('Max speed', formatSpeed(activity.max_speed_kmh), 'speed'),
stat('Avg HR', activity.avg_hr_bpm ? `${activity.avg_hr_bpm} bpm` : '—', 'heart_rate'),
stat('Max HR', activity.max_hr_bpm ? `${activity.max_hr_bpm} bpm` : '—', 'heart_rate'),
stat('Cadence', activity.avg_cadence_rpm ? `${activity.avg_cadence_rpm} rpm` : '—', 'cadence'),
].filter(s => !s.key || !hiddenStats.has(s.key));
</script>
{#if editOpen && editUrl}
<EditDrawer activityId={activity.id} {editUrl} on:saved={onSaved} />
{/if}
<!-- Header -->
<div class="flex items-start gap-4 mb-6">
<a href={`${base}`} class="text-zinc-500 hover:text-white transition-colors mt-1 shrink-0">
@@ -57,9 +92,21 @@
{formatDate(activity.started_at)} · {formatTime(activity.started_at)}
</span>
</div>
<h1 class="text-2xl font-bold text-white">{activity.title}</h1>
{#if detail?.description}
<p class="text-zinc-400 mt-1 text-sm">{detail.description}</p>
<div class="flex items-center gap-3">
<h1 class="text-2xl font-bold text-white">{displayTitle}</h1>
{#if editUrl}
<button
class="text-xs px-2 py-0.5 rounded border border-zinc-700 text-zinc-400 hover:border-zinc-500 hover:text-white transition-colors shrink-0"
on:click={() => editOpen = true}
>
Edit
</button>
{/if}
</div>
{#if descriptionHtml}
<div class="text-zinc-400 mt-2 text-sm leading-relaxed [&_img]:rounded-lg [&_img]:my-2 [&_p]:my-1 [&_a]:text-blue-400">
{@html descriptionHtml}
</div>
{/if}
</div>
</div>