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:
+49
-4
@@ -13,6 +13,7 @@
|
|||||||
"@nextcloud/l10n": "^3.1.0",
|
"@nextcloud/l10n": "^3.1.0",
|
||||||
"@nextcloud/router": "^3.0.1",
|
"@nextcloud/router": "^3.0.1",
|
||||||
"@nextcloud/vue": "^8.29.2",
|
"@nextcloud/vue": "^8.29.2",
|
||||||
|
"markdown-it": "^14.1.0",
|
||||||
"pdfjs-dist": "^4.0.379",
|
"pdfjs-dist": "^4.0.379",
|
||||||
"plotly.js-dist-min": "^2.35.3",
|
"plotly.js-dist-min": "^2.35.3",
|
||||||
"vue": "^2.7.16",
|
"vue": "^2.7.16",
|
||||||
@@ -4113,9 +4114,7 @@
|
|||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||||
"dev": true,
|
"license": "Python-2.0"
|
||||||
"license": "Python-2.0",
|
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/array-buffer-byte-length": {
|
"node_modules/array-buffer-byte-length": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
@@ -5722,7 +5721,6 @@
|
|||||||
"version": "4.5.0",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.12"
|
"node": ">=0.12"
|
||||||
@@ -8622,6 +8620,15 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
|
"node_modules/linkify-it": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"uc.micro": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/linkify-string": {
|
"node_modules/linkify-string": {
|
||||||
"version": "4.3.2",
|
"version": "4.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/linkify-string/-/linkify-string-4.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/linkify-string/-/linkify-string-4.3.2.tgz",
|
||||||
@@ -8737,6 +8744,23 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/markdown-it": {
|
||||||
|
"version": "14.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
|
||||||
|
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"argparse": "^2.0.1",
|
||||||
|
"entities": "^4.4.0",
|
||||||
|
"linkify-it": "^5.0.0",
|
||||||
|
"mdurl": "^2.0.0",
|
||||||
|
"punycode.js": "^2.3.1",
|
||||||
|
"uc.micro": "^2.1.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"markdown-it": "bin/markdown-it.mjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/material-colors": {
|
"node_modules/material-colors": {
|
||||||
"version": "1.2.6",
|
"version": "1.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz",
|
||||||
@@ -8909,6 +8933,12 @@
|
|||||||
"license": "CC0-1.0",
|
"license": "CC0-1.0",
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
|
"node_modules/mdurl": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/meow": {
|
"node_modules/meow": {
|
||||||
"version": "13.2.0",
|
"version": "13.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz",
|
||||||
@@ -10414,6 +10444,15 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/punycode.js": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/qified": {
|
"node_modules/qified": {
|
||||||
"version": "0.5.3",
|
"version": "0.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/qified/-/qified-0.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/qified/-/qified-0.5.3.tgz",
|
||||||
@@ -12669,6 +12708,12 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/uc.micro": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ufo": {
|
"node_modules/ufo": {
|
||||||
"version": "1.6.1",
|
"version": "1.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz",
|
||||||
|
|||||||
Vendored
+1
@@ -22,6 +22,7 @@
|
|||||||
"@nextcloud/l10n": "^3.1.0",
|
"@nextcloud/l10n": "^3.1.0",
|
||||||
"@nextcloud/router": "^3.0.1",
|
"@nextcloud/router": "^3.0.1",
|
||||||
"@nextcloud/vue": "^8.29.2",
|
"@nextcloud/vue": "^8.29.2",
|
||||||
|
"markdown-it": "^14.1.0",
|
||||||
"plotly.js-dist-min": "^2.35.3",
|
"plotly.js-dist-min": "^2.35.3",
|
||||||
"pdfjs-dist": "^4.0.379",
|
"pdfjs-dist": "^4.0.379",
|
||||||
"vue": "^2.7.16",
|
"vue": "^2.7.16",
|
||||||
|
|||||||
Vendored
+84
-36
@@ -199,10 +199,6 @@
|
|||||||
· {{ t('astroglobe', 'Page {page}/{total}', { page: result.page_number, total: result.page_count }) }}
|
· {{ t('astroglobe', 'Page {page}/{total}', { page: result.page_number, total: result.page_count }) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="mcp-result-excerpt">
|
|
||||||
{{ result.excerpt }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -296,6 +292,7 @@
|
|||||||
<!-- PDF/Chunk Viewer Modal -->
|
<!-- PDF/Chunk Viewer Modal -->
|
||||||
<div v-if="showViewer" class="mcp-modal-overlay" @click.self="closeViewer">
|
<div v-if="showViewer" class="mcp-modal-overlay" @click.self="closeViewer">
|
||||||
<div class="mcp-modal">
|
<div class="mcp-modal">
|
||||||
|
<!-- Fixed Header -->
|
||||||
<div class="mcp-modal-header">
|
<div class="mcp-modal-header">
|
||||||
<h3>{{ viewerTitle }}</h3>
|
<h3>{{ viewerTitle }}</h3>
|
||||||
<NcButton type="tertiary" @click="closeViewer">
|
<NcButton type="tertiary" @click="closeViewer">
|
||||||
@@ -304,6 +301,8 @@
|
|||||||
</template>
|
</template>
|
||||||
</NcButton>
|
</NcButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Scrollable Content -->
|
||||||
<div class="mcp-modal-body">
|
<div class="mcp-modal-body">
|
||||||
<!-- Loading State -->
|
<!-- Loading State -->
|
||||||
<div v-if="viewerLoading" class="mcp-viewer-loading">
|
<div v-if="viewerLoading" class="mcp-viewer-loading">
|
||||||
@@ -311,27 +310,43 @@
|
|||||||
<span>{{ t('astroglobe', 'Loading content...') }}</span>
|
<span>{{ t('astroglobe', 'Loading content...') }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- PDF Viewer -->
|
<!-- PDF Viewer (canvas only, controls in footer) -->
|
||||||
<PDFViewer
|
<PDFViewer
|
||||||
v-else-if="viewerType === 'pdf'"
|
v-else-if="viewerType === 'pdf'"
|
||||||
:file-path="currentPdfPath"
|
:file-path="currentPdfPath"
|
||||||
:page-number="viewerPage"
|
:page-number="viewerPage"
|
||||||
@prev-page="viewerPage--"
|
@prev-page="viewerPage--"
|
||||||
@next-page="viewerPage++"
|
@next-page="viewerPage++"
|
||||||
|
@loaded="handlePdfLoaded"
|
||||||
@error="handlePdfError" />
|
@error="handlePdfError" />
|
||||||
|
|
||||||
<!-- Text Viewer (for non-PDFs) -->
|
<!-- Markdown Viewer (for non-PDFs) -->
|
||||||
<div v-else class="mcp-text-viewer">
|
<MarkdownViewer
|
||||||
<div v-if="viewerContext.before" class="mcp-context-text">
|
v-else
|
||||||
{{ viewerContext.before }}
|
:content="getMarkdownContent()" />
|
||||||
</div>
|
</div>
|
||||||
<div class="mcp-highlighted-chunk">
|
|
||||||
{{ viewerContext.chunk }}
|
<!-- Fixed Footer (navigation controls) -->
|
||||||
</div>
|
<div v-if="!viewerLoading && viewerType === 'pdf' && pdfTotalPages > 0" class="mcp-modal-footer">
|
||||||
<div v-if="viewerContext.after" class="mcp-context-text">
|
<NcButton
|
||||||
{{ viewerContext.after }}
|
:disabled="viewerPage <= 1"
|
||||||
</div>
|
@click="viewerPage--">
|
||||||
</div>
|
<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>
|
</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 Cog from 'vue-material-design-icons/Cog.vue'
|
||||||
import ChevronDown from 'vue-material-design-icons/ChevronDown.vue'
|
import ChevronDown from 'vue-material-design-icons/ChevronDown.vue'
|
||||||
import ChevronUp from 'vue-material-design-icons/ChevronUp.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 Refresh from 'vue-material-design-icons/Refresh.vue'
|
||||||
import OpenInNew from 'vue-material-design-icons/OpenInNew.vue'
|
import OpenInNew from 'vue-material-design-icons/OpenInNew.vue'
|
||||||
import Eye from 'vue-material-design-icons/Eye.vue'
|
import Eye from 'vue-material-design-icons/Eye.vue'
|
||||||
import Close from 'vue-material-design-icons/Close.vue'
|
import Close from 'vue-material-design-icons/Close.vue'
|
||||||
|
|
||||||
import PDFViewer from './components/PDFViewer.vue'
|
import PDFViewer from './components/PDFViewer.vue'
|
||||||
|
import MarkdownViewer from './components/MarkdownViewer.vue'
|
||||||
|
|
||||||
import axios from '@nextcloud/axios'
|
import axios from '@nextcloud/axios'
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl } from '@nextcloud/router'
|
||||||
@@ -394,11 +412,14 @@ export default {
|
|||||||
NcEmptyContent,
|
NcEmptyContent,
|
||||||
NcCheckboxRadioSwitch,
|
NcCheckboxRadioSwitch,
|
||||||
PDFViewer,
|
PDFViewer,
|
||||||
|
MarkdownViewer,
|
||||||
Magnify,
|
Magnify,
|
||||||
ChartBox,
|
ChartBox,
|
||||||
Cog,
|
Cog,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
ChevronUp,
|
ChevronUp,
|
||||||
|
ChevronLeft,
|
||||||
|
ChevronRight,
|
||||||
Refresh,
|
Refresh,
|
||||||
OpenInNew,
|
OpenInNew,
|
||||||
Eye,
|
Eye,
|
||||||
@@ -434,6 +455,7 @@ export default {
|
|||||||
viewerTitle: '',
|
viewerTitle: '',
|
||||||
viewerType: 'text',
|
viewerType: 'text',
|
||||||
viewerPage: 1,
|
viewerPage: 1,
|
||||||
|
pdfTotalPages: 0,
|
||||||
currentPdfPath: '',
|
currentPdfPath: '',
|
||||||
viewerContext: {
|
viewerContext: {
|
||||||
chunk: '',
|
chunk: '',
|
||||||
@@ -776,8 +798,35 @@ export default {
|
|||||||
this.viewerType = 'text'
|
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() {
|
closeViewer() {
|
||||||
this.showViewer = false
|
this.showViewer = false
|
||||||
|
this.pdfTotalPages = 0
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1031,25 +1080,6 @@ a.mcp-result-title {
|
|||||||
line-height: 1.4;
|
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 {
|
.mcp-result-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -1159,6 +1189,24 @@ a.mcp-result-title {
|
|||||||
position: relative;
|
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 {
|
.mcp-viewer-loading {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -0,0 +1,185 @@
|
|||||||
|
<template>
|
||||||
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
|
<div class="markdown-viewer" v-html="html" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import MarkdownIt from 'markdown-it'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'MarkdownViewer',
|
||||||
|
|
||||||
|
props: {
|
||||||
|
content: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
const md = new MarkdownIt({
|
||||||
|
html: false, // Disable HTML for security
|
||||||
|
linkify: true,
|
||||||
|
breaks: true,
|
||||||
|
typographer: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
html: '',
|
||||||
|
md,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
content: {
|
||||||
|
immediate: true,
|
||||||
|
handler(newContent) {
|
||||||
|
this.renderMarkdown(newContent)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
renderMarkdown(text) {
|
||||||
|
if (!text) {
|
||||||
|
this.html = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.html = this.md.render(text)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Markdown rendering error:', error)
|
||||||
|
// Fallback to escaped plain text
|
||||||
|
this.html = `<pre>${this.escapeHtml(text)}</pre>`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
escapeHtml(text) {
|
||||||
|
const div = document.createElement('div')
|
||||||
|
div.textContent = text
|
||||||
|
return div.innerHTML
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.markdown-viewer {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--color-main-text);
|
||||||
|
word-wrap: break-word;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
|
||||||
|
// Typography
|
||||||
|
:deep(h1), :deep(h2), :deep(h3), :deep(h4), :deep(h5), :deep(h6) {
|
||||||
|
margin-top: 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.25;
|
||||||
|
color: var(--color-main-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(h1) { font-size: 2em; border-bottom: 1px solid var(--color-border); padding-bottom: 8px; }
|
||||||
|
:deep(h2) { font-size: 1.5em; border-bottom: 1px solid var(--color-border); padding-bottom: 8px; }
|
||||||
|
:deep(h3) { font-size: 1.25em; }
|
||||||
|
:deep(h4) { font-size: 1em; }
|
||||||
|
:deep(h5) { font-size: 0.875em; }
|
||||||
|
:deep(h6) { font-size: 0.85em; color: var(--color-text-maxcontrast); }
|
||||||
|
|
||||||
|
// Paragraphs and spacing
|
||||||
|
:deep(p) {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lists
|
||||||
|
:deep(ul), :deep(ol) {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding-left: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(li) {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code blocks
|
||||||
|
:deep(code) {
|
||||||
|
padding: 2px 6px;
|
||||||
|
background: var(--color-background-dark);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
font-family: 'Courier New', Courier, monospace;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(pre) {
|
||||||
|
background: var(--color-background-dark);
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
overflow-x: auto;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
code {
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blockquotes
|
||||||
|
:deep(blockquote) {
|
||||||
|
margin: 0 0 16px 0;
|
||||||
|
padding: 0 16px;
|
||||||
|
border-left: 4px solid var(--color-primary-element);
|
||||||
|
color: var(--color-text-maxcontrast);
|
||||||
|
|
||||||
|
p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Links
|
||||||
|
:deep(a) {
|
||||||
|
color: var(--color-primary-element);
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tables
|
||||||
|
:deep(table) {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(th), :deep(td) {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(th) {
|
||||||
|
background: var(--color-background-dark);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Horizontal rule
|
||||||
|
:deep(hr) {
|
||||||
|
border: none;
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
margin: 24px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Images
|
||||||
|
:deep(img) {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -11,27 +11,6 @@
|
|||||||
<div v-else ref="container" class="pdf-canvas-container">
|
<div v-else ref="container" class="pdf-canvas-container">
|
||||||
<canvas ref="canvas" />
|
<canvas ref="canvas" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!loading && !error && totalPages > 0" class="pdf-controls">
|
|
||||||
<NcButton
|
|
||||||
:disabled="pageNumber <= 1"
|
|
||||||
@click="$emit('prev-page')">
|
|
||||||
<template #icon>
|
|
||||||
<ChevronLeft :size="20" />
|
|
||||||
</template>
|
|
||||||
{{ t('astroglobe', 'Previous') }}
|
|
||||||
</NcButton>
|
|
||||||
<span class="page-info">
|
|
||||||
{{ t('astroglobe', 'Page {current} of {total}', { current: pageNumber, total: totalPages }) }}
|
|
||||||
</span>
|
|
||||||
<NcButton
|
|
||||||
:disabled="pageNumber >= totalPages"
|
|
||||||
@click="$emit('next-page')">
|
|
||||||
<template #icon>
|
|
||||||
<ChevronRight :size="20" />
|
|
||||||
</template>
|
|
||||||
{{ t('astroglobe', 'Next') }}
|
|
||||||
</NcButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -40,19 +19,13 @@ import * as pdfjsLib from 'pdfjs-dist'
|
|||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl } from '@nextcloud/router'
|
||||||
import { translate as t } from '@nextcloud/l10n'
|
import { translate as t } from '@nextcloud/l10n'
|
||||||
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
|
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
|
||||||
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
|
|
||||||
import AlertCircle from 'vue-material-design-icons/AlertCircle.vue'
|
import AlertCircle from 'vue-material-design-icons/AlertCircle.vue'
|
||||||
import ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue'
|
|
||||||
import ChevronRight from 'vue-material-design-icons/ChevronRight.vue'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PDFViewer',
|
name: 'PDFViewer',
|
||||||
components: {
|
components: {
|
||||||
NcLoadingIcon,
|
NcLoadingIcon,
|
||||||
NcButton,
|
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
ChevronLeft,
|
|
||||||
ChevronRight,
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
filePath: {
|
filePath: {
|
||||||
@@ -247,34 +220,9 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pdf-controls {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
padding: 8px;
|
|
||||||
background: var(--color-background-dark);
|
|
||||||
border-radius: var(--border-radius-large);
|
|
||||||
|
|
||||||
.page-info {
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--color-text-maxcontrast);
|
|
||||||
min-width: 120px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.pdf-viewer {
|
.pdf-viewer {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pdf-controls {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
|
|
||||||
.page-info {
|
|
||||||
order: -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user