Files
meetus/ui/doocus-app/src/components/DocDetail.vue
Mariano Gabriel 8606520ef2 doocus improvemnts
2026-07-05 20:35:44 -03:00

94 lines
4.0 KiB
Vue

<script setup lang="ts">
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)
// 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 extractedHtml = computed(() => (d.value?.content ? marked.parse(d.value.content) : ''))
const metaRows = computed(() => {
const m = d.value?.meta as Record<string, any> | null
if (!m) return []
const out: Array<[string, string]> = []
const push = (k: string, v: unknown) => {
if (v == null || v === '') return
out.push([k, Array.isArray(v) ? v.join(', ') : typeof v === 'object' ? JSON.stringify(v) : String(v)])
}
push('title', m.title); push('author', m.author); push('created', m.created)
push('pages', m.pages); push('slides', m.slides); push('sheets', m.sheets)
push('words', m.word_count); push('extractor', m.extractor)
return out
})
</script>
<template>
<div v-if="!node" class="empty">Select a file to view it.</div>
<div v-else class="detail">
<!-- meetings get no selection header the viewer is the meeting itself -->
<header v-if="node.mode !== 'meeting'" class="head">
<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>
</header>
<!-- extracted: search text (left, secondary) | native original (right) -->
<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>
<div class="md" v-html="extractedHtml" />
</div>
</template>
<template #second>
<NativeViewer :node="node" :original-url="d!.originalUrl" :content="d?.content" :text="d?.text" />
</template>
</SplitPane>
<!-- link / pptx / meeting: single native viewer -->
<div v-else class="body single">
<NativeViewer :node="node" :original-url="d!.originalUrl" :content="d?.content" :text="d?.text" />
</div>
<footer v-if="metaRows.length" class="meta">
<span v-for="[k, v] in metaRows" :key="k" class="chip"><b>{{ k }}</b> {{ v }}</span>
</footer>
</div>
</template>
<style scoped>
.empty { padding: var(--space-4); color: var(--text-secondary); }
.detail { display: flex; flex-direction: column; height: 100%; }
.head {
display: flex; align-items: center; gap: var(--space-3);
padding: var(--space-2) var(--space-3); border-bottom: var(--panel-border); flex-shrink: 0;
}
.head .name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; }
.head .dim, .dim { color: var(--text-secondary); font-size: var(--font-size-sm); }
.badge {
font-size: 10px; text-transform: uppercase; padding: 1px 5px; border-radius: 3px;
background: var(--surface-3); color: var(--text-secondary);
}
.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: 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; }
.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); }
</style>