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

Merge branch 'feat/archive-text-with-title' into integration-all-three

# Conflicts:
#	crates/archivr-server/static/assets/index-C4rpqY7g.js
#	crates/archivr-server/static/index.html
This commit is contained in:
archivr-qa 2026-08-23 19:03:25 +02:00
commit 73b0010d60
No known key found for this signature in database
6 changed files with 112 additions and 51 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>
)
}

View file

@ -3285,3 +3285,51 @@ body.has-audio-bar { padding-bottom: 56px; }
border: 1px solid var(--line);
border-radius: 4px;
}
/* ── Text / Markdown preview ────────────────────────────────────────────── */
.text-preview {
padding: 32px 40px 48px;
max-width: 780px;
margin: 0 auto;
font-family: var(--sans);
color: var(--ink);
overflow: auto;
height: 100%;
box-sizing: border-box;
}
.text-preview--loading,
.text-preview--error {
padding: 24px;
color: var(--muted);
font-size: 13px;
}
.text-preview--error { color: var(--accent); }
.text-preview__title {
font-family: var(--serif, var(--sans));
font-size: 22px;
font-weight: 600;
margin: 0 0 20px;
line-height: 1.25;
color: var(--ink);
}
.text-preview__body {
white-space: pre-wrap;
overflow-wrap: anywhere;
word-break: break-word;
font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
font-size: 13.5px;
line-height: 1.65;
color: var(--ink);
background: var(--paper);
border: 1px solid var(--line);
border-radius: var(--r2);
padding: 16px 18px;
margin: 0;
}
.text-preview__mime {
margin-top: 12px;
font-size: 11px;
color: var(--muted);
letter-spacing: .04em;
text-align: right;
}