diff --git a/source/components/content/viewer.mjs b/source/components/content/viewer.mjs
index 9c6066a488b2933fc683075014ae7018047c4441..81ffc8a21bddc06eb7828280d6befa0d2efac162 100644
--- a/source/components/content/viewer.mjs
+++ b/source/components/content/viewer.mjs
@@ -13,24 +13,24 @@
  */
 
 import {
-    assembleMethodSymbol,
-    CustomElement,
-    registerCustomElement,
+	assembleMethodSymbol,
+	CustomElement,
+	registerCustomElement,
 } from "../../dom/customelement.mjs";
 import "../notify/notify.mjs";
-import {ViewerStyleSheet} from "./stylesheet/viewer.mjs";
-import {instanceSymbol} from "../../constants.mjs";
-import {isString} from "../../types/is.mjs";
-import {getGlobal} from "../../types/global.mjs";
-import {MediaType, parseMediaType} from "../../types/mediatype.mjs";
-import {MarkdownToHTML} from "../../text/markdown-parser.mjs";
+import { ViewerStyleSheet } from "./stylesheet/viewer.mjs";
+import { instanceSymbol } from "../../constants.mjs";
+import { isString } from "../../types/is.mjs";
+import { getGlobal } from "../../types/global.mjs";
+import { MediaType, parseMediaType } from "../../types/mediatype.mjs";
+import { MarkdownToHTML } from "../../text/markdown-parser.mjs";
 import "../layout/tabs.mjs";
 import "./viewer/message.mjs";
-import {getLocaleOfDocument} from "../../dom/locale.mjs";
-import {Button} from "../form/button.mjs";
-import {findTargetElementFromEvent} from "../../dom/events.mjs";
+import { getLocaleOfDocument } from "../../dom/locale.mjs";
+import { Button } from "../form/button.mjs";
+import { findTargetElementFromEvent } from "../../dom/events.mjs";
 
