1
Fork 0
mirror of https://github.com/thegeneralist01/archivr synced 2026-07-22 03:05:32 +02:00

feat: entry previews — tweet/thread/article/video/audio/image/iframe/pdf

- PreviewModal: modal overlay with new-tab link (↗) and keyboard close
- PreviewPanel: routes by entity_kind + primary_media extension to the
  correct viewer (tweet/video/audio/pdf/html/image/fallback)
- TweetPreview: full X-style tweet, thread, and article renderer with
  local artifact map for archived media (CDN fallback)
- AudioBar: persistent fixed bottom player, triggered via ContextRail
  Play button; body.has-audio-bar pads content above it
- VideoPreview, IframePreview, ImagePreview: inline viewers
- PreviewPage: standalone /preview/:archiveId/:entryUid route
- ContextRail: Play/Preview buttons; isAudio/isPreviewable detection
- App.jsx: preview modal state, currentAudio state, preview route guard,
  has-audio-bar body class effect
- routes.rs: CSP updated (media-src self blob https; frame-ancestors self;
  Google Fonts + external images/scripts whitelisted)
- styles.css: preview modal, tweet-wrap scroll (min-height:0), audio bar
  body padding, newtab button, preview panel flex layout
This commit is contained in:
TheGeneralist 2026-07-11 19:45:13 +02:00
parent 5de9e291b0
commit 8755e2c503
Signed by: thegeneralist01
SSH key fingerprint: SHA256:pp9qddbCNmVNoSjevdvQvM5z0DHN7LTa8qBMbcMq/R4
14 changed files with 574 additions and 298 deletions

View file

@ -1906,29 +1906,11 @@ select {
ENTRY PREVIEWS
*/
/* ── 3-column layout when preview is active ─────────────────────── */
.app-shell.has-preview {
grid-template-columns: minmax(0, 1fr) 300px 340px;
}
.app-shell.audio-playing {
padding-bottom: 72px;
}
/* ── Preview pane ─────────────────────────────────────────────────── */
.preview-pane {
min-width: 0;
overflow: auto;
border-left: 1px solid var(--line);
background: var(--paper);
display: flex;
flex-direction: column;
}
.preview-panel {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
height: 100%;
}
.preview-panel-loading,
.preview-panel-empty {
@ -1966,7 +1948,6 @@ select {
display: flex;
flex-direction: column;
min-height: 0;
height: 100%;
}
.preview-iframe-toolbar {
display: flex;
@ -2029,8 +2010,9 @@ select {
/* ── Tweet preview wrap ───────────────────────────────────────────── */
.preview-tweet-wrap {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 20px 16px;
padding: 12px 16px;
background: var(--paper-2);
}
@ -2340,15 +2322,102 @@ select {
}
.audio-bar-close:hover { background: var(--field); color: var(--ink); }
/* ── Responsive: hide preview on small screens ─────────────────── */
@media (max-width: 1100px) {
.app-shell.has-preview {
grid-template-columns: minmax(0, 1fr) 340px;
}
.preview-pane { display: none; }
/* ── Preview modal ────────────────────────────────────────────────── */
.preview-modal-backdrop {
position: fixed;
inset: 0;
z-index: 200;
background: rgba(0, 0, 0, 0.55);
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
@media (max-width: 900px) {
.app-shell.has-preview {
grid-template-columns: 1fr;
}
.preview-modal {
background: var(--paper);
border-radius: 12px;
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.28);
width: 90vw;
max-width: 1100px;
height: 88vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
.preview-modal-header {
display: flex;
align-items: center;
padding: 14px 18px;
border-bottom: 1px solid var(--line);
flex-shrink: 0;
gap: 12px;
}
.preview-modal-title {
flex: 1;
min-width: 0;
font-size: 14px;
font-weight: 600;
color: var(--ink);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.preview-modal-close {
width: 30px;
height: 30px;
border-radius: 50%;
border: none;
background: transparent;
color: var(--muted-2);
font-size: 20px;
line-height: 1;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: background 0.12s, color 0.12s;
}
.preview-modal-close:hover { background: var(--field); color: var(--ink); }
.preview-modal-body {
flex: 1;
min-height: 0;
overflow: auto;
display: flex;
flex-direction: column;
}
/* Preview button in context rail */
.rail-preview-btn {
display: block;
width: 100%;
padding: 8px 12px;
margin-bottom: 16px;
background: var(--accent);
color: var(--paper);
border: none;
border-radius: var(--r3);
font-size: 13px;
font-weight: 600;
cursor: pointer;
text-align: center;
transition: background 0.12s;
}
.rail-preview-btn:hover { background: var(--accent-2); }
/* Open-in-new-tab button in preview modal header */
.preview-modal-newtab {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: var(--r2);
color: var(--muted-2);
text-decoration: none;
font-size: 16px;
flex-shrink: 0;
transition: background 0.12s, color 0.12s;
}
.preview-modal-newtab:hover { background: var(--field); color: var(--ink); }
/* Push content above the fixed AudioBar when it is visible */
body.has-audio-bar { padding-bottom: 56px; }