diff --git a/src/lib/ae_events/ae_events__event_file.ts b/src/lib/ae_events/ae_events__event_file.ts index 28cc6919..3fc0ed15 100644 --- a/src/lib/ae_events/ae_events__event_file.ts +++ b/src/lib/ae_events/ae_events__event_file.ts @@ -365,21 +365,39 @@ export async function create_event_file_obj_from_hosted_file_async({ export async function delete_ae_obj_id__event_file({ api_cfg, event_file_id, + hosted_file_id, params = {}, try_cache = true, log_lvl = 0 }: { api_cfg: any; event_file_id: string; + // Providing hosted_file_id enables full cleanup: removes the hosted_file_link, + // then deletes the hosted_file record and physical file if no other links remain. + // Must be done BEFORE deleting the event_file record so the backend can still + // resolve the link_to_id via Redis. + hosted_file_id?: string; params?: key_val; try_cache?: boolean; log_lvl?: number; }) { + if (hosted_file_id) { + await api.delete_hosted_file({ + api_cfg, + hosted_file_id, + link_to_type: 'event_file', + link_to_id: event_file_id, + rm_orphan: true, + params: { method: 'delete' }, + log_lvl + }); + } + const result = await api.delete_ae_obj({ api_cfg, obj_type: 'event_file', obj_id: event_file_id, - params: { ...params, delete_hosted_file: true, rm_orphan: true }, + params, log_lvl }); if (try_cache) await db_events.file.delete(event_file_id); diff --git a/src/lib/elements/element_manage_event_file_li.svelte b/src/lib/elements/element_manage_event_file_li.svelte index f4c2b8ff..e6f673a5 100644 --- a/src/lib/elements/element_manage_event_file_li.svelte +++ b/src/lib/elements/element_manage_event_file_li.svelte @@ -570,6 +570,8 @@ async function handle_convert_pdf_to_image(event_file_obj: key_val) { api_cfg: $ae_api, event_file_id: event_file_obj.event_file_id, + hosted_file_id: + event_file_obj.hosted_file_id, log_lvl: 1 } ); diff --git a/src/routes/core/files/+page.svelte b/src/routes/core/files/+page.svelte index e5c78ecc..b40c7c1e 100644 --- a/src/routes/core/files/+page.svelte +++ b/src/routes/core/files/+page.svelte @@ -235,6 +235,9 @@ async function copy_hash(hash: string) { // Keyed by hosted_file_id. Value: null = not loaded, [] = loaded/empty, [...] = loaded with links. let links_map = new SvelteMap(); let links_loading = new SvelteMap(); +// Resolved navigation URLs per link — keyed as `${link_to_type}:${link_to_id_random}`. +// Null means no routable page exists for that type. +let link_url_map = new SvelteMap(); async function toggle_links(file: ae_HostedFile) { const id = file.hosted_file_id; @@ -249,7 +252,10 @@ async function toggle_links(file: ae_HostedFile) { endpoint: `/v3/action/hosted_file/${id}/links`, log_lvl: 0 }); - links_map.set(id, result?.data ?? result ?? []); + const file_links = result?.data ?? result ?? []; + links_map.set(id, file_links); + // Resolve navigation URLs for each link in parallel. + await Promise.all(file_links.map((lnk: any) => resolve_link_url(lnk.link_to_type, lnk.link_to_id_random))); } catch (e) { console.error('[hosted_file links]', e); links_map.set(id, []); @@ -258,6 +264,58 @@ async function toggle_links(file: ae_HostedFile) { } } +// Resolves a link record to a navigable app URL. Caches results to avoid repeat calls. +// For nested types (session, presenter, location, journal_entry, etc.) we fetch the +// object to get its parent ID, then construct the correct route. +async function resolve_link_url(link_to_type: string, id_random: string | null): Promise { + if (!id_random) return; + const cache_key = `${link_to_type}:${id_random}`; + if (link_url_map.has(cache_key)) return; + + // Direct types — no parent lookup needed. + const direct: Record = { + event: `/events/${id_random}`, + journal: `/journals/${id_random}`, + archive: `/idaa/archives/${id_random}`, + post: `/idaa/bb/${id_random}` + }; + if (direct[link_to_type]) { + link_url_map.set(cache_key, direct[link_to_type]); + return; + } + + // Nested types — look up parent ID from the object, then build URL. + try { + const result = await api.get_object({ + api_cfg: $ae_api, + endpoint: `/v3/crud/${link_to_type}/${id_random}`, + log_lvl: 0 + }); + const obj = result?.data ?? result; + let url: string | null = null; + + if (link_to_type === 'event_session' && obj?.event_id) { + url = `/events/${obj.event_id}/session/${id_random}`; + } else if (link_to_type === 'event_location' && obj?.event_id) { + url = `/events/${obj.event_id}/location/${id_random}`; + } else if (link_to_type === 'event_presenter' && obj?.event_id) { + url = `/events/${obj.event_id}/presenter/${id_random}`; + } else if (link_to_type === 'journal_entry' && obj?.journal_id) { + url = `/journals/${obj.journal_id}/entry/${id_random}`; + } else if (link_to_type === 'archive_content' && obj?.archive_id) { + // No dedicated content detail page — link to the parent archive. + url = `/idaa/archives/${obj.archive_id}`; + } else if (link_to_type === 'post_comment' && obj?.post_id) { + // No dedicated comment detail page — link to the parent post. + url = `/idaa/bb/${obj.post_id}`; + } + + link_url_map.set(cache_key, url); + } catch { + link_url_map.set(cache_key, null); + } +} + // ── Helpers ─────────────────────────────────────────────────────────────────── function fmt_size(bytes: number | undefined): string { if (!bytes) return '—'; @@ -587,14 +645,29 @@ let total_size = $derived(results.reduce((sum, f) => sum + (f.size ?? 0), 0));
Linked to: {#each file_links as lnk} - - {lnk.link_to_type} - - / {lnk.link_to_id_random ? lnk.link_to_id_random.slice(0, 8) + '…' : lnk.link_to_id} + {@const link_key = `${lnk.link_to_type}:${lnk.link_to_id_random}`} + {@const link_url = link_url_map.get(link_key)} + {@const id_display = lnk.link_to_id_random ? lnk.link_to_id_random.slice(0, 8) + '…' : String(lnk.link_to_id)} + {@const full_id = lnk.link_to_id_random ?? String(lnk.link_to_id)} + {#if link_url} + + {lnk.link_to_type} + / {id_display} + + + {:else} + + {lnk.link_to_type} + / {id_display} - + {/if} {/each}
{/if}