-export {Viewer};
+export { Viewer };
 
 /**
  * @private
@@ -51,657 +51,657 @@ const viewerElementSymbol = Symbol("viewerElement");
  * @summary A simple viewer component for PDF, HTML, and images.
  */
 class Viewer extends CustomElement {
-    /**
-     * This method is called by the `instanceof` operator.
-     * @return {symbol}
-     */
-    static get [instanceSymbol]() {
-        return Symbol.for("@schukai/monster/components/content/viewer@@instance");
-    }
-
-    /**
-     * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
-     * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
-     *
-     * The individual configuration values can be found in the table.
-     *
-     * @property {Object} templates Template definitions
-     * @property {string} templates.main Main template
-     * @property {string} content Content to be displayed in the viewer
-     * @property {Object} classes Css classes
-     * @property {string} classes.viewer Css class for the viewer
-     * @property {Object} renderers Renderers for different media types
-     * @property {function} renderers.image Function to render image content
-     * @property {function} renderers.html Function to render HTML content
-     * @property {function} renderers.pdf Function to render PDF content
-     * @property {function} renderers.plaintext Function to render plain text content
-     * @property {function} renderers.markdown Function to render Markdown content
-     */
-    get defaults() {
-        return Object.assign({}, super.defaults, {
-            templates: {
-                main: getTemplate(),
-            },
-            content: "<slot></slot>",
-            classes: {
-                viewer: "",
-            },
-            labels: getLabels(),
-            renderers: {
-                image: this.setImage,
-                html: this.setHTML,
-                pdf: this.setPDF,
-                download: this.setDownload,
-                plaintext: this.setPlainText,
-                markdown: this.setMarkdown,
-                audio: this.setAudio,
-                video: this.setVideo,
-                message: this.setMessage,
-            },
-
-        });
-    }
-
-    /**
-     * Sets the content of an element based on the provided content and media type.
-     *
-     * @param {string} content - The content to be set.
-     * @param {string} [mediaType="text/plain"] - The media type of the content. Defaults to "text/plain" if not specified.
-     * @return {void} This method does not return a value.
-     * @throws {Error} Throws an error if shadowRoot is not defined.
-     */
-    setContent(content, mediaType = "text/plain") {
-        if (!this.shadowRoot) {
-            throw new Error("no shadow-root is defined");
-        }
-
-        const renderers = this.getOption("renderers");
-
-        const isDataURL = (value) => {
-            return (
-                (typeof value === "string" && value.startsWith("data:")) ||
-                (value instanceof URL && value.protocol === "data:")
-            );
-        };
-
-        if (isDataURL(content)) {
-            try {
-                const dataUrl = content.toString();
-                const [header] = dataUrl.split(",");
-                const [typeSegment] = header.split(";");
-                mediaType = typeSegment.replace("data:", "") || "text/plain";
-            } catch (error) {
-                this.dispatchEvent(
-                    new CustomEvent("viewer-error", {
-                        detail: "Invalid data URL format",
-                    }),
-                );
-                return;
-            }
-        }
-
-        if (mediaType === undefined || mediaType === null || mediaType === "") {
-            mediaType = "text/plain";
-        }
-
-        let mediaTypeObject;
-
-        try {
-            mediaTypeObject = new parseMediaType(mediaType);
-            if (!(mediaTypeObject instanceof MediaType)) {
-                this.dispatchEvent(
-                    new CustomEvent("viewer-error", {detail: "Invalid MediaType"}),
-                );
-                return;
-            }
-        } catch (error) {
-            this.dispatchEvent(new CustomEvent("viewer-error", {detail: error}));
-            return;
-        }
-
-        const checkRenderer = (renderer, contentType) => {
-            if (renderers && typeof renderers[renderer] === "function") {
-                return true;
-            } else {
-                this.dispatchEvent(
-                    new CustomEvent("viewer-error", {
-                        detail: `Renderer for ${contentType} not found`,
-                    }),
-                );
-                return false;
-            }
-        };
-
-        switch (mediaTypeObject.type) {
-            case "text":
-                switch (mediaTypeObject.subtype) {
-                    case "html":
-                        if (checkRenderer("html", mediaTypeObject.toString())) {
-                            renderers.html.call(this, content);
-                        }
-                        break;
-                    case "plain":
-                        if (checkRenderer("plaintext", mediaTypeObject.toString())) {
-                            renderers.plaintext.call(this, content);
-                        }
-                        break;
-                    case "markdown":
-                        if (checkRenderer("markdown", mediaTypeObject.toString())) {
-                            this.setMarkdown(content);
-                        }
-                        break;
-                    default:
-                        if (checkRenderer("plaintext", mediaTypeObject.toString())) {
-                            renderers.plaintext.call(this, content);
-                        }
-                        break;
-                }
-                break;
-
-            case "application":
-                switch (mediaTypeObject.subtype) {
-                    case "pdf":
-                        if (checkRenderer("pdf", mediaTypeObject.toString())) {
-                            renderers.pdf.call(this, content);
-                        }
-                        break;
-
-                    default:
-
-                        // Handle octet-stream as a generic binary data type
-                        if (checkRenderer("download", mediaTypeObject.toString())) {
-                            renderers.download.call(this, content);
-                            break;
-                        }
-
-                        this.setOption("content", content + "!!");
-                        break;
-                }
-                break;
-
-            case "message":
-                switch (mediaTypeObject.subtype) {
-                    case "rfc822":
-                        if (checkRenderer("message", mediaTypeObject.toString())) {
-                            renderers.message.call(this, content);
-                        }
-                        break;
-
-                    default:
-                        this.setOption("content", content);
-                        break;
-                }
-                break;
-
-            case "audio":
-                if (checkRenderer(mediaTypeObject.type, mediaTypeObject.toString())) {
-                    renderers[mediaTypeObject.type].call(this, content);
-                }
-                break;
-
-            case "video":
-                if (checkRenderer(mediaTypeObject.type, mediaTypeObject.toString())) {
-                    renderers[mediaTypeObject.type].call(this, content);
-                }
-                break;
-
-            case "image":
-                if (checkRenderer("image", mediaTypeObject.toString())) {
-                    renderers.image.call(this, content);
-                }
-                break;
-
-            default:
-                this.setOption("content", content);
-                this.dispatchEvent(
-                    new CustomEvent("viewer-error", {
-                        detail: `Unsupported media type: ${mediaTypeObject.toString()}`,
-                    }),
-                ); // Notify about unsupported media type
-                return;
-        }
-    }
-
-    /**
-     * Sets the audio content for the viewer. Accepts a Blob, URL, or string and processes it
-     * to configure audio playback within the viewer. Throws an error if the input type is invalid.
-     *
-     * @param {Blob|string} data - The audio content. This can be a Blob, a URL, or a string.
-     * @return {void} No return value.
-     */
-    setAudio(data) {
-        if (isBlob(data)) {
-            data = URL.createObjectURL(data);
-        } else if (isURL(data)) {
-            // nothing to do
-        } else if (isString(data)) {
-            // nothing to do
-        } else {
-            this.dispatchEvent(
-                new CustomEvent("viewer-error", {detail: "Blob or URL expected"}),
-            );
-            throw new Error("Blob or URL expected");
-        }
-
-        this.setOption(
-            "content",
-            `
+	/**
+	 * This method is called by the `instanceof` operator.
+	 * @return {symbol}
+	 */
+	static get [instanceSymbol]() {
+		return Symbol.for("@schukai/monster/components/content/viewer@@instance");
+	}
+
+	/**
+	 * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
+	 * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
+	 *
+	 * The individual configuration values can be found in the table.
+	 *
+	 * @property {Object} templates Template definitions
+	 * @property {string} templates.main Main template
+	 * @property {string} content Content to be displayed in the viewer
+	 * @property {Object} classes Css classes
+	 * @property {string} classes.viewer Css class for the viewer
+	 * @property {Object} renderers Renderers for different media types
+	 * @property {function} renderers.image Function to render image content
+	 * @property {function} renderers.html Function to render HTML content
+	 * @property {function} renderers.pdf Function to render PDF content
+	 * @property {function} renderers.plaintext Function to render plain text content
+	 * @property {function} renderers.markdown Function to render Markdown content
+	 */
+	get defaults() {
+		return Object.assign({}, super.defaults, {
+			templates: {
+				main: getTemplate(),
+			},
+			content: "<slot></slot>",
+			classes: {
+				viewer: "",
+			},
+			labels: getLabels(),
+			renderers: {
+				image: this.setImage,
+				html: this.setHTML,
+				pdf: this.setPDF,
+				download: this.setDownload,
+				plaintext: this.setPlainText,
+				markdown: this.setMarkdown,
+				audio: this.setAudio,
+				video: this.setVideo,
+				message: this.setMessage,
+			},
+		});
+	}
+
+	/**
+	 * Sets the content of an element based on the provided content and media type.
+	 *
+	 * @param {string} content - The content to be set.
+	 * @param {string} [mediaType="text/plain"] - The media type of the content. Defaults to "text/plain" if not specified.
+	 * @return {void} This method does not return a value.
+	 * @throws {Error} Throws an error if shadowRoot is not defined.
+	 */
+	setContent(content, mediaType = "text/plain") {
+		if (!this.shadowRoot) {
+			throw new Error("no shadow-root is defined");
+		}
+
+		const renderers = this.getOption("renderers");
+
+		const isDataURL = (value) => {
+			return (
+				(typeof value === "string" && value.startsWith("data:")) ||
+				(value instanceof URL && value.protocol === "data:")
+			);
+		};
+
+		if (isDataURL(content)) {
+			try {
+				const dataUrl = content.toString();
+				const [header] = dataUrl.split(",");
+				const [typeSegment] = header.split(";");
+				mediaType = typeSegment.replace("data:", "") || "text/plain";
+			} catch (error) {
+				this.dispatchEvent(
+					new CustomEvent("viewer-error", {
+						detail: "Invalid data URL format",
+					}),
+				);
+				return;
+			}
+		}
+
+		if (mediaType === undefined || mediaType === null || mediaType === "") {
+			mediaType = "text/plain";
+		}
+
+		let mediaTypeObject;
+
+		try {
+			mediaTypeObject = new parseMediaType(mediaType);
+			if (!(mediaTypeObject instanceof MediaType)) {
+				this.dispatchEvent(
+					new CustomEvent("viewer-error", { detail: "Invalid MediaType" }),
+				);
+				return;
+			}
+		} catch (error) {
+			this.dispatchEvent(new CustomEvent("viewer-error", { detail: error }));
+			return;
+		}
+
+		const checkRenderer = (renderer, contentType) => {
+			if (renderers && typeof renderers[renderer] === "function") {
+				return true;
+			} else {
+				this.dispatchEvent(
+					new CustomEvent("viewer-error", {
+						detail: `Renderer for ${contentType} not found`,
+					}),
+				);
+				return false;
+			}
+		};
+
+		switch (mediaTypeObject.type) {
+			case "text":
+				switch (mediaTypeObject.subtype) {
+					case "html":
+						if (checkRenderer("html", mediaTypeObject.toString())) {
+							renderers.html.call(this, content);
+						}
+						break;
+					case "plain":
+						if (checkRenderer("plaintext", mediaTypeObject.toString())) {
+							renderers.plaintext.call(this, content);
+						}
+						break;
+					case "markdown":
+						if (checkRenderer("markdown", mediaTypeObject.toString())) {
+							this.setMarkdown(content);
+						}
+						break;
+					default:
+						if (checkRenderer("plaintext", mediaTypeObject.toString())) {
+							renderers.plaintext.call(this, content);
+						}
+						break;
+				}
+				break;
+
+			case "application":
+				switch (mediaTypeObject.subtype) {
+					case "pdf":
+						if (checkRenderer("pdf", mediaTypeObject.toString())) {
+							renderers.pdf.call(this, content);
+						}
+						break;
+
+					default:
+						// Handle octet-stream as a generic binary data type
+						if (checkRenderer("download", mediaTypeObject.toString())) {
+							renderers.download.call(this, content);
+							break;
+						}
+
+						this.setOption("content", content);
+						break;
+				}
+				break;
+
+			case "message":
+				switch (mediaTypeObject.subtype) {
+					case "rfc822":
+						if (checkRenderer("message", mediaTypeObject.toString())) {
+							renderers.message.call(this, content);
+						}
+						break;
+
+					default:
+						this.setOption("content", content);
+						break;
+				}
+				break;
+
+			case "audio":
+				if (checkRenderer(mediaTypeObject.type, mediaTypeObject.toString())) {
+					renderers[mediaTypeObject.type].call(this, content);
+				}
+				break;
+
+			case "video":
+				if (checkRenderer(mediaTypeObject.type, mediaTypeObject.toString())) {
+					renderers[mediaTypeObject.type].call(this, content);
+				}
+				break;
+
+			case "image":
+				if (checkRenderer("image", mediaTypeObject.toString())) {
+					renderers.image.call(this, content);
+				}
+				break;
+
+			default:
+				this.setOption("content", content);
+				this.dispatchEvent(
+					new CustomEvent("viewer-error", {
+						detail: `Unsupported media type: ${mediaTypeObject.toString()}`,
+					}),
+				); // Notify about unsupported media type
+				return;
+		}
+	}
+
+	/**
+	 * Sets the audio content for the viewer. Accepts a Blob, URL, or string and processes it
+	 * to configure audio playback within the viewer. Throws an error if the input type is invalid.
+	 *
+	 * @param {Blob|string} data - The audio content. This can be a Blob, a URL, or a string.
+	 * @return {void} No return value.
+	 */
+	setAudio(data) {
+		if (isBlob(data)) {
+			data = URL.createObjectURL(data);
+		} else if (isURL(data)) {
+			// nothing to do
+		} else if (isString(data)) {
+			// nothing to do
+		} else {
+			this.dispatchEvent(
+				new CustomEvent("viewer-error", { detail: "Blob or URL expected" }),
+			);
+			throw new Error("Blob or URL expected");
+		}
+
+		this.setOption(
+			"content",
+			`
                    <audio controls part="audio" style="max-width: 100%">
                        <source src="${data}">
                    </audio>`,
-        );
-    }
-
-    /**
-     * Sets the video content for the viewer. The method accepts a Blob, URL, or string,
-     * verifies its type, and updates the viewer's content accordingly.
-     *
-     * @param {Blob|string} data - The video data to set. It can be a Blob, URL, or string.
-     * @return {void} This method does not return a value. It updates the viewer's state.
-     * @throws {Error} Throws an error if the provided data is not a Blob or URL.
-     */
-    setVideo(data) {
-        if (isBlob(data)) {
-            data = URL.createObjectURL(data);
-        } else if (isURL(data)) {
-            // nothing to do
-        } else if (isString(data)) {
-            // nothing to do
-        } else {
-            this.dispatchEvent(
-                new CustomEvent("viewer-error", {detail: "Blob or URL expected"}),
-            );
-            throw new Error("Blob or URL expected");
-        }
-
-        this.setOption(
-            "content",
-            `
+		);
+	}
+
+	/**
+	 * Sets the video content for the viewer. The method accepts a Blob, URL, or string,
+	 * verifies its type, and updates the viewer's content accordingly.
+	 *
+	 * @param {Blob|string} data - The video data to set. It can be a Blob, URL, or string.
+	 * @return {void} This method does not return a value. It updates the viewer's state.
+	 * @throws {Error} Throws an error if the provided data is not a Blob or URL.
+	 */
+	setVideo(data) {
+		if (isBlob(data)) {
+			data = URL.createObjectURL(data);
+		} else if (isURL(data)) {
+			// nothing to do
+		} else if (isString(data)) {
+			// nothing to do
+		} else {
+			this.dispatchEvent(
+				new CustomEvent("viewer-error", { detail: "Blob or URL expected" }),
+			);
+			throw new Error("Blob or URL expected");
+		}
+
+		this.setOption(
+			"content",
+			`
                    <video controls part="video" style="max-width: 100%">
                      <source src="${data}">
                    </video>`,
-        );
-    }
-
-    /**
-     * Renders Markdown content using built-in or custom Markdown parser.
-     * Overrideable via `customRenderers['text/markdown']`.
-     *
-     * @param {string|Blob} data
-     */
-    setMarkdown(data) {
-        if (isBlob(data)) {
-            blobToText(data)
-                .then((markdownText) => {
-                    try {
-                        const html = MarkdownToHTML.convert(markdownText);
-                        this.setHTML(html);
-                    } catch (error) {
-                        this.setPlainText(markdownText); // Fallback to plain text if conversion fails
-                        this.dispatchEvent(
-                            new CustomEvent("viewer-error", {detail: error}),
-                        );
-                    }
-                })
-                .catch((error) => {
-                    this.dispatchEvent(
-                        new CustomEvent("viewer-error", {detail: error}),
-                    );
-                    throw new Error(error);
-                });
-            return;
-        } else if (isURL(data)) {
-            getGlobal()
-                .fetch(data)
-                .then((response) => {
-                    return response.text();
-                })
-                .then((markdownText) => {
-                    try {
-                        const html = MarkdownToHTML.convert(markdownText);
-                        this.setHTML(html);
-                    } catch (error) {
-                        this.setPlainText(markdownText); // Fallback to plain text if conversion fails
-                        this.dispatchEvent(
-                            new CustomEvent("viewer-error", {detail: error}),
-                        );
-                    }
-                })
-                .catch((error) => {
-                    this.dispatchEvent(
-                        new CustomEvent("viewer-error", {detail: error}),
-                    );
-                    throw new Error(error);
-                });
-            return;
-        } else if (isString(data)) {
-            try {
-                const html = MarkdownToHTML.convert(data);
-                this.setHTML(html);
-            } catch (error) {
-                this.setPlainText(data); // Fallback to plain text if conversion fails
-                this.dispatchEvent(new CustomEvent("viewer-error", {detail: error}));
-            }
-            return;
-        }
-
-        this.dispatchEvent(
-            new CustomEvent("viewer-error", {detail: "Blob or string expected"}),
-        );
-        throw new Error("Blob or string expected");
-    }
-
-    /**
-     * Configures and embeds a PDF document into the application with customizable display settings.
-     *
-     * @param {Blob|URL|string} data The PDF data to be embedded. Can be provided as a Blob, URL, or base64 string.
-     * @param {boolean} [navigation=true] Determines whether the navigation pane is displayed in the PDF viewer.
-     * @param {boolean} [toolbar=true] Controls the visibility of the toolbar in the PDF viewer.
-     * @param {boolean} [scrollbar=false] Configures the display of the scrollbar in the PDF viewer.
-     * @return {void} This method returns nothing but sets the embedded PDF as the content.
-     */
-    setPDF(data, navigation = true, toolbar = true, scrollbar = false) {
-        const hashes =
-            "#toolbar=" +
-            (toolbar ? "1" : "0") +
-            "&navpanes=" +
-            (navigation ? "1" : "0") +
-            "&scrollbar=" +
-            (scrollbar ? "1" : "0");
-
-        let pdfURL = "";
-        if (isBlob(data)) {
-            pdfURL = URL.createObjectURL(data);
-            pdfURL += hashes;
-        } else if (isURL(data)) {
-            // check if the url already contains the hashes
-            if (data?.hash?.indexOf("#") === -1) {
-                pdfURL = data.toString() + hashes;
-            } else {
-                pdfURL = data.toString();
-            }
-        } else if (isString(data)) {
-            //URL.createObjectURL(data);
-            const blobObj = new Blob([atob(data)], {type: "application/pdf"});
-            const url = window.URL.createObjectURL(blobObj);
-
-            pdfURL = data;
-        } else {
-            this.dispatchEvent(
-                new CustomEvent("viewer-error", {detail: "Blob or URL expected"}),
-            );
-            throw new Error("Blob or URL expected");
-        }
-
-        const html =
-            '<object part="pdf" data="' +
-            pdfURL +
-            '" width="100%" height="100%" type="application/pdf"></object>';
-
-        this.setOption("content", html);
-    }
-
-    /**
-     * Sets the download functionality for the viewer.
-     * @param data
-     * @param filename
-     */
-    setDownload(data, filename = "download") {
-
-        const rawData = data;
-
-        if (isBlob(data)) {
-            data = URL.createObjectURL(data);
-        } else if (isURL(data)) {
-            // nothing to do
-        } else if (isString(data)) {
-            // nothing to do
-        } else {
-            this.dispatchEvent(
-                new CustomEvent("viewer-error", {detail: "Blob or URL expected"}),
-            );
-            throw new Error("Blob or URL expected");
-        }
-
-        const button = `<monster-button data-monster-role="download">` + this.getOption('labels.download') + `</monster-button>`;
-        this.setOption("content", button);
-
-        this.addEventListener("click", (event) => {
-
-            const element = findTargetElementFromEvent(event, "data-monster-role", "download");
-            if (element instanceof Button) {
-                const anchor = document.createElement("a");
-                anchor.href = URL.createObjectURL(new Blob([rawData]))
-                anchor.download = filename;
-                anchor.style.display = "none";
-                document.body.appendChild(anchor);
-                anchor.click();
-                document.body.removeChild(anchor);
-            }
-
-
-        })
-
-    }
-
-    /**
-     * Sets the content for displaying an email message.
-     * The data is expected to be an object with a structure containing
-     * 'to', 'from', 'subject', 'parts', and 'headers'.
-     * The parts are processed to display plain text and HTML in separate tabs,
-     * and attachments are listed.
-     *
-     * @param {object} emailData - The structured email data.
-     */
-    setMessage(emailData) {
-        if (!emailData || typeof emailData !== "object") {
-            this.dispatchEvent(
-                new CustomEvent("viewer-error", {
-                    detail: "Invalid email data provided",
-                }),
-            );
-            return;
-        }
-
-        this.setOption(
-            "content",
-            '<monster-message-content part="message"></monster-message-content>',
-        );
-
-        setTimeout(() => {
-            const messageContent = this.shadowRoot.querySelector(
-                "monster-message-content",
-            );
-            if (!messageContent) {
-                this.dispatchEvent(
-                    new CustomEvent("viewer-error", {
-                        detail: "Message content element not found",
-                    }),
-                );
-                return;
-            }
-
-            messageContent.setMessage(emailData);
-        }, 100);
-    }
-
-    /**
-     * Sets an image for the target by accepting a blob, URL, or string representation of the image.
-     *
-     * @param {(Blob|string)} data - The image data, which can be a Blob, a valid URL, or a string representation of the image.
-     * @return {void} Does not return a value.
-     */
-    setImage(data) {
-        if (isBlob(data)) {
-            data = URL.createObjectURL(data);
-        } else if (isURL(data)) {
-            // nothing to do
-        } else if (isString(data)) {
-            // nothing to do
-        } else {
-            this.dispatchEvent(
-                new CustomEvent("viewer-error", {detail: "Blob or URL expected"}),
-            );
-            throw new Error("Blob or URL expected");
-        }
-
-        this.setOption(
-            "content",
-            `<img style="max-width: 100%" src="${data}" alt="image" part="image" onerror="this.dispatchEvent(new CustomEvent('viewer-error', {detail: 'Image loading error'}));">`,
-        );
-    }
-
-    /**
-     *
-     * if the data is a string, it is interpreted as HTML.
-     * if the data is a URL, the HTML is loaded from the url and set as content.
-     * if the data is an HTMLElement, the outerHTML is used as content.
-     *
-     * @param {HTMLElement|URL|string|Blob} data
-     */
-    setHTML(data) {
-        if (data instanceof Blob) {
-            blobToText(data)
-                .then((html) => {
-                    this.setOption("content", html);
-                })
-                .catch((error) => {
-                    this.dispatchEvent(
-                        new CustomEvent("viewer-error", {detail: error}),
-                    );
-                    throw new Error(error);
-                });
-
-            return;
-        } else if (data instanceof HTMLElement) {
-            data = data.outerHTML;
-        } else if (isString(data)) {
-            // nothing to do
-        } else if (isURL(data)) {
-            // fetch element
-            getGlobal()
-                .fetch(data)
-                .then((response) => {
-                    return response.text();
-                })
-                .then((html) => {
-                    this.setOption("content", html);
-                })
-                .catch((error) => {
-                    this.dispatchEvent(
-                        new CustomEvent("viewer-error", {detail: error}),
-                    );
-                    throw new Error(error);
-                });
-        } else {
-            this.dispatchEvent(
-                new CustomEvent("viewer-error", {
-                    detail: "HTMLElement or string expected",
-                }),
-            );
-            throw new Error("HTMLElement or string expected");
-        }
-
-        this.setOption("content", data);
-    }
-
-    /**
-     * Sets the plain text content by processing the input data, which can be of various types, including Blob,
-     * HTMLElement, string, or a valid URL. The method extracts and sets the raw text content into a predefined option.
-     *
-     * @param {Blob|HTMLElement|string} data - The input data to be processed. It can be a Blob object, an HTMLElement,
-     * a plain string, or a string formatted as a valid URL. The method determines
-     * the data type and processes it accordingly.
-     * @return {void} - This method does not return any value. It processes the content and updates the relevant option
-     * property.
-     */
-    setPlainText(data) {
-        const mkPreSpan = (text) => {
-            const pre = document.createElement("pre");
-            pre.innerText = text;
-            pre.setAttribute("part", "text");
-            return pre.outerHTML;
-        };
-
-        if (data instanceof Blob) {
-            blobToText(data)
-                .then((text) => {
-                    const div = document.createElement("div");
-                    div.innerHTML = text;
-                    text = div.innerText;
-
-                    this.setOption("content", mkPreSpan(text));
-                })
-                .catch((error) => {
-                    this.dispatchEvent(
-                        new CustomEvent("viewer-error", {detail: error}),
-                    );
-                    throw new Error(error);
-                });
-
-            return;
-        } else if (data instanceof HTMLElement) {
-            data = data.outerText;
-        } else if (isString(data)) {
-            const div = document.createElement("div");
-            div.innerHTML = data;
-            data = div.innerText;
-        } else if (isURL(data)) {
-            getGlobal()
-                .fetch(data)
-                .then((response) => {
-                    return response.text();
-                })
-                .then((text) => {
-                    const div = document.createElement("div");
-                    div.innerHTML = text;
-                    text = div.innerText;
-
-                    this.setOption("content", mkPreSpan(text));
-                })
-                .catch((error) => {
-                    this.dispatchEvent(
-                        new CustomEvent("viewer-error", {detail: error}),
-                    );
-                    throw new Error(error);
-                });
-        } else {
-            this.dispatchEvent(
-                new CustomEvent("viewer-error", {
-                    detail: "HTMLElement or string expected",
-                }),
-            );
-            throw new Error("HTMLElement or string expected");
-        }
-
-        this.setOption("content", mkPreSpan(data));
-    }
-
-    /**
-     *
-     * @return {Viewer}
-     */
-    [assembleMethodSymbol]() {
-        super[assembleMethodSymbol]();
-
-        initControlReferences.call(this);
-        initEventHandler.call(this);
-    }
-
-    /**
-     *
-     * @return {string}
-     */
-    static getTag() {
-        return "monster-viewer";
-    }
-
-    /**
-     * @return {CSSStyleSheet[]}
-     */
-    static getCSSStyleSheet() {
-        return [ViewerStyleSheet];
-    }
+		);
+	}
+
+	/**
+	 * Renders Markdown content using built-in or custom Markdown parser.
+	 * Overrideable via `customRenderers['text/markdown']`.
+	 *
+	 * @param {string|Blob} data
+	 */
+	setMarkdown(data) {
+		if (isBlob(data)) {
+			blobToText(data)
+				.then((markdownText) => {
+					try {
+						const html = MarkdownToHTML.convert(markdownText);
+						this.setHTML(html);
+					} catch (error) {
+						this.setPlainText(markdownText); // Fallback to plain text if conversion fails
+						this.dispatchEvent(
+							new CustomEvent("viewer-error", { detail: error }),
+						);
+					}
+				})
+				.catch((error) => {
+					this.dispatchEvent(
+						new CustomEvent("viewer-error", { detail: error }),
+					);
+					throw new Error(error);
+				});
+			return;
+		} else if (isURL(data)) {
+			getGlobal()
+				.fetch(data)
+				.then((response) => {
+					return response.text();
+				})
+				.then((markdownText) => {
+					try {
+						const html = MarkdownToHTML.convert(markdownText);
+						this.setHTML(html);
+					} catch (error) {
+						this.setPlainText(markdownText); // Fallback to plain text if conversion fails
+						this.dispatchEvent(
+							new CustomEvent("viewer-error", { detail: error }),
+						);
+					}
+				})
+				.catch((error) => {
+					this.dispatchEvent(
+						new CustomEvent("viewer-error", { detail: error }),
+					);
+					throw new Error(error);
+				});
+			return;
+		} else if (isString(data)) {
+			try {
+				const html = MarkdownToHTML.convert(data);
+				this.setHTML(html);
+			} catch (error) {
+				this.setPlainText(data); // Fallback to plain text if conversion fails
+				this.dispatchEvent(new CustomEvent("viewer-error", { detail: error }));
+			}
+			return;
+		}
+
+		this.dispatchEvent(
+			new CustomEvent("viewer-error", { detail: "Blob or string expected" }),
+		);
+		throw new Error("Blob or string expected");
+	}
+
+	/**
+	 * Configures and embeds a PDF document into the application with customizable display settings.
+	 *
+	 * @param {Blob|URL|string} data The PDF data to be embedded. Can be provided as a Blob, URL, or base64 string.
+	 * @param {boolean} [navigation=true] Determines whether the navigation pane is displayed in the PDF viewer.
+	 * @param {boolean} [toolbar=true] Controls the visibility of the toolbar in the PDF viewer.
+	 * @param {boolean} [scrollbar=false] Configures the display of the scrollbar in the PDF viewer.
+	 * @return {void} This method returns nothing but sets the embedded PDF as the content.
+	 */
+	setPDF(data, navigation = true, toolbar = true, scrollbar = false) {
+		const hashes =
+			"#toolbar=" +
+			(toolbar ? "1" : "0") +
+			"&navpanes=" +
+			(navigation ? "1" : "0") +
+			"&scrollbar=" +
+			(scrollbar ? "1" : "0");
+
+		let pdfURL = "";
+		if (isBlob(data)) {
+			pdfURL = URL.createObjectURL(data);
+			pdfURL += hashes;
+		} else if (isURL(data)) {
+			// check if the url already contains the hashes
+			if (data?.hash?.indexOf("#") === -1) {
+				pdfURL = data.toString() + hashes;
+			} else {
+				pdfURL = data.toString();
+			}
+		} else if (isString(data)) {
+			//URL.createObjectURL(data);
+			const blobObj = new Blob([atob(data)], { type: "application/pdf" });
+			const url = window.URL.createObjectURL(blobObj);
+
+			pdfURL = data;
+		} else {
+			this.dispatchEvent(
+				new CustomEvent("viewer-error", { detail: "Blob or URL expected" }),
+			);
+			throw new Error("Blob or URL expected");
+		}
+
+		const html =
+			'<object part="pdf" data="' +
+			pdfURL +
+			'" width="100%" height="100%" type="application/pdf"></object>';
+
+		this.setOption("content", html);
+	}
+
+	/**
+	 * Sets the download functionality for the viewer.
+	 * @param data
+	 * @param filename
+	 */
+	setDownload(data, filename = "download") {
+		const rawData = data;
+
+		if (isBlob(data)) {
+			data = URL.createObjectURL(data);
+		} else if (isURL(data)) {
+			// nothing to do
+		} else if (isString(data)) {
+			// nothing to do
+		} else {
+			this.dispatchEvent(
+				new CustomEvent("viewer-error", { detail: "Blob or URL expected" }),
+			);
+			throw new Error("Blob or URL expected");
+		}
+
+		const button =
+			`<monster-button data-monster-role="download">` +
+			this.getOption("labels.download") +
+			`</monster-button>`;
+		this.setOption("content", button);
+
+		this.addEventListener("click", (event) => {
+			const element = findTargetElementFromEvent(
+				event,
+				"data-monster-role",
+				"download",
+			);
+			if (element instanceof Button) {
+				const anchor = document.createElement("a");
+				anchor.href = URL.createObjectURL(new Blob([rawData]));
+				anchor.download = filename;
+				anchor.style.display = "none";
+				document.body.appendChild(anchor);
+				anchor.click();
+				document.body.removeChild(anchor);
+			}
+		});
+	}
+
+	/**
+	 * Sets the content for displaying an email message.
+	 * The data is expected to be an object with a structure containing
+	 * 'to', 'from', 'subject', 'parts', and 'headers'.
+	 * The parts are processed to display plain text and HTML in separate tabs,
+	 * and attachments are listed.
+	 *
+	 * @param {object} emailData - The structured email data.
+	 */
+	setMessage(emailData) {
+		if (!emailData || typeof emailData !== "object") {
+			this.dispatchEvent(
+				new CustomEvent("viewer-error", {
+					detail: "Invalid email data provided",
+				}),
+			);
+			return;
+		}
+
+		this.setOption(
+			"content",
+			'<monster-message-content part="message"></monster-message-content>',
+		);
+
+		setTimeout(() => {
+			const messageContent = this.shadowRoot.querySelector(
+				"monster-message-content",
+			);
+			if (!messageContent) {
+				this.dispatchEvent(
+					new CustomEvent("viewer-error", {
+						detail: "Message content element not found",
+					}),
+				);
+				return;
+			}
+
+			messageContent.setMessage(emailData);
+		}, 100);
+	}
+
+	/**
+	 * Sets an image for the target by accepting a blob, URL, or string representation of the image.
+	 *
+	 * @param {(Blob|string)} data - The image data, which can be a Blob, a valid URL, or a string representation of the image.
+	 * @return {void} Does not return a value.
+	 */
+	setImage(data) {
+		if (isBlob(data)) {
+			data = URL.createObjectURL(data);
+		} else if (isURL(data)) {
+			// nothing to do
+		} else if (isString(data)) {
+			// nothing to do
+		} else {
+			this.dispatchEvent(
+				new CustomEvent("viewer-error", { detail: "Blob or URL expected" }),
+			);
+			throw new Error("Blob or URL expected");
+		}
+
+		this.setOption(
+			"content",
+			`<img style="max-width: 100%" src="${data}" alt="image" part="image" onerror="this.dispatchEvent(new CustomEvent('viewer-error', {detail: 'Image loading error'}));">`,
+		);
+	}
+
+	/**
+	 *
+	 * if the data is a string, it is interpreted as HTML.
+	 * if the data is a URL, the HTML is loaded from the url and set as content.
+	 * if the data is an HTMLElement, the outerHTML is used as content.
+	 *
+	 * @param {HTMLElement|URL|string|Blob} data
+	 */
+	setHTML(data) {
+		if (data instanceof Blob) {
+			blobToText(data)
+				.then((html) => {
+					this.setOption("content", html);
+				})
+				.catch((error) => {
+					this.dispatchEvent(
+						new CustomEvent("viewer-error", { detail: error }),
+					);
+					throw new Error(error);
+				});
+
+			return;
+		} else if (data instanceof HTMLElement) {
+			data = data.outerHTML;
+		} else if (isString(data)) {
+			// nothing to do
+		} else if (isURL(data)) {
+			// fetch element
+			getGlobal()
+				.fetch(data)
+				.then((response) => {
+					return response.text();
+				})
+				.then((html) => {
+					this.setOption("content", html);
+				})
+				.catch((error) => {
+					this.dispatchEvent(
+						new CustomEvent("viewer-error", { detail: error }),
+					);
+					throw new Error(error);
+				});
+		} else {
+			this.dispatchEvent(
+				new CustomEvent("viewer-error", {
+					detail: "HTMLElement or string expected",
+				}),
+			);
+			throw new Error("HTMLElement or string expected");
+		}
+
+		this.setOption("content", data);
+	}
+
+	/**
+	 * Sets the plain text content by processing the input data, which can be of various types, including Blob,
+	 * HTMLElement, string, or a valid URL. The method extracts and sets the raw text content into a predefined option.
+	 *
+	 * @param {Blob|HTMLElement|string} data - The input data to be processed. It can be a Blob object, an HTMLElement,
+	 * a plain string, or a string formatted as a valid URL. The method determines
+	 * the data type and processes it accordingly.
+	 * @return {void} - This method does not return any value. It processes the content and updates the relevant option
+	 * property.
+	 */
+	setPlainText(data) {
+		const mkPreSpan = (text) => {
+			const pre = document.createElement("pre");
+			pre.innerText = text;
+			pre.setAttribute("part", "text");
+			return pre.outerHTML;
+		};
+
+		if (data instanceof Blob) {
+			blobToText(data)
+				.then((text) => {
+					const div = document.createElement("div");
+					div.innerHTML = text;
+					text = div.innerText;
+
+					this.setOption("content", mkPreSpan(text));
+				})
+				.catch((error) => {
+					this.dispatchEvent(
+						new CustomEvent("viewer-error", { detail: error }),
+					);
+					throw new Error(error);
+				});
+
+			return;
+		} else if (data instanceof HTMLElement) {
+			data = data.outerText;
+		} else if (isString(data)) {
+			const div = document.createElement("div");
+			div.innerHTML = data;
+			data = div.innerText;
+		} else if (isURL(data)) {
+			getGlobal()
+				.fetch(data)
+				.then((response) => {
+					return response.text();
+				})
+				.then((text) => {
+					const div = document.createElement("div");
+					div.innerHTML = text;
+					text = div.innerText;
+
+					this.setOption("content", mkPreSpan(text));
+				})
+				.catch((error) => {
+					this.dispatchEvent(
+						new CustomEvent("viewer-error", { detail: error }),
+					);
+					throw new Error(error);
+				});
+		} else {
+			this.dispatchEvent(
+				new CustomEvent("viewer-error", {
+					detail: "HTMLElement or string expected",
+				}),
+			);
+			throw new Error("HTMLElement or string expected");
+		}
+
+		this.setOption("content", mkPreSpan(data));
+	}
+
+	/**
+	 *
+	 * @return {Viewer}
+	 */
+	[assembleMethodSymbol]() {
+		super[assembleMethodSymbol]();
+
+		initControlReferences.call(this);
+		initEventHandler.call(this);
+	}
+
+	/**
+	 *
+	 * @return {string}
+	 */
+	static getTag() {
+		return "monster-viewer";
+	}
+
+	/**
+	 * @return {CSSStyleSheet[]}
+	 */
+	static getCSSStyleSheet() {
+		return [ViewerStyleSheet];
+	}
 }
 
 /**
@@ -710,12 +710,12 @@ class Viewer extends CustomElement {
  * @return {boolean}
  */
 function isURL(variable) {
-    try {
-        new URL(variable);
-        return true;
-    } catch (error) {
-        return false;
-    }
+	try {
+		new URL(variable);
+		return true;
+	} catch (error) {
+		return false;
+	}
 }
 
 /**
@@ -724,7 +724,7 @@ function isURL(variable) {
  * @return {boolean}
  */
 function isBlob(variable) {
-    return variable instanceof Blob;
+	return variable instanceof Blob;
 }
 
 /**
@@ -733,12 +733,12 @@ function isBlob(variable) {
  * @return {Promise<unknown>}
  */
 function blobToText(blob) {
-    return new Promise((resolve, reject) => {
-        const reader = new FileReader();
-        reader.onloadend = () => resolve(reader.result);
-        reader.onerror = reject;
-        reader.readAsText(blob);
-    });
+	return new Promise((resolve, reject) => {
+		const reader = new FileReader();
+		reader.onloadend = () => resolve(reader.result);
+		reader.onerror = reject;
+		reader.readAsText(blob);
+	});
 }
 
 /**
@@ -747,122 +747,122 @@ function blobToText(blob) {
  * @throws {Error} no shadow-root is defined
  */
 function initControlReferences() {
-    if (!this.shadowRoot) {
-        throw new Error("no shadow-root is defined");
-    }
+	if (!this.shadowRoot) {
+		throw new Error("no shadow-root is defined");
+	}
 
-    this[viewerElementSymbol] = this.shadowRoot.getElementById("viewer");
+	this[viewerElementSymbol] = this.shadowRoot.getElementById("viewer");
 }
 
 /**
  * @private
  */
 function initEventHandler() {
-    return this;
+	return this;
 }
 
 function getLabels() {
-    switch (getLocaleOfDocument().language) {
-        case "de": // German
-            return {
-                download: "Herunterladen",
-            };
-
-        case "es": // Spanish
-            return {
-                download: "Descargar",
-            };
-
-        case "zh": // Mandarin
-            return {
-                download: "下载",
-            };
-
-        case "hi": // Hindi
-            return {
-                download: "下载",
-            };
-
-        case "bn": // Bengali
-            return {
-                download: "ডাউনলোড",
-            };
-
-        case "pt": // Portuguese
-            return {
-                download: "Baixar",
-            };
-
-        case "ru": // Russian
-            return {
-                download: "Скачать",
-            };
-
-        case "ja": // Japanese
-            return {
-                download: "ダウンロード",
-            };
-
-        case "pa": // Western Punjabi
-            return {
-                download: "ਡਾਊਨਲੋਡ",
-            };
-
-        case "mr": // Marathi
-            return {
-                download: "डाउनलोड",
-            };
-
-        case "fr": // French
-            return {
-                download: "Télécharger",
-            };
-
-        case "it": // Italian
-            return {
-                download: "Scarica",
-            };
-
-        case "nl": // Dutch
-            return {
-                download: "Downloaden",
-            };
-
-        case "sv": // Swedish
-            return {
-                download: "Ladda ner",
-            };
-
-        case "pl": // Polish
-            return {
-                download: "Ściągnij",
-            };
-
-        case "da": // Danish
-            return {
-                download: "Lad ned",
-            };
-
-        case "fi": // Finnish
-            return {
-                download: "Lataa",
-            };
-
-        case "no": // Norwegian
-            return {
-                download: "Laste ned",
-            };
-
-        case "cs": // Czech
-            return {
-                download: "Stáhnout",
-            };
-
-        default:
-            return {
-                download: "Download",
-            };
-    }
+	switch (getLocaleOfDocument().language) {
+		case "de": // German
+			return {
+				download: "Herunterladen",
+			};
+
+		case "es": // Spanish
+			return {
+				download: "Descargar",
+			};
+
+		case "zh": // Mandarin
+			return {
+				download: "下载",
+			};
+
+		case "hi": // Hindi
+			return {
+				download: "下载",
+			};
+
+		case "bn": // Bengali
+			return {
+				download: "ডাউনলোড",
+			};
+
+		case "pt": // Portuguese
+			return {
+				download: "Baixar",
+			};
+
+		case "ru": // Russian
+			return {
+				download: "Скачать",
+			};
+
+		case "ja": // Japanese
+			return {
+				download: "ダウンロード",
+			};
+
+		case "pa": // Western Punjabi
+			return {
+				download: "ਡਾਊਨਲੋਡ",
+			};
+
+		case "mr": // Marathi
+			return {
+				download: "डाउनलोड",
+			};
+
+		case "fr": // French
+			return {
+				download: "Télécharger",
+			};
+
+		case "it": // Italian
+			return {
+				download: "Scarica",
+			};
+
+		case "nl": // Dutch
+			return {
+				download: "Downloaden",
+			};
+
+		case "sv": // Swedish
+			return {
+				download: "Ladda ner",
+			};
+
+		case "pl": // Polish
+			return {
+				download: "Ściągnij",
+			};
+
+		case "da": // Danish
+			return {
+				download: "Lad ned",
+			};
+
+		case "fi": // Finnish
+			return {
+				download: "Lataa",
+			};
+
+		case "no": // Norwegian
+			return {
+				download: "Laste ned",
+			};
+
+		case "cs": // Czech
+			return {
+				download: "Stáhnout",
+			};
+
+		default:
+			return {
+				download: "Download",
+			};
+	}
 }
 
 /**
@@ -870,8 +870,8 @@ function getLabels() {
  * @return {string}
  */
 function getTemplate() {
-    // language=HTML
-    return `
+	// language=HTML
+	return `
         <div id="viewer" data-monster-role="viewer" part="viewer"
              data-monster-replace="path:content"
              data-monster-attributes="class path:classes.viewer">