@schukai/monster 4.20.0 → 4.21.0

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 CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
 
4
4
 
5
+ ## [4.21.0] - 2025-06-22
6
+
7
+ ### Add Features
8
+
9
+ - Add Markdown parser and HTML converter [#324](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/324)
10
+
11
+
12
+
13
+ ## [4.20.1] - 2025-06-12
14
+
15
+ ### Bug Fixes
16
+
17
+ - update css styles
18
+ - update css collapse
19
+ ### Changes
20
+
21
+ - move issues [#323](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/323)
22
+
23
+
24
+
5
25
  ## [4.20.0] - 2025-06-11
6
26
 
7
27
  ### Add Features
package/package.json CHANGED
@@ -1 +1 @@
1
- {"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.7.1","@popperjs/core":"^2.11.8"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.20.0"}
1
+ {"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.7.1","@popperjs/core":"^2.11.8"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.21.0"}
@@ -23,6 +23,7 @@ import { instanceSymbol } from "../../constants.mjs";
23
23
  import { isString } from "../../types/is.mjs";
24
24
  import { getGlobal } from "../../types/global.mjs";
25
25
  import { MediaType, parseMediaType } from "../../types/mediatype.mjs";
26
+ import { MarkdownToHTML } from "../../text/markdown-parser.mjs";
26
27
 
27
28
  export { Viewer };
28
29
 
@@ -33,7 +34,7 @@ export { Viewer };
33
34
  const viewerElementSymbol = Symbol("viewerElement");
34
35
 
35
36
  /**
36
- * The Viewer component is used to show a PDF, HTML or Image.
37
+ * The Viewer component is used to show a PDF, HTML, or Image.
37
38
  *
38
39
  * @fragments /fragments/components/content/viewer
39
40
  *
@@ -42,7 +43,7 @@ const viewerElementSymbol = Symbol("viewerElement");
42
43
  * @example /examples/components/content/html-viewer with HTML content
43
44
  *
44
45
  * @copyright schukai GmbH
45
- * @summary A simple viewer component for PDF, HTML and images.
46
+ * @summary A simple viewer component for PDF, HTML, and images.
46
47
  */
47
48
  class Viewer extends CustomElement {
48
49
  /**
@@ -64,6 +65,12 @@ class Viewer extends CustomElement {
64
65
  * @property {string} content Content to be displayed in the viewer
65
66
  * @property {Object} classes Css classes
66
67
  * @property {string} classes.viewer Css class for the viewer
68
+ * @property {Object} renderers Renderers for different media types
69
+ * @property {function} renderers.image Function to render image content
70
+ * @property {function} renderers.html Function to render HTML content
71
+ * @property {function} renderers.pdf Function to render PDF content
72
+ * @property {function} renderers.plaintext Function to render plain text content
73
+ * @property {function} renderers.markdown Function to render Markdown content
67
74
  */
68
75
  get defaults() {
69
76
  return Object.assign({}, super.defaults, {
@@ -74,63 +81,93 @@ class Viewer extends CustomElement {
74
81
  classes: {
75
82
  viewer: "",
76
83
  },
84
+ renderers : {
85
+ image: this.setImage,
86
+ html: this.setHTML,
87
+ pdf: this.setPDF,
88
+ plaintext: this.setPlainText,
89
+ markdown: this.setMarkdown,
90
+ }
77
91
  });
78
92
  }
79
93
 
80
94
  /**
81
- * Sets the content of an element based on the provided content and media type.
95
+ * Sets the content of the viewer by processing `data` according to the specified `mediaType`.
96
+ * If no `mediaType` is provided, it defaults to "text/plain".
97
+ * The method uses an appropriate renderer based on the media type to process and display the content.
82
98
  *
83
- * @param {string} content - The content to be set.
84
- * @param {string} [mediaType="text/plain"] - The media type of the content. Defaults to "text/plain" if not specified.
85
- * @return {void} This method does not return a value.
86
- * @throws {Error} Throws an error if shadowRoot is not defined.
99
+ * @param {any} data - The content to be displayed in the viewer.
100
+ * @param {string} [mediaType="text/plain"] - The media type of the content to determine the appropriate renderer.
101
+ * @return {void} This method does not return a value, but processes the content or throws an exception on failure.
102
+ * @throws {Error} Throws an error if there is no shadow root defined or if there is an issue with rendering `data`.
87
103
  */
88
- setContent(content, mediaType = "text/plain") {
104
+ setContent(data, mediaType = "text/plain") {
89
105
  if (!this.shadowRoot) {
90
106
  throw new Error("no shadow-root is defined");
91
107
  }
92
108
 
93
- let type;
94
- try {
95
- const m = new parseMediaType(mediaType);
96
- switch (m.type) {
97
- case "image":
98
- return this.setImage(content);
99
- }
100
109
 
101
- mediaType = m.toString();
102
- } catch (error) {
103
- type = null;
110
+ let mt;
111
+ try {
112
+ mt = new parseMediaType(mediaType).toString();
113
+ } catch {
114
+ mt = "text/plain";
104
115
  }
116
+ if (!mt) mt = "text/plain";
117
+
118
+ const { renderers } = this.options;
119
+ const primaryType = mt.split("/")[0];
120
+ const renderer =
121
+ renderers[mt] ||
122
+ renderers[primaryType] ||
123
+ ((d) => this.setPlainText(d));
105
124
 
106
- if (mediaType === undefined || mediaType === null || mediaType === "") {
107
- mediaType = "text/plain";
125
+ try {
126
+ const result = renderer.call(this, data);
127
+ if (result && typeof result.then === "function") {
128
+ result.catch((err) => {
129
+ this.dispatchEvent(new CustomEvent("viewer-error", { detail: err }));
130
+ });
131
+ }
132
+ } catch (err) {
133
+ this.dispatchEvent(new CustomEvent("viewer-error", { detail: err }));
134
+ throw new Error(err);
108
135
  }
136
+ }
137
+
138
+ /**
139
+ * Renders Markdown content using built-in or custom Markdown parser.
140
+ * Overrideable via `customRenderers['text/markdown']`.
141
+ *
142
+ * @param {string|Blob} data
143
+ */
144
+ setMarkdown(data) {
145
+ const render = (markdownText) => {
146
+ try {
147
+ const html = MarkdownToHTML.convert(markdownText);
148
+ this.setHTML(html);
149
+ } catch (error) {
150
+ this.setPlainText(markdownText); // Fallback
151
+ }
152
+ };
109
153
 
110
- switch (mediaType) {
111
- case "text/html":
112
- this.setHTML(content);
113
- break;
114
- case "text/plain":
115
- this.setPlainText(content);
116
- break;
117
- case "application/pdf":
118
- this.setPDF(content);
119
- break;
120
- case "image/png":
121
- case "image/jpeg":
122
- case "image/gif":
123
- this.setImage(content);
124
- break;
125
- default:
126
- this.setOption("content", content);
154
+ if (data instanceof Blob) {
155
+ blobToText(data)
156
+ .then(render)
157
+ .catch((err) => {
158
+ this.setPlainText("[Invalid Markdown]");
159
+ });
160
+ } else if (isString(data)) {
161
+ render(data);
162
+ } else {
163
+ this.setPlainText("[Unsupported Markdown input]");
127
164
  }
128
165
  }
129
166
 
130
167
  /**
131
168
  * Configures and embeds a PDF document into the application with customizable display settings.
132
169
  *
133
- * @param {Blob|URL|string} data The PDF data to be embedded. Can be provided as a Blob, URL or base64 string.
170
+ * @param {Blob|URL|string} data The PDF data to be embedded. Can be provided as a Blob, URL, or base64 string.
134
171
  * @param {boolean} [navigation=true] Determines whether the navigation pane is displayed in the PDF viewer.
135
172
  * @param {boolean} [toolbar=true] Controls the visibility of the toolbar in the PDF viewer.
136
173
  * @param {boolean} [scrollbar=false] Configures the display of the scrollbar in the PDF viewer.
@@ -163,6 +200,7 @@ class Viewer extends CustomElement {
163
200
 
164
201
  pdfURL = data;
165
202
  } else {
203
+ this.dispatchEvent(new CustomEvent("viewer-error", { detail: "Blob or URL expected" }));
166
204
  throw new Error("Blob or URL expected");
167
205
  }
168
206
 
@@ -188,16 +226,20 @@ class Viewer extends CustomElement {
188
226
  } else if (isString(data)) {
189
227
  // nothing to do
190
228
  } else {
229
+ this.dispatchEvent(new CustomEvent("viewer-error", { detail: "Blob or URL expected" }));
191
230
  throw new Error("Blob or URL expected");
192
231
  }
193
232
 
194
- this.setOption("content", '<img src="' + data + '" alt="image" />');
233
+ this.setOption(
234
+ "content",
235
+ `<img style="max-width: 100%" src="${data}" alt="image" part="image">`
236
+ );
195
237
  }
196
238
 
197
239
  /**
198
240
  *
199
241
  * if the data is a string, it is interpreted as HTML.
200
- * if the data is an url, the HTML is loaded from the url and set as content.
242
+ * if the data is a URL, the HTML is loaded from the url and set as content.
201
243
  * if the data is an HTMLElement, the outerHTML is used as content.
202
244
  *
203
245
  * @param {HTMLElement|URL|string|Blob} data
@@ -209,6 +251,7 @@ class Viewer extends CustomElement {
209
251
  this.setOption("content", html);
210
252
  })
211
253
  .catch((error) => {
254
+ this.dispatchEvent(new CustomEvent("viewer-error", { detail: error }));
212
255
  throw new Error(error);
213
256
  });
214
257
 
@@ -228,9 +271,11 @@ class Viewer extends CustomElement {
228
271
  this.setOption("content", html);
229
272
  })
230
273
  .catch((error) => {
274
+ this.dispatchEvent(new CustomEvent("viewer-error", { detail: error }));
231
275
  throw new Error(error);
232
276
  });
233
277
  } else {
278
+ this.dispatchEvent(new CustomEvent("viewer-error", { detail: "HTMLElement or string expected" }));
234
279
  throw new Error("HTMLElement or string expected");
235
280
  }
236
281
 
@@ -265,6 +310,7 @@ class Viewer extends CustomElement {
265
310
  this.setOption("content", mkPreSpan(text));
266
311
  })
267
312
  .catch((error) => {
313
+ this.dispatchEvent(new CustomEvent("viewer-error", { detail: error }));
268
314
  throw new Error(error);
269
315
  });
270
316
 
@@ -289,9 +335,11 @@ class Viewer extends CustomElement {
289
335
  this.setOption("content", mkPreSpan(text));
290
336
  })
291
337
  .catch((error) => {
338
+ this.dispatchEvent(new CustomEvent("viewer-error", { detail: error }));
292
339
  throw new Error(error);
293
340
  });
294
341
  } else {
342
+ this.dispatchEvent(new CustomEvent("viewer-error", { detail: "HTMLElement or string expected" }));
295
343
  throw new Error("HTMLElement or string expected");
296
344
  }
297
345
 
@@ -23,9 +23,9 @@ import { clone } from "../../util/clone.mjs";
23
23
  import { ColumnBarStyleSheet } from "./stylesheet/column-bar.mjs";
24
24
  import { createPopper } from "@popperjs/core";
25
25
  import { getLocaleOfDocument } from "../../dom/locale.mjs";
26
- import {hasObjectLink} from "../../dom/attributes.mjs";
27
- import {customElementUpdaterLinkSymbol} from "../../dom/constants.mjs";
28
- import {getGlobalObject} from "../../types/global.mjs";
26
+ import { hasObjectLink } from "../../dom/attributes.mjs";
27
+ import { customElementUpdaterLinkSymbol } from "../../dom/constants.mjs";
28
+ import { getGlobalObject } from "../../types/global.mjs";
29
29
 
30
30
  export { ColumnBar };
31
31
 
@@ -89,10 +89,7 @@ class ColumnBar extends CustomElement {
89
89
  * @returns {Map<unknown, unknown>}
90
90
  */
91
91
  get customization() {
92
- return new Map([
93
- ...super.customization,
94
- ["templateFormatter.i18n", true],
95
- ]);
92
+ return new Map([...super.customization, ["templateFormatter.i18n", true]]);
96
93
  }
97
94
 
98
95
  /**
@@ -130,14 +127,23 @@ class ColumnBar extends CustomElement {
130
127
  const isOutsideElement = !path.includes(this);
131
128
  const isOutsideShadow = !path.includes(this.shadowRoot);
132
129
 
133
- if (isOutsideElement && isOutsideShadow && this[settingsLayerElementSymbol]) {
130
+ if (
131
+ isOutsideElement &&
132
+ isOutsideShadow &&
133
+ this[settingsLayerElementSymbol]
134
+ ) {
134
135
  this[settingsLayerElementSymbol].classList.remove("visible");
135
136
  }
136
- }
137
-
138
- getGlobalObject("document").addEventListener("click" , this[closeEventHandlerSymbol]);
139
- getGlobalObject("document").addEventListener("touch" , this[closeEventHandlerSymbol]);
137
+ };
140
138
 
139
+ getGlobalObject("document").addEventListener(
140
+ "click",
141
+ this[closeEventHandlerSymbol],
142
+ );
143
+ getGlobalObject("document").addEventListener(
144
+ "touch",
145
+ this[closeEventHandlerSymbol],
146
+ );
141
147
  }
142
148
 
143
149
  /**
@@ -149,12 +155,17 @@ class ColumnBar extends CustomElement {
149
155
  disconnectedCallback() {
150
156
  super.disconnectedCallback();
151
157
 
152
- if(this[closeEventHandlerSymbol]) {
153
- getGlobalObject("document").removeEventListener("click", this[closeEventHandlerSymbol]);
154
- getGlobalObject("document").removeEventListener("touch", this[closeEventHandlerSymbol]);
158
+ if (this[closeEventHandlerSymbol]) {
159
+ getGlobalObject("document").removeEventListener(
160
+ "click",
161
+ this[closeEventHandlerSymbol],
162
+ );
163
+ getGlobalObject("document").removeEventListener(
164
+ "touch",
165
+ this[closeEventHandlerSymbol],
166
+ );
155
167
  this[closeEventHandlerSymbol] = null;
156
168
  }
157
-
158
169
  }
159
170
 
160
171
  /**
@@ -1260,8 +1260,8 @@ function collectSearchQueries() {
1260
1260
  return "";
1261
1261
  }
1262
1262
 
1263
- if(!op || !isString(op)) op = "OR";
1264
- op = " "+op.toUpperCase().trim()+" ";
1263
+ if (!op || !isString(op)) op = "OR";
1264
+ op = " " + op.toUpperCase().trim() + " ";
1265
1265
 
1266
1266
  let query = "";
1267
1267
  value.forEach((v) => {
@@ -1282,8 +1282,8 @@ function collectSearchQueries() {
1282
1282
  return "";
1283
1283
  }
1284
1284
 
1285
- if(!op || !isString(op)) op = "OR";
1286
- op = " "+op.toUpperCase().trim()+" ";
1285
+ if (!op || !isString(op)) op = "OR";
1286
+ op = " " + op.toUpperCase().trim() + " ";
1287
1287
 
1288
1288
  let query = "";
1289
1289
  value.forEach((v) => {