@schukai/monster 4.25.1 → 4.25.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,24 +13,24 @@
13
13
  */
14
14
 
15
15
  import {
16
- assembleMethodSymbol,
17
- CustomElement,
18
- registerCustomElement,
16
+ assembleMethodSymbol,
17
+ CustomElement,
18
+ registerCustomElement,
19
19
  } from "../../dom/customelement.mjs";
20
20
  import "../notify/notify.mjs";
21
- import {ViewerStyleSheet} from "./stylesheet/viewer.mjs";
22
- import {instanceSymbol} from "../../constants.mjs";
23
- import {isString} from "../../types/is.mjs";
24
- import {getGlobal} from "../../types/global.mjs";
25
- import {MediaType, parseMediaType} from "../../types/mediatype.mjs";
26
- import {MarkdownToHTML} from "../../text/markdown-parser.mjs";
21
+ import { ViewerStyleSheet } from "./stylesheet/viewer.mjs";
22
+ import { instanceSymbol } from "../../constants.mjs";
23
+ import { isString } from "../../types/is.mjs";
24
+ import { getGlobal } from "../../types/global.mjs";
25
+ import { MediaType, parseMediaType } from "../../types/mediatype.mjs";
26
+ import { MarkdownToHTML } from "../../text/markdown-parser.mjs";
27
27
  import "../layout/tabs.mjs";
28
28
  import "./viewer/message.mjs";
29
- import {getLocaleOfDocument} from "../../dom/locale.mjs";
30
- import {Button} from "../form/button.mjs";
31
- import {findTargetElementFromEvent} from "../../dom/events.mjs";
29
+ import { getLocaleOfDocument } from "../../dom/locale.mjs";
30
+ import { Button } from "../form/button.mjs";
31
+ import { findTargetElementFromEvent } from "../../dom/events.mjs";
32
32
 
33
- export {Viewer};
33
+ export { Viewer };
34
34
 
35
35
  /**
36
36
  * @private
@@ -51,657 +51,657 @@ const viewerElementSymbol = Symbol("viewerElement");
51
51
  * @summary A simple viewer component for PDF, HTML, and images.
52
52
  */
