feat: improve chunk viewer with fixed navigation and markdown rendering

This commit implements three UI improvements for the chunk viewer:

1. Fixed modal footer with navigation controls
   - Moved PDF navigation buttons to a fixed footer
   - Footer remains visible while scrolling content
   - Three-section layout: fixed header, scrollable body, fixed footer

2. Removed duplicate navigation controls
   - Removed previous/next buttons from PDFViewer component
   - Controls now only in App.vue modal footer
   - Cleaned up unused imports and CSS

3. Markdown rendering for chunk content
   - Created MarkdownViewer component using markdown-it
   - Renders markdown content aligned with Nextcloud design system
   - Removed problematic markdown-it-task-checkbox dependency
   - Combines before/chunk/after context with visual separators

4. Cleaned up search results display
   - Removed excerpt snippets from results list
   - Kept only chunk/page metadata for cleaner UI

The modal structure now has:
- Fixed header (title + close button)
- Scrollable body (PDF canvas or markdown content)
- Fixed footer (page navigation - always visible)

Fixes markdown rendering "require is not defined" error by using
only markdown-it without CommonJS plugins.
This commit is contained in:
Chris Coutinho
2025-12-18 00:02:08 +01:00
parent 04c64e97b0
commit b246a03ac4
5 changed files with 319 additions and 92 deletions
+84 -36
View File
@@ -199,10 +199,6 @@
· {{ t('astroglobe', 'Page {page}/{total}', { page: result.page_number, total: result.page_count }) }}
</span>
</div>
<div
class="mcp-result-excerpt">
{{ result.excerpt }}
</div>
</div>
</div>
</div>
@@ -296,6 +292,7 @@
<!-- PDF/Chunk Viewer Modal -->
<div v-if="showViewer" class="mcp-modal-overlay" @click.self="closeViewer">
<div class="mcp-modal">
<!-- Fixed Header -->
<div class="mcp-modal-header">
<h3>{{ viewerTitle }}</h3>
<NcButton type="tertiary" @click="closeViewer">
@@ -304,6 +301,8 @@
</template>
</NcButton>
</div>
<!-- Scrollable Content -->
<div class="mcp-modal-body">
<!-- Loading State -->
<div v-if="viewerLoading" class="mcp-viewer-loading">
@@ -311,27 +310,43 @@
<span>{{ t('astroglobe', 'Loading content...') }}</span>
</div>
<!-- PDF Viewer -->
<!-- PDF Viewer (canvas only, controls in footer) -->
<PDFViewer
v-else-if="viewerType === 'pdf'"
:file-path="currentPdfPath"
:page-number="viewerPage"
@prev-page="viewerPage--"
@next-page="viewerPage++"
@loaded="handlePdfLoaded"
@error="handlePdfError" />
<!-- Text Viewer (for non-PDFs) -->
<div v-else class="mcp-text-viewer">
<div v-if="viewerContext.before" class="mcp-context-text">
{{ viewerContext.before }}
</div>
<div class="mcp-highlighted-chunk">
{{ viewerContext.chunk }}
</div>
<div v-if="viewerContext.after" class="mcp-context-text">
{{ viewerContext.after }}
</div>
</div>
<!-- Markdown Viewer (for non-PDFs) -->
<MarkdownViewer
v-else
:content="getMarkdownContent()" />
</div>
<!-- Fixed Footer (navigation controls) -->
<div v-if="!viewerLoading && viewerType === 'pdf' && pdfTotalPages > 0" class="mcp-modal-footer">
<NcButton
:disabled="viewerPage <= 1"
@click="viewerPage--">
<template #icon>
<ChevronLeft :size="20" />
</template>
{{ t('astroglobe', 'Previous') }}
</NcButton>
<span class="mcp-page-info">
{{ t('astroglobe', 'Page {current} of {total}', { current: viewerPage, total: pdfTotalPages }) }}
</span>
<NcButton
:disabled="viewerPage >= pdfTotalPages"
@click="viewerPage++">
<template #icon>
<ChevronRight :size="20" />
</template>
{{ t('astroglobe', 'Next') }}
</NcButton>
</div>
</div>
</div>
@@ -356,12 +371,15 @@ import ChartBox from 'vue-material-design-icons/ChartBox.vue'
import Cog from 'vue-material-design-icons/Cog.vue'
import ChevronDown from 'vue-material-design-icons/ChevronDown.vue'
import ChevronUp from 'vue-material-design-icons/ChevronUp.vue'
import ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue'
import ChevronRight from 'vue-material-design-icons/ChevronRight.vue'
import Refresh from 'vue-material-design-icons/Refresh.vue'
import OpenInNew from 'vue-material-design-icons/OpenInNew.vue'
import Eye from 'vue-material-design-icons/Eye.vue'
import Close from 'vue-material-design-icons/Close.vue'
import PDFViewer from './components/PDFViewer.vue'
import MarkdownViewer from './components/MarkdownViewer.vue'
import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
@@ -394,11 +412,14 @@ export default {
NcEmptyContent,
NcCheckboxRadioSwitch,
PDFViewer,
MarkdownViewer,
Magnify,
ChartBox,
Cog,
ChevronDown,
ChevronUp,
ChevronLeft,
ChevronRight,
Refresh,
OpenInNew,
Eye,
@@ -434,6 +455,7 @@ export default {
viewerTitle: '',
viewerType: 'text',
viewerPage: 1,
pdfTotalPages: 0,
currentPdfPath: '',
viewerContext: {
chunk: '',
@@ -776,8 +798,35 @@ export default {
this.viewerType = 'text'
},
handlePdfLoaded(event) {
this.pdfTotalPages = event.totalPages || 0
},
getMarkdownContent() {
// Combine before/chunk/after context into single markdown string
let content = ''
if (this.viewerContext.before) {
content += this.viewerContext.before + '\n\n'
}
if (this.viewerContext.chunk) {
// Highlight the main chunk with a separator
content += '---\n\n'
content += this.viewerContext.chunk
content += '\n\n---'
}
if (this.viewerContext.after) {
content += '\n\n' + this.viewerContext.after
}
return content
},
closeViewer() {
this.showViewer = false
this.pdfTotalPages = 0
},
},
}
@@ -1031,25 +1080,6 @@ a.mcp-result-title {
line-height: 1.4;
}
.mcp-result-excerpt {
font-size: 13px;
color: var(--color-text-maxcontrast);
line-height: 1.5;
white-space: pre-wrap;
word-wrap: break-word;
&--expanded {
display: block;
-webkit-line-clamp: unset;
background: var(--color-background-dark);
padding: 12px;
border-radius: var(--border-radius);
margin-top: 8px;
white-space: pre-wrap;
word-break: break-word;
}
}
.mcp-result-actions {
display: flex;
align-items: center;
@@ -1159,6 +1189,24 @@ a.mcp-result-title {
position: relative;
}
.mcp-modal-footer {
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
padding: 16px 20px;
border-top: 1px solid var(--color-border);
background: var(--color-main-background);
flex-shrink: 0;
.mcp-page-info {
font-size: 14px;
color: var(--color-text-maxcontrast);
min-width: 150px;
text-align: center;
}
}
.mcp-viewer-loading {
display: flex;
flex-direction: column;