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