53
53
  class Viewer extends CustomElement {
54
- /**
55
- * This method is called by the `instanceof` operator.
56
- * @return {symbol}
57
- */
58
- static get [instanceSymbol]() {
59
- return Symbol.for("@schukai/monster/components/content/viewer@@instance");
60
- }
61
-
62
- /**
63
- * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
64
- * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
65
- *
66
- * The individual configuration values can be found in the table.
67
- *
68
- * @property {Object} templates Template definitions
69
- * @property {string} templates.main Main template
70
- * @property {string} content Content to be displayed in the viewer
71
- * @property {Object} classes Css classes
72
- * @property {string} classes.viewer Css class for the viewer
73
- * @property {Object} renderers Renderers for different media types
74
- * @property {function} renderers.image Function to render image content
75
- * @property {function} renderers.html Function to render HTML content
76
- * @property {function} renderers.pdf Function to render PDF content
77
- * @property {function} renderers.plaintext Function to render plain text content
78
- * @property {function} renderers.markdown Function to render Markdown content
79
- */
80
- get defaults() {
81
- return Object.assign({}, super.defaults, {
82
- templates: {
83
- main: getTemplate(),
84
- },
85
- content: "<slot></slot>",
86
- classes: {
87
- viewer: "",
88
- },
89
- labels: getLabels(),
90
- renderers: {
91
- image: this.setImage,
92
- html: this.setHTML,
93
- pdf: this.setPDF,
94
- download: this.setDownload,
95
- plaintext: this.setPlainText,
96
- markdown: this.setMarkdown,
97
- audio: this.setAudio,
98
- video: this.setVideo,
99
- message: this.setMessage,
100
- },
101
-
102
- });
103
- }
104
-
105
- /**
106
- * Sets the content of an element based on the provided content and media type.
107
- *
108
- * @param {string} content - The content to be set.
109
- * @param {string} [mediaType="text/plain"] - The media type of the content. Defaults to "text/plain" if not specified.
110
- * @return {void} This method does not return a value.
111
- * @throws {Error} Throws an error if shadowRoot is not defined.
112
- */
113
- setContent(content, mediaType = "text/plain") {
114
- if (!this.shadowRoot) {
115
- throw new Error("no shadow-root is defined");
116
- }
117
-
118
- const renderers = this.getOption("renderers");
119
-
120
- const isDataURL = (value) => {
121
- return (
122
- (typeof value === "string" && value.startsWith("data:")) ||
123
- (value instanceof URL && value.protocol === "data:")
124
- );
125
- };
126
-
127
- if (isDataURL(content)) {
128
- try {
129
- const dataUrl = content.toString();
130
- const [header] = dataUrl.split(",");
131
- const [typeSegment] = header.split(";");
132
- mediaType = typeSegment.replace("data:", "") || "text/plain";
133
- } catch (error) {
134
- this.dispatchEvent(
135
- new CustomEvent("viewer-error", {
136
- detail: "Invalid data URL format",
137
- }),
138
- );
139
- return;
140
- }
141
- }
142
-
143
- if (mediaType === undefined || mediaType === null || mediaType === "") {
144
- mediaType = "text/plain";
145
- }
146
-
147
- let mediaTypeObject;
148
-
149
- try {
150
- mediaTypeObject = new parseMediaType(mediaType);
151
- if (!(mediaTypeObject instanceof MediaType)) {
152
- this.dispatchEvent(
153
- new CustomEvent("viewer-error", {detail: "Invalid MediaType"}),
154
- );
155
- return;
156
- }
157
- } catch (error) {
158
- this.dispatchEvent(new CustomEvent("viewer-error", {detail: error}));
159
- return;
160
- }
161
-
162
- const checkRenderer = (renderer, contentType) => {
163
- if (renderers && typeof renderers[renderer] === "function") {
164
- return true;
165
- } else {
166
- this.dispatchEvent(
167
- new CustomEvent("viewer-error", {
168
- detail: `Renderer for ${contentType} not found`,
169
- }),
170
- );
171
- return false;
172
- }
173
- };
174
-
175
- switch (mediaTypeObject.type) {
176
- case "text":
177
- switch (mediaTypeObject.subtype) {
178
- case "html":
179
- if (checkRenderer("html", mediaTypeObject.toString())) {
180
- renderers.html.call(this, content);
181
- }
182
- break;
183
- case "plain":
184
- if (checkRenderer("plaintext", mediaTypeObject.toString())) {
185
- renderers.plaintext.call(this, content);
186
- }
187
- break;
188
- case "markdown":
189
- if (checkRenderer("markdown", mediaTypeObject.toString())) {
190
- this.setMarkdown(content);
191
- }
192
- break;
193
- default:
194
- if (checkRenderer("plaintext", mediaTypeObject.toString())) {
195
- renderers.plaintext.call(this, content);
196
- }
197
- break;
198
- }
199
- break;
200
-
201
- case "application":
202
- switch (mediaTypeObject.subtype) {
203
- case "pdf":
204
- if (checkRenderer("pdf", mediaTypeObject.toString())) {
205
- renderers.pdf.call(this, content);
206
- }
207
- break;
208
-
209
- default:
210
-
211
- // Handle octet-stream as a generic binary data type
212
- if (checkRenderer("download", mediaTypeObject.toString())) {
213
- renderers.download.call(this, content);
214
- break;
215
- }
216
-
217
- this.setOption("content", content + "!!");
218
- break;
219
- }
220
- break;
221
-
222
- case "message":
223
- switch (mediaTypeObject.subtype) {
224
- case "rfc822":
225
- if (checkRenderer("message", mediaTypeObject.toString())) {
226
- renderers.message.call(this, content);
227
- }
228
- break;
229
-
230
- default:
231
- this.setOption("content", content);
232
- break;
233
- }
234
- break;
235
-
236
- case "audio":
237
- if (checkRenderer(mediaTypeObject.type, mediaTypeObject.toString())) {
238
- renderers[mediaTypeObject.type].call(this, content);
239
- }
240
- break;
241
-
242
- case "video":
243
- if (checkRenderer(mediaTypeObject.type, mediaTypeObject.toString())) {
244
- renderers[mediaTypeObject.type].call(this, content);
245
- }
246
- break;
247
-
248
- case "image":
249
- if (checkRenderer("image", mediaTypeObject.toString())) {
250
- renderers.image.call(this, content);
251
- }
252
- break;
253
-
254
- default:
255
- this.setOption("content", content);
256
- this.dispatchEvent(
257
- new CustomEvent("viewer-error", {
258
- detail: `Unsupported media type: ${mediaTypeObject.toString()}`,
259
- }),
260
- ); // Notify about unsupported media type
261
- return;
262
- }
263
- }
264
-
265
- /**
266
- * Sets the audio content for the viewer. Accepts a Blob, URL, or string and processes it
267
- * to configure audio playback within the viewer. Throws an error if the input type is invalid.
268
- *
269
- * @param {Blob|string} data - The audio content. This can be a Blob, a URL, or a string.
270
- * @return {void} No return value.
271
- */
272
- setAudio(data) {
273
- if (isBlob(data)) {
274
- data = URL.createObjectURL(data);
275
- } else if (isURL(data)) {
276
- // nothing to do
277
- } else if (isString(data)) {
278
- // nothing to do
279
- } else {
280
- this.dispatchEvent(
281
- new CustomEvent("viewer-error", {detail: "Blob or URL expected"}),
282
- );
283
- throw new Error("Blob or URL expected");
284
- }
285
-
286
- this.setOption(
287
- "content",
288
- `
54
+ /**
55
+ * This method is called by the `instanceof` operator.
56
+ * @return {symbol}
57
+ */
58
+ static get [instanceSymbol]() {
59
+ return Symbol.for("@schukai/monster/components/content/viewer@@instance");
60
+ }
61
+
62
+ /**
63
+ * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
64
+ * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
65
+ *
66
+ * The individual configuration values can be found in the table.
67
+ *
68
+ * @property {Object} templates Template definitions
69
+ * @property {string} templates.main Main template
70
+ * @property {string} content Content to be displayed in the viewer
71
+ * @property {Object} classes Css classes
72
+ * @property {string} classes.viewer Css class for the viewer
73
+ * @property {Object} renderers Renderers for different media types
74
+ * @property {function} renderers.image Function to render image content
75
+ * @property {function} renderers.html Function to render HTML content
76
+ * @property {function} renderers.pdf Function to render PDF content
77
+ * @property {function} renderers.plaintext Function to render plain text content
78
+ * @property {function} renderers.markdown Function to render Markdown content
79
+ */
80
+ get defaults() {
81
+ return Object.assign({}, super.defaults, {
82
+ templates: {
83
+ main: getTemplate(),
84
+ },
85
+ content: "<slot></slot>",
86
+ classes: {
87
+ viewer: "",
88
+ },
89
+ labels: getLabels(),
90
+ renderers: {
91
+ image: this.setImage,
92
+ html: this.setHTML,
93
+ pdf: this.setPDF,
94
+ download: this.setDownload,
95
+ plaintext: this.setPlainText,
96
+ markdown: this.setMarkdown,
97
+ audio: this.setAudio,
98
+ video: this.setVideo,
99
+ message: this.setMessage,
100
+ },
101
+ });
102
+ }
103
+
104
+ /**
105
+ * Sets the content of an element based on the provided content and media type.
106
+ *
107
+ * @param {string} content - The content to be set.
108
+ * @param {string} [mediaType="text/plain"] - The media type of the content. Defaults to "text/plain" if not specified.
109
+ * @return {void} This method does not return a value.
110
+ * @throws {Error} Throws an error if shadowRoot is not defined.
111
+ */
112
+ setContent(content, mediaType = "text/plain") {
113
+ if (!this.shadowRoot) {
114
+ throw new Error("no shadow-root is defined");
115
+ }
116
+
117
+ const renderers = this.getOption("renderers");
118
+
119
+ const isDataURL = (value) => {
120
+ return (
121
+ (typeof value === "string" && value.startsWith("data:")) ||
122
+ (value instanceof URL && value.protocol === "data:")
123
+ );
124
+ };
125
+
126
+ if (isDataURL(content)) {
127
+ try {
128
+ const dataUrl = content.toString();
129
+ const [header] = dataUrl.split(",");
130
+ const [typeSegment] = header.split(";");
131
+ mediaType = typeSegment.replace("data:", "") || "text/plain";
132
+ } catch (error) {
133
+ this.dispatchEvent(
134
+ new CustomEvent("viewer-error", {
135
+ detail: "Invalid data URL format",
136
+ }),
137
+ );
138
+ return;
139
+ }
140
+ }
141
+
142
+ if (mediaType === undefined || mediaType === null || mediaType === "") {
143
+ mediaType = "text/plain";
144
+ }
145
+
146
+ let mediaTypeObject;
147
+
148
+ try {
149
+ mediaTypeObject = new parseMediaType(mediaType);
150
+ if (!(mediaTypeObject instanceof MediaType)) {
151
+ this.dispatchEvent(
152
+ new CustomEvent("viewer-error", { detail: "Invalid MediaType" }),
153
+ );
154
+ return;
155
+ }
156
+ } catch (error) {
157
+ this.dispatchEvent(new CustomEvent("viewer-error", { detail: error }));
158
+ return;
159
+ }
160
+
161
+ const checkRenderer = (renderer, contentType) => {
162
+ if (renderers && typeof renderers[renderer] === "function") {
163
+ return true;
164
+ } else {
165
+ this.dispatchEvent(
166
+ new CustomEvent("viewer-error", {
167
+ detail: `Renderer for ${contentType} not found`,
168
+ }),
169
+ );
170
+ return false;
171
+ }
172
+ };
173
+
174
+ switch (mediaTypeObject.type) {
175
+ case "text":
176
+ switch (mediaTypeObject.subtype) {
177
+ case "html":
178
+ if (checkRenderer("html", mediaTypeObject.toString())) {
179
+ renderers.html.call(this, content);
180
+ }
181
+ break;
182
+ case "plain":
183
+ if (checkRenderer("plaintext", mediaTypeObject.toString())) {
184
+ renderers.plaintext.call(this, content);
185
+ }
186
+ break;
187
+ case "markdown":
188
+ if (checkRenderer("markdown", mediaTypeObject.toString())) {
189
+ this.setMarkdown(content);
190
+ }
191
+ break;
192
+ default:
193
+ if (checkRenderer("plaintext", mediaTypeObject.toString())) {
194
+ renderers.plaintext.call(this, content);
195
+ }
196
+ break;
197
+ }
198
+ break;
199
+
200
+ case "application":
201
+ switch (mediaTypeObject.subtype) {
202
+ case "pdf":
203
+ if (checkRenderer("pdf", mediaTypeObject.toString())) {
204
+ renderers.pdf.call(this, content);
205
+ }
206
+ break;
207
+
208
+ default:
209
+ // Handle octet-stream as a generic binary data type
210
+ if (checkRenderer("download", mediaTypeObject.toString())) {
211
+ renderers.download.call(this, content);
212
+ break;
213
+ }
214
+
215
+ this.setOption("content", content);
216
+ break;
217
+ }
218
+ break;
219
+
220
+ case "message":
221
+ switch (mediaTypeObject.subtype) {
222
+ case "rfc822":
223
+ if (checkRenderer("message", mediaTypeObject.toString())) {
224
+ renderers.message.call(this, content);
225
+ }
226
+ break;
227
+
228
+ default:
229
+ this.setOption("content", content);
230
+ break;
231
+ }
232
+ break;
233
+
234
+ case "audio":
235
+ if (checkRenderer(mediaTypeObject.type, mediaTypeObject.toString())) {
236
+ renderers[mediaTypeObject.type].call(this, content);
237
+ }
238
+ break;
239
+
240
+ case "video":
241
+ if (checkRenderer(mediaTypeObject.type, mediaTypeObject.toString())) {
242
+ renderers[mediaTypeObject.type].call(this, content);
243
+ }
244
+ break;
245
+
246
+ case "image":
247
+ if (checkRenderer("image", mediaTypeObject.toString())) {
248
+ renderers.image.call(this, content);
249
+ }
250
+ break;
251
+
252
+ default:
253
+ this.setOption("content", content);
254
+ this.dispatchEvent(
255
+ new CustomEvent("viewer-error", {
256
+ detail: `Unsupported media type: ${mediaTypeObject.toString()}`,
257
+ }),
258
+ ); // Notify about unsupported media type
259
+ return;
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Sets the audio content for the viewer. Accepts a Blob, URL, or string and processes it
265
+ * to configure audio playback within the viewer. Throws an error if the input type is invalid.
266
+ *
267
+ * @param {Blob|string} data - The audio content. This can be a Blob, a URL, or a string.
268
+ * @return {void} No return value.
269
+ */
270
+ setAudio(data) {
271
+ if (isBlob(data)) {
272
+ data = URL.createObjectURL(data);
273
+ } else if (isURL(data)) {
274
+ // nothing to do
275
+ } else if (isString(data)) {
276
+ // nothing to do
277
+ } else {
278
+ this.dispatchEvent(
279
+ new CustomEvent("viewer-error", { detail: "Blob or URL expected" }),
280
+ );
281
+ throw new Error("Blob or URL expected");
282
+ }
283
+
284
+ this.setOption(
285
+ "content",
286
+ `
289
287
  <audio controls part="audio" style="max-width: 100%">
290
288
  <source src="${data}">
291
289
  </audio>`,
292
- );
293
- }
294
-
295
- /**
296
- * Sets the video content for the viewer. The method accepts a Blob, URL, or string,
297
- * verifies its type, and updates the viewer's content accordingly.
298
- *
299
- * @param {Blob|string} data - The video data to set. It can be a Blob, URL, or string.
300
- * @return {void} This method does not return a value. It updates the viewer's state.
301
- * @throws {Error} Throws an error if the provided data is not a Blob or URL.
302
- */
303
- setVideo(data) {
304
- if (isBlob(data)) {
305
- data = URL.createObjectURL(data);
306
- } else if (isURL(data)) {
307
- // nothing to do
308
- } else if (isString(data)) {
309
- // nothing to do
310
- } else {
311
- this.dispatchEvent(
312
- new CustomEvent("viewer-error", {detail: "Blob or URL expected"}),
313
- );
314
- throw new Error("Blob or URL expected");
315
- }
316
-
317
- this.setOption(
318
- "content",
319
- `
290
+ );
291
+ }
292
+
293
+ /**
294
+ * Sets the video content for the viewer. The method accepts a Blob, URL, or string,
295
+ * verifies its type, and updates the viewer's content accordingly.
296
+ *
297
+ * @param {Blob|string} data - The video data to set. It can be a Blob, URL, or string.
298
+ * @return {void} This method does not return a value. It updates the viewer's state.
299
+ * @throws {Error} Throws an error if the provided data is not a Blob or URL.
300
+ */
301
+ setVideo(data) {
302
+ if (isBlob(data)) {
303
+ data = URL.createObjectURL(data);
304
+ } else if (isURL(data)) {
305
+ // nothing to do
306
+ } else if (isString(data)) {
307
+ // nothing to do
308
+ } else {
309
+ this.dispatchEvent(
310
+ new CustomEvent("viewer-error", { detail: "Blob or URL expected" }),
311
+ );
312
+ throw new Error("Blob or URL expected");
313
+ }
314
+
315
+ this.setOption(
316
+ "content",
317
+ `
320
318
  <video controls part="video" style="max-width: 100%">
321
319
  <source src="${data}">
322
320
  </video>`,
323
- );
324
- }
325
-
326
- /**
327
- * Renders Markdown content using built-in or custom Markdown parser.
328
- * Overrideable via `customRenderers['text/markdown']`.
329
- *
330
- * @param {string|Blob} data
331
- */
332
- setMarkdown(data) {
333
- if (isBlob(data)) {
334
- blobToText(data)
335
- .then((markdownText) => {
336
- try {
337
- const html = MarkdownToHTML.convert(markdownText);
338
- this.setHTML(html);
339
- } catch (error) {
340
- this.setPlainText(markdownText); // Fallback to plain text if conversion fails
341
- this.dispatchEvent(
342
- new CustomEvent("viewer-error", {detail: error}),
343
- );
344
- }
345
- })
346
- .catch((error) => {
347
- this.dispatchEvent(
348
- new CustomEvent("viewer-error", {detail: error}),
349
- );
350
- throw new Error(error);
351
- });
352
- return;
353
- } else if (isURL(data)) {
354
- getGlobal()
355
- .fetch(data)
356
- .then((response) => {
357
- return response.text();
358
- })
359
- .then((markdownText) => {
360
- try {
361
- const html = MarkdownToHTML.convert(markdownText);
362
- this.setHTML(html);
363
- } catch (error) {
364
- this.setPlainText(markdownText); // Fallback to plain text if conversion fails
365
- this.dispatchEvent(
366
- new CustomEvent("viewer-error", {detail: error}),
367
- );
368
- }
369
- })
370
- .catch((error) => {
371
- this.dispatchEvent(
372
- new CustomEvent("viewer-error", {detail: error}),
373
- );
374
- throw new Error(error);
375
- });
376
- return;
377
- } else if (isString(data)) {
378
- try {
379
- const html = MarkdownToHTML.convert(data);
380
- this.setHTML(html);
381
- } catch (error) {
382
- this.setPlainText(data); // Fallback to plain text if conversion fails
383
- this.dispatchEvent(new CustomEvent("viewer-error", {detail: error}));
384
- }
385
- return;
386
- }
387
-
388
- this.dispatchEvent(
389
- new CustomEvent("viewer-error", {detail: "Blob or string expected"}),
390
- );
391
- throw new Error("Blob or string expected");
392
- }
393
-
394
- /**
395
- * Configures and embeds a PDF document into the application with customizable display settings.
396
- *
397
- * @param {Blob|URL|string} data The PDF data to be embedded. Can be provided as a Blob, URL, or base64 string.
398
- * @param {boolean} [navigation=true] Determines whether the navigation pane is displayed in the PDF viewer.
399
- * @param {boolean} [toolbar=true] Controls the visibility of the toolbar in the PDF viewer.
400
- * @param {boolean} [scrollbar=false] Configures the display of the scrollbar in the PDF viewer.
401
- * @return {void} This method returns nothing but sets the embedded PDF as the content.
402
- */
403
- setPDF(data, navigation = true, toolbar = true, scrollbar = false) {
404
- const hashes =
405
- "#toolbar=" +
406
- (toolbar ? "1" : "0") +
407
- "&navpanes=" +
408
- (navigation ? "1" : "0") +
409
- "&scrollbar=" +
410
- (scrollbar ? "1" : "0");
411
-
412
- let pdfURL = "";
413
- if (isBlob(data)) {
414
- pdfURL = URL.createObjectURL(data);
415
- pdfURL += hashes;
416
- } else if (isURL(data)) {
417
- // check if the url already contains the hashes
418
- if (data?.hash?.indexOf("#") === -1) {
419
- pdfURL = data.toString() + hashes;
420
- } else {
421
- pdfURL = data.toString();
422
- }
423
- } else if (isString(data)) {
424
- //URL.createObjectURL(data);
425
- const blobObj = new Blob([atob(data)], {type: "application/pdf"});
426
- const url = window.URL.createObjectURL(blobObj);
427
-
428
- pdfURL = data;
429
- } else {
430
- this.dispatchEvent(
431
- new CustomEvent("viewer-error", {detail: "Blob or URL expected"}),
432
- );
433
- throw new Error("Blob or URL expected");
434
- }
435
-
436
- const html =
437
- '<object part="pdf" data="' +
438
- pdfURL +
439
- '" width="100%" height="100%" type="application/pdf"></object>';
440
-
441
- this.setOption("content", html);
442
- }
443
-
444
- /**
445
- * Sets the download functionality for the viewer.
446
- * @param data
447
- * @param filename
448
- */
449
- setDownload(data, filename = "download") {
450
-
451
- const rawData = data;
452
-
453
- if (isBlob(data)) {
454
- data = URL.createObjectURL(data);
455
- } else if (isURL(data)) {
456
- // nothing to do
457
- } else if (isString(data)) {
458
- // nothing to do
459
- } else {
460
- this.dispatchEvent(
461
- new CustomEvent("viewer-error", {detail: "Blob or URL expected"}),
462
- );
463
- throw new Error("Blob or URL expected");
464
- }
465
-
466
- const button = `<monster-button data-monster-role="download">` + this.getOption('labels.download') + `</monster-button>`;
467
- this.setOption("content", button);
468
-
469
- this.addEventListener("click", (event) => {
470
-
471
- const element = findTargetElementFromEvent(event, "data-monster-role", "download");
472
- if (element instanceof Button) {
473
- const anchor = document.createElement("a");
474
- anchor.href = URL.createObjectURL(new Blob([rawData]))
475
- anchor.download = filename;
476
- anchor.style.display = "none";
477
- document.body.appendChild(anchor);
478
- anchor.click();
479
- document.body.removeChild(anchor);
480
- }
481
-
482
-
483
- })
484
-
485
- }
486
-
487
- /**
488
- * Sets the content for displaying an email message.
489
- * The data is expected to be an object with a structure containing
490
- * 'to', 'from', 'subject', 'parts', and 'headers'.
491
- * The parts are processed to display plain text and HTML in separate tabs,
492
- * and attachments are listed.
493
- *
494
- * @param {object} emailData - The structured email data.
495
- */
496
- setMessage(emailData) {
497
- if (!emailData || typeof emailData !== "object") {
498
- this.dispatchEvent(
499
- new CustomEvent("viewer-error", {
500
- detail: "Invalid email data provided",
501
- }),
502
- );
503
- return;
504
- }
505
-
506
- this.setOption(
507
- "content",
508
- '<monster-message-content part="message"></monster-message-content>',
509
- );
510
-
511
- setTimeout(() => {
512
- const messageContent = this.shadowRoot.querySelector(
513
- "monster-message-content",
514
- );
515
- if (!messageContent) {
516
- this.dispatchEvent(
517
- new CustomEvent("viewer-error", {
518
- detail: "Message content element not found",
519
- }),
520
- );
521
- return;
522
- }
523
-
524
- messageContent.setMessage(emailData);
525
- }, 100);
526
- }
527
-
528
- /**
529
- * Sets an image for the target by accepting a blob, URL, or string representation of the image.
530
- *
531
- * @param {(Blob|string)} data - The image data, which can be a Blob, a valid URL, or a string representation of the image.
532
- * @return {void} Does not return a value.
533
- */
534
- setImage(data) {
535
- if (isBlob(data)) {
536
- data = URL.createObjectURL(data);
537
- } else if (isURL(data)) {
538
- // nothing to do
539
- } else if (isString(data)) {
540
- // nothing to do
541
- } else {
542
- this.dispatchEvent(
543
- new CustomEvent("viewer-error", {detail: "Blob or URL expected"}),
544
- );
545
- throw new Error("Blob or URL expected");
546
- }
547
-
548
- this.setOption(
549
- "content",
550
- `<img style="max-width: 100%" src="${data}" alt="image" part="image" onerror="this.dispatchEvent(new CustomEvent('viewer-error', {detail: 'Image loading error'}));">`,
551
- );
552
- }
553
-
554
- /**
555
- *
556
- * if the data is a string, it is interpreted as HTML.
557
- * if the data is a URL, the HTML is loaded from the url and set as content.
558
- * if the data is an HTMLElement, the outerHTML is used as content.
559
- *
560
- * @param {HTMLElement|URL|string|Blob} data
561
- */
562
- setHTML(data) {
563
- if (data instanceof Blob) {
564
- blobToText(data)
565
- .then((html) => {
566
- this.setOption("content", html);
567
- })
568
- .catch((error) => {
569
- this.dispatchEvent(
570
- new CustomEvent("viewer-error", {detail: error}),
571
- );
572
- throw new Error(error);
573
- });
574
-
575
- return;
576
- } else if (data instanceof HTMLElement) {
577
- data = data.outerHTML;
578
- } else if (isString(data)) {
579
- // nothing to do
580
- } else if (isURL(data)) {
581
- // fetch element
582
- getGlobal()
583
- .fetch(data)
584
- .then((response) => {
585
- return response.text();
586
- })
587
- .then((html) => {
588
- this.setOption("content", html);
589
- })
590
- .catch((error) => {
591
- this.dispatchEvent(
592
- new CustomEvent("viewer-error", {detail: error}),
593
- );
594
- throw new Error(error);
595
- });
596
- } else {
597
- this.dispatchEvent(
598
- new CustomEvent("viewer-error", {
599
- detail: "HTMLElement or string expected",
600
- }),
601
- );
602
- throw new Error("HTMLElement or string expected");
603
- }
604
-
605
- this.setOption("content", data);
606
- }
607
-
608
- /**
609
- * Sets the plain text content by processing the input data, which can be of various types, including Blob,
610
- * HTMLElement, string, or a valid URL. The method extracts and sets the raw text content into a predefined option.
611
- *
612
- * @param {Blob|HTMLElement|string} data - The input data to be processed. It can be a Blob object, an HTMLElement,
613
- * a plain string, or a string formatted as a valid URL. The method determines
614
- * the data type and processes it accordingly.
615
- * @return {void} - This method does not return any value. It processes the content and updates the relevant option
616
- * property.
617
- */
618
- setPlainText(data) {
619
- const mkPreSpan = (text) => {
620
- const pre = document.createElement("pre");
621
- pre.innerText = text;
622
- pre.setAttribute("part", "text");
623
- return pre.outerHTML;
624
- };
625
-
626
- if (data instanceof Blob) {
627
- blobToText(data)
628
- .then((text) => {
629
- const div = document.createElement("div");
630
- div.innerHTML = text;
631
- text = div.innerText;
632
-
633
- this.setOption("content", mkPreSpan(text));
634
- })
635
- .catch((error) => {
636
- this.dispatchEvent(
637
- new CustomEvent("viewer-error", {detail: error}),
638
- );
639
- throw new Error(error);
640
- });
641
-
642
- return;
643
- } else if (data instanceof HTMLElement) {
644
- data = data.outerText;
645
- } else if (isString(data)) {
646
- const div = document.createElement("div");
647
- div.innerHTML = data;
648
- data = div.innerText;
649
- } else if (isURL(data)) {
650
- getGlobal()
651
- .fetch(data)
652
- .then((response) => {
653
- return response.text();
654
- })
655
- .then((text) => {
656
- const div = document.createElement("div");
657
- div.innerHTML = text;
658
- text = div.innerText;
659
-
660
- this.setOption("content", mkPreSpan(text));
661
- })
662
- .catch((error) => {
663
- this.dispatchEvent(
664
- new CustomEvent("viewer-error", {detail: error}),
665
- );
666
- throw new Error(error);
667
- });
668
- } else {
669
- this.dispatchEvent(
670
- new CustomEvent("viewer-error", {
671
- detail: "HTMLElement or string expected",
672
- }),
673
- );
674
- throw new Error("HTMLElement or string expected");
675
- }
676
-
677
- this.setOption("content", mkPreSpan(data));
678
- }
679
-
680
- /**
681
- *
682
- * @return {Viewer}
683
- */
684
- [assembleMethodSymbol]() {
685
- super[assembleMethodSymbol]();
686
-
687
- initControlReferences.call(this);
688
- initEventHandler.call(this);
689
- }
690
-
691
- /**
692
- *
693
- * @return {string}
694
- */
695
- static getTag() {
696
- return "monster-viewer";
697
- }
698
-
699
- /**
700
- * @return {CSSStyleSheet[]}
701
- */
702
- static getCSSStyleSheet() {
703
- return [ViewerStyleSheet];
704
- }
321
+ );
322
+ }
323
+
324
+ /**
325
+ * Renders Markdown content using built-in or custom Markdown parser.
326
+ * Overrideable via `customRenderers['text/markdown']`.
327
+ *
328
+ * @param {string|Blob} data
329
+ */
330
+ setMarkdown(data) {
331
+ if (isBlob(data)) {
332
+ blobToText(data)
333
+ .then((markdownText) => {
334
+ try {
335
+ const html = MarkdownToHTML.convert(markdownText);
336
+ this.setHTML(html);
337
+ } catch (error) {
338
+ this.setPlainText(markdownText); // Fallback to plain text if conversion fails
339
+ this.dispatchEvent(
340
+ new CustomEvent("viewer-error", { detail: error }),
341
+ );
342
+ }
343
+ })
344
+ .catch((error) => {
345
+ this.dispatchEvent(
346
+ new CustomEvent("viewer-error", { detail: error }),
347
+ );
348
+ throw new Error(error);
349
+ });
350
+ return;
351
+ } else if (isURL(data)) {
352
+ getGlobal()
353
+ .fetch(data)
354
+ .then((response) => {
355
+ return response.text();
356
+ })
357
+ .then((markdownText) => {
358
+ try {
359
+ const html = MarkdownToHTML.convert(markdownText);
360
+ this.setHTML(html);
361
+ } catch (error) {
362
+ this.setPlainText(markdownText); // Fallback to plain text if conversion fails
363
+ this.dispatchEvent(
364
+ new CustomEvent("viewer-error", { detail: error }),
365
+ );
366
+ }
367
+ })
368
+ .catch((error) => {
369
+ this.dispatchEvent(
370
+ new CustomEvent("viewer-error", { detail: error }),
371
+ );
372
+ throw new Error(error);
373
+ });
374
+ return;
375
+ } else if (isString(data)) {
376
+ try {
377
+ const html = MarkdownToHTML.convert(data);
378
+ this.setHTML(html);
379
+ } catch (error) {
380
+ this.setPlainText(data); // Fallback to plain text if conversion fails
381
+ this.dispatchEvent(new CustomEvent("viewer-error", { detail: error }));
382
+ }
383
+ return;
384
+ }
385
+
386
+ this.dispatchEvent(
387
+ new CustomEvent("viewer-error", { detail: "Blob or string expected" }),
388
+ );
389
+ throw new Error("Blob or string expected");
390
+ }
391
+
392
+ /**
393
+ * Configures and embeds a PDF document into the application with customizable display settings.
394
+ *
395
+ * @param {Blob|URL|string} data The PDF data to be embedded. Can be provided as a Blob, URL, or base64 string.
396
+ * @param {boolean} [navigation=true] Determines whether the navigation pane is displayed in the PDF viewer.
397
+ * @param {boolean} [toolbar=true] Controls the visibility of the toolbar in the PDF viewer.
398
+ * @param {boolean} [scrollbar=false] Configures the display of the scrollbar in the PDF viewer.
399
+ * @return {void} This method returns nothing but sets the embedded PDF as the content.
400
+ */
401
+ setPDF(data, navigation = true, toolbar = true, scrollbar = false) {
402
+ const hashes =
403
+ "#toolbar=" +
404
+ (toolbar ? "1" : "0") +
405
+ "&navpanes=" +
406
+ (navigation ? "1" : "0") +
407
+ "&scrollbar=" +
408
+ (scrollbar ? "1" : "0");
409
+
410
+ let pdfURL = "";
411
+ if (isBlob(data)) {
412
+ pdfURL = URL.createObjectURL(data);
413
+ pdfURL += hashes;
414
+ } else if (isURL(data)) {
415
+ // check if the url already contains the hashes
416
+ if (data?.hash?.indexOf("#") === -1) {
417
+ pdfURL = data.toString() + hashes;
418
+ } else {
419
+ pdfURL = data.toString();
420
+ }
421
+ } else if (isString(data)) {
422
+ //URL.createObjectURL(data);
423
+ const blobObj = new Blob([atob(data)], { type: "application/pdf" });
424
+ const url = window.URL.createObjectURL(blobObj);
425
+
426
+ pdfURL = data;
427
+ } else {
428
+ this.dispatchEvent(
429
+ new CustomEvent("viewer-error", { detail: "Blob or URL expected" }),
430
+ );
431
+ throw new Error("Blob or URL expected");
432
+ }
433
+
434
+ const html =
435
+ '<object part="pdf" data="' +
436
+ pdfURL +
437
+ '" width="100%" height="100%" type="application/pdf"></object>';
438
+
439
+ this.setOption("content", html);
440
+ }
441
+
442
+ /**
443
+ * Sets the download functionality for the viewer.
444
+ * @param data
445
+ * @param filename
446
+ */
447
+ setDownload(data, filename = "download") {
448
+ const rawData = data;
449
+
450
+ if (isBlob(data)) {
451
+ data = URL.createObjectURL(data);
452
+ } else if (isURL(data)) {
453
+ // nothing to do
454
+ } else if (isString(data)) {
455
+ // nothing to do
456
+ } else {
457
+ this.dispatchEvent(
458
+ new CustomEvent("viewer-error", { detail: "Blob or URL expected" }),
459
+ );
460
+ throw new Error("Blob or URL expected");
461
+ }
462
+
463
+ const button =
464
+ `<monster-button data-monster-role="download">` +
465
+ this.getOption("labels.download") +
466
+ `</monster-button>`;
467
+ this.setOption("content", button);
468
+
469
+ this.addEventListener("click", (event) => {
470
+ const element = findTargetElementFromEvent(
471
+ event,
472
+ "data-monster-role",
473
+ "download",
474
+ );
475
+ if (element instanceof Button) {
476
+ const anchor = document.createElement("a");
477
+ anchor.href = URL.createObjectURL(new Blob([rawData]));
478
+ anchor.download = filename;
479
+ anchor.style.display = "none";
480
+ document.body.appendChild(anchor);
481
+ anchor.click();
482
+ document.body.removeChild(anchor);
483
+ }
484
+ });
485
+ }
486
+
487
+ /**
488
+ * Sets the content for displaying an email message.
489
+ * The data is expected to be an object with a structure containing
490
+ * 'to', 'from', 'subject', 'parts', and 'headers'.
491
+ * The parts are processed to display plain text and HTML in separate tabs,
492
+ * and attachments are listed.
493
+ *
494
+ * @param {object} emailData - The structured email data.
495
+ */
496
+ setMessage(emailData) {
497
+ if (!emailData || typeof emailData !== "object") {
498
+ this.dispatchEvent(
499
+ new CustomEvent("viewer-error", {
500
+ detail: "Invalid email data provided",
501
+ }),
502
+ );
503
+ return;
504
+ }
505
+
506
+ this.setOption(
507
+ "content",
508
+ '<monster-message-content part="message"></monster-message-content>',
509
+ );
510
+
511
+ setTimeout(() => {
512
+ const messageContent = this.shadowRoot.querySelector(
513
+ "monster-message-content",
514
+ );
515
+ if (!messageContent) {
516
+ this.dispatchEvent(
517
+ new CustomEvent("viewer-error", {
518
+ detail: "Message content element not found",
519
+ }),
520
+ );
521
+ return;
522
+ }
523
+
524
+ messageContent.setMessage(emailData);
525
+ }, 100);
526
+ }
527
+
528
+ /**
529
+ * Sets an image for the target by accepting a blob, URL, or string representation of the image.
530
+ *
531
+ * @param {(Blob|string)} data - The image data, which can be a Blob, a valid URL, or a string representation of the image.
532
+ * @return {void} Does not return a value.
533
+ */
534
+ setImage(data) {
535
+ if (isBlob(data)) {
536
+ data = URL.createObjectURL(data);
537
+ } else if (isURL(data)) {
538
+ // nothing to do
539
+ } else if (isString(data)) {
540
+ // nothing to do
541
+ } else {
542
+ this.dispatchEvent(
543
+ new CustomEvent("viewer-error", { detail: "Blob or URL expected" }),
544
+ );
545
+ throw new Error("Blob or URL expected");
546
+ }
547
+
548
+ this.setOption(
549
+ "content",
550
+ `<img style="max-width: 100%" src="${data}" alt="image" part="image" onerror="this.dispatchEvent(new CustomEvent('viewer-error', {detail: 'Image loading error'}));">`,
551
+ );
552
+ }
553
+
554
+ /**
555
+ *
556
+ * if the data is a string, it is interpreted as HTML.
557
+ * if the data is a URL, the HTML is loaded from the url and set as content.
558
+ * if the data is an HTMLElement, the outerHTML is used as content.
559
+ *
560
+ * @param {HTMLElement|URL|string|Blob} data
561
+ */
562
+ setHTML(data) {
563
+ if (data instanceof Blob) {
564
+ blobToText(data)
565
+ .then((html) => {
566
+ this.setOption("content", html);
567
+ })
568
+ .catch((error) => {
569
+ this.dispatchEvent(
570
+ new CustomEvent("viewer-error", { detail: error }),
571
+ );
572
+ throw new Error(error);
573
+ });
574
+
575
+ return;
576
+ } else if (data instanceof HTMLElement) {
577
+ data = data.outerHTML;
578
+ } else if (isString(data)) {
579
+ // nothing to do
580
+ } else if (isURL(data)) {
581
+ // fetch element
582
+ getGlobal()
583
+ .fetch(data)
584
+ .then((response) => {
585
+ return response.text();
586
+ })
587
+ .then((html) => {
588
+ this.setOption("content", html);
589
+ })
590
+ .catch((error) => {
591
+ this.dispatchEvent(
592
+ new CustomEvent("viewer-error", { detail: error }),
593
+ );
594
+ throw new Error(error);
595
+ });
596
+ } else {
597
+ this.dispatchEvent(
598
+ new CustomEvent("viewer-error", {
599
+ detail: "HTMLElement or string expected",
600
+ }),
601
+ );
602
+ throw new Error("HTMLElement or string expected");
603
+ }
604
+
605
+ this.setOption("content", data);
606
+ }
607
+
608
+ /**
609
+ * Sets the plain text content by processing the input data, which can be of various types, including Blob,
610
+ * HTMLElement, string, or a valid URL. The method extracts and sets the raw text content into a predefined option.
611
+ *
612
+ * @param {Blob|HTMLElement|string} data - The input data to be processed. It can be a Blob object, an HTMLElement,
613
+ * a plain string, or a string formatted as a valid URL. The method determines
614
+ * the data type and processes it accordingly.
615
+ * @return {void} - This method does not return any value. It processes the content and updates the relevant option
616
+ * property.
617
+ */
618
+ setPlainText(data) {
619
+ const mkPreSpan = (text) => {
620
+ const pre = document.createElement("pre");
621
+ pre.innerText = text;
622
+ pre.setAttribute("part", "text");
623
+ return pre.outerHTML;
624
+ };
625
+
626
+ if (data instanceof Blob) {
627
+ blobToText(data)
628
+ .then((text) => {
629
+ const div = document.createElement("div");
630
+ div.innerHTML = text;
631
+ text = div.innerText;
632
+
633
+ this.setOption("content", mkPreSpan(text));
634
+ })
635
+ .catch((error) => {
636
+ this.dispatchEvent(
637
+ new CustomEvent("viewer-error", { detail: error }),
638
+ );
639
+ throw new Error(error);
640
+ });
641
+
642
+ return;
643
+ } else if (data instanceof HTMLElement) {
644
+ data = data.outerText;
645
+ } else if (isString(data)) {
646
+ const div = document.createElement("div");
647
+ div.innerHTML = data;
648
+ data = div.innerText;
649
+ } else if (isURL(data)) {
650
+ getGlobal()
651
+ .fetch(data)
652
+ .then((response) => {
653
+ return response.text();
654
+ })
655
+ .then((text) => {
656
+ const div = document.createElement("div");
657
+ div.innerHTML = text;
658
+ text = div.innerText;
659
+
660
+ this.setOption("content", mkPreSpan(text));
661
+ })
662
+ .catch((error) => {
663
+ this.dispatchEvent(
664
+ new CustomEvent("viewer-error", { detail: error }),
665
+ );
666
+ throw new Error(error);
667
+ });
668
+ } else {
669
+ this.dispatchEvent(
670
+ new CustomEvent("viewer-error", {
671
+ detail: "HTMLElement or string expected",
672
+ }),
673
+ );
674
+ throw new Error("HTMLElement or string expected");
675
+ }
676
+
677
+ this.setOption("content", mkPreSpan(data));
678
+ }
679
+
680
+ /**
681
+ *
682
+ * @return {Viewer}
683
+ */
684
+ [assembleMethodSymbol]() {
685
+ super[assembleMethodSymbol]();
686
+
687
+ initControlReferences.call(this);
688
+ initEventHandler.call(this);
689
+ }
690
+
691
+ /**
692
+ *
693
+ * @return {string}
694
+ */
695
+ static getTag() {
696
+ return "monster-viewer";
697
+ }
698
+
699
+ /**
700
+ * @return {CSSStyleSheet[]}
701
+ */
702
+ static getCSSStyleSheet() {
703
+ return [ViewerStyleSheet];
704
+ }
705
705
  }
