Enhance file attachment viewing experience
Replaced direct file links with a JavaScript-based viewer (`openAttachmentViewer`) and added a DevExtreme Popup (`attachment-viewer-popup`) for displaying attachments in a modal dialog. The viewer supports PDFs (via PDF.js), XML (with CodeMirror syntax highlighting), plain text, images, and provides a fallback for unsupported file types. Dynamically load CodeMirror for XML files and handle errors gracefully when loading file content. Added `onAttachmentPopupHiding` to clear popup content on close. Updated `ViewAttachmentModel` to return text/XML content directly for AJAX requests, improving frontend performance and enabling dynamic content loading.
This commit is contained in:
@@ -75,9 +75,9 @@ else
|
|||||||
_ => "📎"
|
_ => "📎"
|
||||||
};
|
};
|
||||||
|
|
||||||
<a href="/Invoices/ViewAttachment?filePath=@Uri.EscapeDataString(attachment.SavedFilePath)"
|
<a href="javascript:void(0);"
|
||||||
class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
|
class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
|
||||||
target="_blank">
|
onclick="openAttachmentViewer('@attachment.OriginalFileName', '@Uri.EscapeDataString(attachment.SavedFilePath)', '@extension')">
|
||||||
<div>
|
<div>
|
||||||
<span class="me-2">@icon</span>
|
<span class="me-2">@icon</span>
|
||||||
<strong>@attachment.OriginalFileName</strong>
|
<strong>@attachment.OriginalFileName</strong>
|
||||||
@@ -115,6 +115,20 @@ else
|
|||||||
return '<iframe id=""pdf-iframe"" style=""width:100%;height:100%;border:none;""></iframe>';
|
return '<iframe id=""pdf-iframe"" style=""width:100%;height:100%;border:none;""></iframe>';
|
||||||
}"))
|
}"))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@(Html.DevExtreme().Popup()
|
||||||
|
.ID("attachment-viewer-popup")
|
||||||
|
.Title("Anhang")
|
||||||
|
.Width("90%")
|
||||||
|
.Height("90%")
|
||||||
|
.ShowCloseButton(true)
|
||||||
|
.OnHiding("onAttachmentPopupHiding")
|
||||||
|
.Shading(true)
|
||||||
|
.ShadingColor("rgba(0, 0, 0, 0.5)")
|
||||||
|
.ContentTemplate(new JS(@"function() {
|
||||||
|
return '<div id=""attachment-content"" style=""width:100%;height:100%;overflow:auto;""></div>';
|
||||||
|
}"))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -160,4 +174,104 @@ else
|
|||||||
// iframe src leeren beim Schließen → verhindert dass PDF im Hintergrund weiter läuft
|
// iframe src leeren beim Schließen → verhindert dass PDF im Hintergrund weiter läuft
|
||||||
$('#pdf-iframe').attr('src', '');
|
$('#pdf-iframe').attr('src', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openAttachmentViewer(fileName, encodedFilePath, extension) {
|
||||||
|
var filePath = decodeURIComponent(encodedFilePath);
|
||||||
|
var popup = $('#attachment-viewer-popup').dxPopup('instance');
|
||||||
|
var $content = $('#attachment-content');
|
||||||
|
|
||||||
|
// Popup-Titel setzen
|
||||||
|
popup.option('title', fileName);
|
||||||
|
|
||||||
|
// Z-Index setzen
|
||||||
|
popup.option('container', undefined);
|
||||||
|
popup.option('position', { my: 'center', at: 'center', of: window });
|
||||||
|
|
||||||
|
// Content basierend auf Dateityp laden
|
||||||
|
popup.option('onShown', function () {
|
||||||
|
$content.html('<div class="text-center p-5"><div class="spinner-border text-primary" role="status"><span class="visually-hidden">Laden...</span></div></div>');
|
||||||
|
|
||||||
|
// Z-Index sicherstellen
|
||||||
|
$('.dx-popup-wrapper').css('z-index', '10500');
|
||||||
|
$('.dx-overlay-shader').css('z-index', '10499');
|
||||||
|
|
||||||
|
if (extension === '.pdf') {
|
||||||
|
// PDF mit PDF.js anzeigen
|
||||||
|
var pdfUrl = window.location.origin + '/Invoices/ViewAttachment?handler=Download&filePath=' + encodedFilePath;
|
||||||
|
var viewerUrl = '/js/pdfjs/web/viewer.html?file=' + encodeURIComponent(pdfUrl);
|
||||||
|
$content.html('<iframe style="width:100%;height:100%;border:none;" src="' + viewerUrl + '"></iframe>');
|
||||||
|
}
|
||||||
|
else if (extension === '.xml' || extension === '.txt') {
|
||||||
|
// Text/XML laden und mit Syntax-Highlighting anzeigen
|
||||||
|
$.get('/Invoices/ViewAttachment?filePath=' + encodedFilePath, function(data) {
|
||||||
|
if (extension === '.xml') {
|
||||||
|
// CodeMirror für XML
|
||||||
|
var textarea = $('<textarea id="code-viewer"></textarea>');
|
||||||
|
$content.html('').append(textarea);
|
||||||
|
|
||||||
|
// CodeMirror laden (falls noch nicht geladen)
|
||||||
|
if (typeof CodeMirror === 'undefined') {
|
||||||
|
$('<link>')
|
||||||
|
.attr('rel', 'stylesheet')
|
||||||
|
.attr('href', 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css')
|
||||||
|
.appendTo('head');
|
||||||
|
$('<link>')
|
||||||
|
.attr('rel', 'stylesheet')
|
||||||
|
.attr('href', 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/theme/monokai.min.css')
|
||||||
|
.appendTo('head');
|
||||||
|
|
||||||
|
$.getScript('https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js', function() {
|
||||||
|
$.getScript('https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/xml/xml.min.js', function() {
|
||||||
|
initCodeMirror(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
initCodeMirror(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function initCodeMirror(content) {
|
||||||
|
CodeMirror(document.getElementById('attachment-content'), {
|
||||||
|
value: content,
|
||||||
|
mode: 'xml',
|
||||||
|
lineNumbers: true,
|
||||||
|
readOnly: true,
|
||||||
|
theme: 'monokai',
|
||||||
|
lineWrapping: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Plain Text
|
||||||
|
$content.html('<pre style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; height: 100%; overflow: auto; margin: 0;">' +
|
||||||
|
$('<div>').text(data).html() + '</pre>');
|
||||||
|
}
|
||||||
|
}).fail(function() {
|
||||||
|
$content.html('<div class="alert alert-danger m-3">Fehler beim Laden der Datei.</div>');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (extension === '.jpg' || extension === '.jpeg' || extension === '.png' || extension === '.gif') {
|
||||||
|
// Bild anzeigen
|
||||||
|
var imageUrl = window.location.origin + '/Invoices/ViewAttachment?handler=Download&filePath=' + encodedFilePath;
|
||||||
|
$content.html('<div class="text-center p-3" style="height: 100%; display: flex; align-items: center; justify-content: center;">' +
|
||||||
|
'<img src="' + imageUrl + '" alt="' + fileName + '" class="img-fluid" style="max-height: 100%; max-width: 100%; object-fit: contain;">' +
|
||||||
|
'</div>');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Nicht unterstützter Typ → Download anbieten
|
||||||
|
var downloadUrl = window.location.origin + '/Invoices/ViewAttachment?handler=Download&filePath=' + encodedFilePath;
|
||||||
|
$content.html('<div class="alert alert-info m-3">' +
|
||||||
|
'<h5>Dieser Dateityp kann nicht angezeigt werden.</h5>' +
|
||||||
|
'<p>Bitte laden Sie die Datei herunter:</p>' +
|
||||||
|
'<a href="' + downloadUrl + '" class="btn btn-primary" download="' + fileName + '">' +
|
||||||
|
'<i class="dx-icon-download"></i> Datei herunterladen' +
|
||||||
|
'</a></div>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
popup.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onAttachmentPopupHiding() {
|
||||||
|
// Content leeren
|
||||||
|
$('#attachment-content').html('');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -19,10 +19,17 @@ public class ViewAttachmentModel(AttachmentViewerService viewerService) : PageMo
|
|||||||
FileName = Path.GetFileName(filePath);
|
FileName = Path.GetFileName(filePath);
|
||||||
ViewerType = viewerService.DetermineViewerType(FileName);
|
ViewerType = viewerService.DetermineViewerType(FileName);
|
||||||
|
|
||||||
// Für Text/XML: Inhalt laden
|
// Für Text/XML: Inhalt direkt als Content zurückgeben (für AJAX)
|
||||||
if (ViewerType == AttachmentViewerType.Xml || ViewerType == AttachmentViewerType.Text)
|
if (ViewerType == AttachmentViewerType.Xml || ViewerType == AttachmentViewerType.Text)
|
||||||
{
|
{
|
||||||
TextContent = System.IO.File.ReadAllText(filePath, Encoding.UTF8);
|
TextContent = System.IO.File.ReadAllText(filePath, Encoding.UTF8);
|
||||||
|
|
||||||
|
// Wenn Request von AJAX kommt (Accept: */* oder text/plain)
|
||||||
|
if (Request.Headers.Accept.ToString().Contains("*/*") ||
|
||||||
|
Request.Headers["X-Requested-With"] == "XMLHttpRequest")
|
||||||
|
{
|
||||||
|
return Content(TextContent, "text/plain", Encoding.UTF8);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Page();
|
return Page();
|
||||||
|
|||||||
Reference in New Issue
Block a user