// Images and audio in notes. A block points to its file through its content: // image ![Beschriftung](media:med_…) // audio [Beschriftung](media:med_…) // so the reference travels with the normal note sync. Files are loaded with the login (fetch) and // shown as blob: URLs; there are no public links. import { getToken } from './api'; const MEDIA_RE = /^!?\[((?:[^\]\\]|\\.)*)\]\(media:(med_[0-9a-f]+)\)$/; export interface MediaRef { id: string; caption: string; } export function parseMediaContent(content: string): MediaRef | null { const m = MEDIA_RE.exec((content || '').trim()); if (!m) return null; return { id: m[2], caption: m[1].replace(/\\(.)/g, '$1') }; } export function mediaContent(kind: 'image' | 'audio', id: string, caption = ''): string { const safe = caption.replace(/[\r\n]+/g, ' ').replace(/([\\\]])/g, '\\$1'); return `${kind === 'image' ? '!' : ''}[${safe}](media:${id})`; } // Loaded files, once per page. The Blob is kept as well: the CSP does not allow fetch() on blob: URLs. const loaded = new Map>(); function loadMedia(id: string): Promise<{ blob: Blob; url: string }> { return loadAuthorized(`/api/v1/media/${encodeURIComponent(id)}`); } /** Any picture of the API (needs the login, so no plain ), as Blob plus blob: URL. */ export function loadAuthorized(path: string): Promise<{ blob: Blob; url: string }> { let p = loaded.get(path); if (!p) { p = (async () => { const token = getToken(); const res = await fetch(path, { headers: token ? { Authorization: `Bearer ${token}` } : {}, }); if (!res.ok) throw new Error(res.status === 404 ? 'Datei nicht gefunden' : `HTTP ${res.status}`); const blob = await res.blob(); return { blob, url: URL.createObjectURL(blob) }; })(); p.catch(() => loaded.delete(path)); // try again next time (e.g. was offline) loaded.set(path, p); } return p; } /** blob: URL of a file for /