706
706
 
707
707
  /**
@@ -710,12 +710,12 @@ class Viewer extends CustomElement {
710
710
  * @return {boolean}
711
711
  */
712
712
  function isURL(variable) {
713
- try {
714
- new URL(variable);
715
- return true;
716
- } catch (error) {
717
- return false;
718
- }
713
+ try {
714
+ new URL(variable);
715
+ return true;
716
+ } catch (error) {
717
+ return false;
718
+ }
719
719
  }
720
720
 
721
721
  /**
@@ -724,7 +724,7 @@ function isURL(variable) {
724
724
  * @return {boolean}
725
725
  */
726
726
  function isBlob(variable) {
727
- return variable instanceof Blob;
727
+ return variable instanceof Blob;
728
728
  }
729
729
 
730
730
  /**
@@ -733,12 +733,12 @@ function isBlob(variable) {
733
733
  * @return {Promise<unknown>}
734
734
  */
735
735
  function blobToText(blob) {
736
- return new Promise((resolve, reject) => {
737
- const reader = new FileReader();
738
- reader.onloadend = () => resolve(reader.result);
739
- reader.onerror = reject;
740
- reader.readAsText(blob);
741
- });
736
+ return new Promise((resolve, reject) => {
737
+ const reader = new FileReader();
738
+ reader.onloadend = () => resolve(reader.result);
739
+ reader.onerror = reject;
740
+ reader.readAsText(blob);
741
+ });
742
742
  }
