1
Fork 0
mirror of https://github.com/thegeneralist01/archivr synced 2026-10-09 21:03:17 +02:00

feat(frontend): preview text-capture entries (.md / .txt)

Text captures land as `.md` (Markdown) or `.txt` (plain) blobs, but
PreviewPanel only dispatched on video/audio/image/pdf/html extensions,
so opening a text entry hit the 'No preview available' fallback with
the raw artifact path exposed.

- New `TextPreview` component fetches the primary artifact as text,
  renders it in a monospace `<pre>` with word-wrap, and shows the
  entry title on top and the MIME as a small trailing tag. Handles
  loading/error states.
- `PreviewPanel` gains a `TEXT_EXTS` set + a branch that dispatches
  to `TextPreview` for `md` / `markdown` / `txt`.
- CSS is padded and centered to ~780px so a text note reads like a
  document rather than an edge-to-edge terminal dump.

v1 intentionally does NOT parse Markdown: keeping frontend deps at
react+react-dom only. Bump to a real Markdown renderer if we start
capturing Markdown-authored notes.
This commit is contained in:
archivr-qa 2026-08-23 19:02:46 +02:00
parent 067d766819
commit e8bb03e08f
No known key found for this signature in database
6 changed files with 114 additions and 5 deletions

View file

@ -2,10 +2,12 @@ import VideoPreview from './VideoPreview';
import IframePreview from './IframePreview';
import ImagePreview from './ImagePreview';
import TweetPreview from './TweetPreview';
import TextPreview from './TextPreview';
const VIDEO_EXTS = new Set(['mp4', 'webm', 'mov', 'mkv', 'avi', 'm4v', 'ogv']);
const AUDIO_EXTS = new Set(['mp3', 'ogg', 'm4a', 'opus', 'wav', 'flac', 'aac']);
const IMAGE_EXTS = new Set(['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif', 'svg', 'bmp']);
const TEXT_EXTS = new Set(['md', 'markdown', 'txt']);
const CONTENT_TYPES = {
mp4: 'video/mp4', webm: 'video/webm', mov: 'video/quicktime',
mkv: 'video/x-matroska', avi: 'video/x-msvideo', m4v: 'video/mp4', ogv: 'video/ogg',
@ -131,7 +133,20 @@ export default function PreviewPanel({ archiveId, entry, detail, fullPage, onXAr
);
}
// 7. Image
// 6b. Plain text / Markdown
if (TEXT_EXTS.has(ext)) {
return (
<div className="preview-panel" style={{ flex: 1, minHeight: 0, overflow: 'auto' }}>
<TextPreview
src={primaryMediaUrl}
mime={primaryArtifact.mime_type}
title={summary.title}
/>
</div>
);
}
// 7. Image
if (IMAGE_EXTS.has(ext)) {
return (
<div className="preview-panel" style={{ height: '100%' }}>

View file

@ -0,0 +1,46 @@
import { useEffect, useState } from 'react'
/**
* Renders the primary_media artifact of a text/document entry as plain text.
*
* v1 intentionally does NOT render Markdown — we don't want a Markdown parser
* dep just to unblock the "no preview available" fallback, and monospace text
* with visible fences reads fine for the note-length content this feature
* captures. Bump to a real renderer if/when Markdown-authored entries grow.
*/
export default function TextPreview({ src, mime, title }) {
const [text, setText] = useState(null)
const [error, setError] = useState(null)
useEffect(() => {
let cancelled = false
setText(null)
setError(null)
fetch(src, { credentials: 'same-origin' })
.then(r => {
if (!r.ok) throw new Error(`HTTP ${r.status}`)
return r.text()
})
.then(t => { if (!cancelled) setText(t) })
.catch(e => { if (!cancelled) setError(e.message || String(e)) })
return () => { cancelled = true }
}, [src])
if (error) {
return (
<div className="text-preview text-preview--error">
Failed to load text: {error}
</div>
)
}
if (text === null) {
return <div className="text-preview text-preview--loading">Loading…</div>
}
return (
<div className="text-preview">
{title && <h1 className="text-preview__title">{title}</h1>}
<pre className="text-preview__body">{text}</pre>
{mime && <div className="text-preview__mime">{mime}</div>}
</div>
)
}