viewers
This commit is contained in:
@@ -2,35 +2,18 @@
|
||||
import { computed } from 'vue'
|
||||
import { marked } from 'marked'
|
||||
import SplitPane from '@framework/components/SplitPane.vue'
|
||||
import NativeViewer from '@/components/NativeViewer.vue'
|
||||
import { store, humanBytes } from '@/store'
|
||||
|
||||
const d = computed(() => store.detail)
|
||||
const node = computed(() => d.value?.node)
|
||||
|
||||
type ViewerKind = 'pdf' | 'image' | 'html' | 'markdown' | 'text' | 'office' | 'video' | 'download'
|
||||
// Left pane (extracted search text) only exists for extracted heavy formats,
|
||||
// and not for pptx (whose viewer already shows the per-slide text).
|
||||
const hasExtracted = computed(() =>
|
||||
node.value?.mode === 'extracted' && node.value.ext !== 'pptx' && d.value?.content != null)
|
||||
|
||||
const viewerKind = computed<ViewerKind>(() => {
|
||||
const ext = node.value?.ext ?? ''
|
||||
if (ext === 'pdf') return 'pdf'
|
||||
if (['png', 'jpg', 'jpeg'].includes(ext)) return 'image'
|
||||
if (['html', 'htm'].includes(ext)) return 'html'
|
||||
if (['md', 'markdown'].includes(ext)) return 'markdown'
|
||||
if (['txt', 'json', 'yaml', 'yml', 'csv'].includes(ext)) return 'text'
|
||||
if (['docx', 'pptx', 'xlsx'].includes(ext)) return 'office'
|
||||
if (ext === 'mp4') return 'video'
|
||||
return 'download'
|
||||
})
|
||||
|
||||
// Left pane (extracted search text) only exists for extracted heavy formats.
|
||||
const hasExtracted = computed(() => node.value?.mode === 'extracted' && d.value?.content != null)
|
||||
|
||||
const extractedHtml = computed(() =>
|
||||
d.value?.content ? marked.parse(d.value.content) : '')
|
||||
|
||||
// Right pane text (for text-native originals rendered inline).
|
||||
const inlineText = computed(() => d.value?.text ?? '')
|
||||
const inlineMarkdown = computed(() =>
|
||||
viewerKind.value === 'markdown' && inlineText.value ? marked.parse(inlineText.value) : '')
|
||||
const extractedHtml = computed(() => (d.value?.content ? marked.parse(d.value.content) : ''))
|
||||
|
||||
const metaRows = computed(() => {
|
||||
const m = d.value?.meta as Record<string, any> | null
|
||||
@@ -54,11 +37,10 @@ const metaRows = computed(() => {
|
||||
<span class="badge" :data-mode="node.mode">{{ node.mode }}</span>
|
||||
<span class="name">{{ node.path }}</span>
|
||||
<span class="dim">{{ node.ext }} · {{ humanBytes(node.bytes) }}</span>
|
||||
<a class="dl" :href="d?.originalUrl" :download="node.name">↓ original</a>
|
||||
</header>
|
||||
|
||||
<!-- extracted: search text (left, secondary) | native original (right) -->
|
||||
<SplitPane v-if="hasExtracted" class="body" :initial-size="1" :min="0.3" :max="3">
|
||||
<SplitPane v-if="hasExtracted" class="body" :initial-size="1" :min="0.25" :max="3">
|
||||
<template #first>
|
||||
<div class="pane">
|
||||
<div class="pane-label">Extracted text · search index (not the document)</div>
|
||||
@@ -66,35 +48,13 @@ const metaRows = computed(() => {
|
||||
</div>
|
||||
</template>
|
||||
<template #second>
|
||||
<div class="pane viewer">
|
||||
<template v-if="viewerKind === 'pdf'">
|
||||
<iframe class="frame" :src="d?.originalUrl" title="pdf" />
|
||||
</template>
|
||||
<div v-else class="office">
|
||||
<p>No inline preview for <b>.{{ node.ext }}</b>.</p>
|
||||
<a class="btn" :href="d?.originalUrl" :download="node.name">Download original</a>
|
||||
<p class="dim">The extracted text on the left is a search aid, not a faithful render.</p>
|
||||
</div>
|
||||
</div>
|
||||
<NativeViewer :node="node" :original-url="d!.originalUrl" :content="d?.content" :text="d?.text" />
|
||||
</template>
|
||||
</SplitPane>
|
||||
|
||||
<!-- non-extracted: single native viewer of the original -->
|
||||
<!-- link / pptx / meeting: single native viewer -->
|
||||
<div v-else class="body single">
|
||||
<div v-if="viewerKind === 'image'" class="viewer center">
|
||||
<img :src="d?.originalUrl" :alt="node.name" />
|
||||
</div>
|
||||
<iframe v-else-if="viewerKind === 'html'" class="frame" :src="d?.originalUrl" sandbox="" title="html" />
|
||||
<div v-else-if="viewerKind === 'markdown'" class="md pad" v-html="inlineMarkdown" />
|
||||
<pre v-else-if="viewerKind === 'text'" class="mono">{{ inlineText }}</pre>
|
||||
<div v-else-if="viewerKind === 'video'" class="viewer center meeting">
|
||||
<video class="video" :src="d?.originalUrl" controls />
|
||||
<p class="dim">Meeting — its transcript is produced by meetus, not doocus.</p>
|
||||
</div>
|
||||
<div v-else class="office">
|
||||
<p>No inline preview for <b>.{{ node.ext }}</b>.</p>
|
||||
<a class="btn" :href="d?.originalUrl" :download="node.name">Download original</a>
|
||||
</div>
|
||||
<NativeViewer :node="node" :original-url="d!.originalUrl" :content="d?.content" :text="d?.text" />
|
||||
</div>
|
||||
|
||||
<footer v-if="metaRows.length" class="meta">
|
||||
@@ -112,7 +72,6 @@ const metaRows = computed(() => {
|
||||
}
|
||||
.head .name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; }
|
||||
.head .dim, .dim { color: var(--text-secondary); font-size: var(--font-size-sm); }
|
||||
.head .dl { margin-left: auto; }
|
||||
.badge {
|
||||
font-size: 10px; text-transform: uppercase; padding: 1px 5px; border-radius: 3px;
|
||||
background: var(--surface-3); color: var(--text-secondary);
|
||||
@@ -120,22 +79,13 @@ const metaRows = computed(() => {
|
||||
.badge[data-mode='extracted'] { background: var(--status-live, #2a6); color: #fff; }
|
||||
.badge[data-mode='meeting'] { background: var(--status-processing, #a60); color: #fff; }
|
||||
.body { flex: 1; min-height: 0; }
|
||||
.body.single { overflow: auto; }
|
||||
.body.single { overflow: hidden; }
|
||||
.pane { height: 100%; display: flex; flex-direction: column; overflow: hidden; }
|
||||
.pane-label {
|
||||
font-size: 11px; color: var(--text-secondary); padding: var(--space-1) var(--space-3);
|
||||
border-bottom: 1px solid var(--surface-2); flex-shrink: 0;
|
||||
}
|
||||
.md { padding: var(--space-3); overflow: auto; }
|
||||
.md.pad { padding: var(--space-4); }
|
||||
.viewer { height: 100%; overflow: auto; }
|
||||
.viewer.center { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-2); padding: var(--space-3); }
|
||||
.frame { width: 100%; height: 100%; border: 0; }
|
||||
.viewer img { max-width: 100%; }
|
||||
.video { max-width: 100%; max-height: 70vh; }
|
||||
.mono { margin: 0; padding: var(--space-4); white-space: pre-wrap; overflow-wrap: anywhere; font-family: var(--font-mono, monospace); font-size: var(--font-size-sm); }
|
||||
.office { padding: var(--space-4); display: flex; flex-direction: column; gap: var(--space-2); align-items: flex-start; }
|
||||
.btn { padding: var(--space-1) var(--space-3); background: var(--surface-2); border: var(--panel-border); border-radius: var(--panel-radius); }
|
||||
.meta { display: flex; flex-wrap: wrap; gap: var(--space-2); padding: var(--space-2) var(--space-3); border-top: var(--panel-border); flex-shrink: 0; }
|
||||
.chip { font-size: var(--font-size-sm); color: var(--text-secondary); }
|
||||
.chip b { color: var(--text-primary); }
|
||||
|
||||
Reference in New Issue
Block a user