743
743
 
744
744
  /**
@@ -747,122 +747,122 @@ function blobToText(blob) {
747
747
  * @throws {Error} no shadow-root is defined
748
748
  */
749
749
  function initControlReferences() {
750
- if (!this.shadowRoot) {
751
- throw new Error("no shadow-root is defined");
752
- }
750
+ if (!this.shadowRoot) {
751
+ throw new Error("no shadow-root is defined");
752
+ }
753
753
 
754
- this[viewerElementSymbol] = this.shadowRoot.getElementById("viewer");
754
+ this[viewerElementSymbol] = this.shadowRoot.getElementById("viewer");
755
755
  }
756
756
 
757
757
  /**
758
758
  * @private
759
759
  */
760
760
  function initEventHandler() {
761
- return this;
761
+ return this;
762
762
  }
763
763
 
764
764
  function getLabels() {
765
- switch (getLocaleOfDocument().language) {
766
- case "de": // German
767
- return {
768
- download: "Herunterladen",
769
- };
770
-
771
- case "es": // Spanish
772
- return {
773
- download: "Descargar",
774
- };
775
-
776
- case "zh": // Mandarin
777
- return {
778
- download: "下载",
779
- };
780
-
781
- case "hi": // Hindi
782
- return {
783
- download: "下载",
784
- };
785
-
786
- case "bn": // Bengali
787
- return {
788
- download: "ডাউনলোড",
789
- };
790
-
791
- case "pt": // Portuguese
792
- return {
793
- download: "Baixar",
794
- };
795
-
796
- case "ru": // Russian
797
- return {
798
- download: "Скачать",
799
- };
800
-
801
- case "ja": // Japanese
802
- return {
803
- download: "ダウンロード",
804
- };
805
-
806
- case "pa": // Western Punjabi
807
- return {
808
- download: "ਡਾਊਨਲੋਡ",
809
- };
810
-
811
- case "mr": // Marathi
812
- return {
813
- download: "डाउनलोड",
814
- };
815
-
816
- case "fr": // French
817
- return {
818
- download: "Télécharger",
819
- };
820
-
821
- case "it": // Italian
822
- return {
823
- download: "Scarica",
824
- };
825
-
826
- case "nl": // Dutch
827
- return {
828
- download: "Downloaden",
829
- };
830
-
831
- case "sv": // Swedish
832
- return {
833
- download: "Ladda ner",
834
- };
835
-
836
- case "pl": // Polish
837
- return {
838
- download: "Ściągnij",
839
- };
840
-
841
- case "da": // Danish
842
- return {
843
- download: "Lad ned",
844
- };
845
-
846
- case "fi": // Finnish
847
- return {
848
- download: "Lataa",
849
- };
850
-
851
- case "no": // Norwegian
852
- return {
853
- download: "Laste ned",
854
- };
855
-
856
- case "cs": // Czech
857
- return {
858
- download: "Stáhnout",
859
- };
860
-
861
- default:
862
- return {
863
- download: "Download",
864
- };
865
- }
765
+ switch (getLocaleOfDocument().language) {
766
+ case "de": // German
767
+ return {
768
+ download: "Herunterladen",
769
+ };
770
+
771
+ case "es": // Spanish
772
+ return {
773
+ download: "Descargar",
774
+ };
775
+
776
+ case "zh": // Mandarin
777
+ return {
778
+ download: "下载",
779
+ };
780
+
781
+ case "hi": // Hindi
782
+ return {
783
+ download: "下载",
784
+ };
785
+
786
+ case "bn": // Bengali
787
+ return {
788
+ download: "ডাউনলোড",
789
+ };
790
+
791
+ case "pt": // Portuguese
792
+ return {
793
+ download: "Baixar",
794
+ };
795
+
796
+ case "ru": // Russian
797
+ return {
798
+ download: "Скачать",
799
+ };
800
+
801
+ case "ja": // Japanese
802
+ return {
803
+ download: "ダウンロード",
804
+ };
805
+
806
+ case "pa": // Western Punjabi
807
+ return {
808
+ download: "ਡਾਊਨਲੋਡ",
809
+ };
810
+
811
+ case "mr": // Marathi
812
+ return {
813
+ download: "डाउनलोड",
814
+ };
815
+
816
+ case "fr": // French
817
+ return {
818
+ download: "Télécharger",
819
+ };
820
+
821
+ case "it": // Italian
822
+ return {
823
+ download: "Scarica",
824
+ };
825
+
826
+ case "nl": // Dutch
827
+ return {
828
+ download: "Downloaden",
829
+ };
830
+
831
+ case "sv": // Swedish
832
+ return {
833
+ download: "Ladda ner",
834
+ };
835
+
836
+ case "pl": // Polish
837
+ return {
838
+ download: "Ściągnij",
839
+ };
840
+
841
+ case "da": // Danish
842
+ return {
843
+ download: "Lad ned",
844
+ };
845
+
846
+ case "fi": // Finnish
847
+ return {
848
+ download: "Lataa",
849
+ };
850
+
851
+ case "no": // Norwegian
852
+ return {
853
+ download: "Laste ned",
854
+ };
855
+
856
+ case "cs": // Czech
857
+ return {
858
+ download: "Stáhnout",
859
+ };
860
+
861
+ default:
862
+ return {
863
+ download: "Download",
864
+ };
865
+ }
866
866
  }
867
867
 
868
868
  /**
@@ -870,8 +870,8 @@ function getLabels() {
870
870
  * @return {string}
871
871
  */
872
872
  function getTemplate() {
873
- // language=HTML
874
- return `
873
+ // language=HTML
874
+ return `
875
875
  <div id="viewer" data-monster-role="viewer" part="viewer"
876
876
  data-monster-replace="path:content"
877
877
  data-monster-attributes="class path:classes.viewer">