@schukai/monster 4.23.6 → 4.24.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 +23 -0
- package/package.json +1 -1
- package/source/components/content/viewer/html.mjs +171 -0
- package/source/components/content/viewer/message.mjs +704 -0
- package/source/components/content/viewer/style/html.pcss +10 -0
- package/source/components/content/viewer/style/message.pcss +148 -0
- package/source/components/content/viewer/stylesheet/html.mjs +38 -0
- package/source/components/content/viewer/stylesheet/message.mjs +38 -0
- package/source/components/content/viewer.mjs +626 -522
- package/source/components/form/digits.mjs +0 -1
- package/source/components/form/select.mjs +2787 -2845
- package/source/components/form/style/select.pcss +0 -4
- package/source/components/form/stylesheet/select.mjs +14 -7
- package/source/components/form/util/floating-ui.mjs +2 -1
- package/source/components/layout/board.mjs +0 -5
- package/source/components/layout/panel.mjs +1 -1
- package/source/components/layout/popper.mjs +19 -10
- package/source/components/layout/tabs.mjs +17 -13
- package/source/components/navigation/table-of-content.mjs +0 -1
- package/source/components/tree-menu/style/tree-menu.pcss +1 -0
- package/source/components/tree-menu/stylesheet/tree-menu.mjs +1 -1
- package/source/dom/sanitize-html.mjs +54 -0
- package/source/monster.mjs +3 -0
- package/source/text/markdown-parser.mjs +253 -241
- package/source/types/version.mjs +1 -1
- package/test/cases/monster.mjs +1 -1
- package/test/web/import.js +1 -0
- package/test/web/test.html +2 -2
- package/test/web/tests.js +555 -149
@@ -20,245 +20,257 @@ export { MarkdownToHTML };
|
|
20
20
|
* and task list items.
|
21
21
|
*/
|
22
22
|
class MarkdownToHTML {
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
23
|
+
constructor(markdown, options = {}) {
|
24
|
+
this.markdown = markdown;
|
25
|
+
this.tokens = [];
|
26
|
+
|
27
|
+
this.options = {
|
28
|
+
taskListDisabled: true,
|
29
|
+
codeHighlightClassPrefix: "language-",
|
30
|
+
escapeHTML: true,
|
31
|
+
...options,
|
32
|
+
};
|
33
|
+
|
34
|
+
this._taskId = 0; // For unique checkbox IDs in task lists
|
35
|
+
}
|
36
|
+
|
37
|
+
/**
|
38
|
+
* Tokenizes the Markdown input into a structured array of tokens.
|
39
|
+
*/
|
40
|
+
tokenize() {
|
41
|
+
const lines = this.markdown.split("\n");
|
42
|
+
let inCodeBlock = false;
|
43
|
+
let codeBuffer = [];
|
44
|
+
let listBuffer = null;
|
45
|
+
let listType = null; // 'ul' or 'ol'
|
46
|
+
let codeLang = "";
|
47
|
+
|
48
|
+
for (const line of lines) {
|
49
|
+
const trimmed = line.trim();
|
50
|
+
|
51
|
+
// Detect start/end of fenced code blocks
|
52
|
+
const codeFenceMatch = trimmed.match(/^```(\w+)?/);
|
53
|
+
if (codeFenceMatch) {
|
54
|
+
if (inCodeBlock) {
|
55
|
+
this.tokens.push({
|
56
|
+
type: "code-block",
|
57
|
+
content: codeBuffer.join("\n"),
|
58
|
+
language: codeLang || null,
|
59
|
+
});
|
60
|
+
codeBuffer = [];
|
61
|
+
codeLang = "";
|
62
|
+
inCodeBlock = false;
|
63
|
+
} else {
|
64
|
+
this._flushList(listBuffer);
|
65
|
+
listBuffer = null;
|
66
|
+
listType = null;
|
67
|
+
codeLang = codeFenceMatch[1] || "";
|
68
|
+
inCodeBlock = true;
|
69
|
+
}
|
70
|
+
continue;
|
71
|
+
}
|
72
|
+
|
73
|
+
if (inCodeBlock) {
|
74
|
+
codeBuffer.push(line);
|
75
|
+
continue;
|
76
|
+
}
|
77
|
+
|
78
|
+
// Heading (e.g. #, ##, ###, etc.)
|
79
|
+
if (/^#{1,6}\s/.test(trimmed)) {
|
80
|
+
this._flushList(listBuffer);
|
81
|
+
listBuffer = null;
|
82
|
+
listType = null;
|
83
|
+
const level = trimmed.match(/^#+/)[0].length;
|
84
|
+
this.tokens.push({
|
85
|
+
type: "heading",
|
86
|
+
level,
|
87
|
+
content: trimmed.slice(level + 1).trim(),
|
88
|
+
});
|
89
|
+
continue;
|
90
|
+
}
|
91
|
+
|
92
|
+
// Ordered list item (e.g. 1. Item)
|
93
|
+
if (/^\d+\.\s+/.test(trimmed)) {
|
94
|
+
if (listType && listType !== "ol") {
|
95
|
+
this._flushList(listBuffer);
|
96
|
+
listBuffer = null;
|
97
|
+
}
|
98
|
+
listBuffer = listBuffer || { type: "ordered-list", items: [] };
|
99
|
+
listType = "ol";
|
100
|
+
listBuffer.items.push(trimmed.replace(/^\d+\.\s+/, ""));
|
101
|
+
continue;
|
102
|
+
}
|
103
|
+
|
104
|
+
// Unordered list item or task list (e.g. - Item, - [x] Task)
|
105
|
+
if (/^[-+*]\s+/.test(trimmed)) {
|
106
|
+
if (listType && listType !== "ul") {
|
107
|
+
this._flushList(listBuffer);
|
108
|
+
listBuffer = null;
|
109
|
+
}
|
110
|
+
listBuffer = listBuffer || { type: "unordered-list", items: [] };
|
111
|
+
listType = "ul";
|
112
|
+
|
113
|
+
const content = trimmed.replace(/^[-+*]\s+/, "");
|
114
|
+
const taskMatch = content.match(/^\[( |x|X)]\s+(.*)/);
|
115
|
+
|
116
|
+
if (taskMatch) {
|
117
|
+
listBuffer.items.push({
|
118
|
+
type: "task",
|
119
|
+
checked: taskMatch[1].toLowerCase() === "x",
|
120
|
+
content: taskMatch[2],
|
121
|
+
});
|
122
|
+
} else {
|
123
|
+
listBuffer.items.push(content);
|
124
|
+
}
|
125
|
+
continue;
|
126
|
+
}
|
127
|
+
|
128
|
+
// Blank line
|
129
|
+
if (trimmed === "") {
|
130
|
+
this._flushList(listBuffer);
|
131
|
+
listBuffer = null;
|
132
|
+
listType = null;
|
133
|
+
this.tokens.push({ type: "blank" });
|
134
|
+
continue;
|
135
|
+
}
|
136
|
+
|
137
|
+
// Plain paragraph
|
138
|
+
this._flushList(listBuffer);
|
139
|
+
listBuffer = null;
|
140
|
+
listType = null;
|
141
|
+
this.tokens.push({ type: "paragraph", content: trimmed });
|
142
|
+
}
|
143
|
+
|
144
|
+
// Flush any remaining list or code buffer at EOF
|
145
|
+
this._flushList(listBuffer);
|
146
|
+
if (inCodeBlock && codeBuffer.length > 0) {
|
147
|
+
this.tokens.push({
|
148
|
+
type: "code-block",
|
149
|
+
content: codeBuffer.join("\n"),
|
150
|
+
language: codeLang || null,
|
151
|
+
});
|
152
|
+
}
|
153
|
+
|
154
|
+
return this.tokens;
|
155
|
+
}
|
156
|
+
|
157
|
+
/**
|
158
|
+
* Pushes the current list buffer into the token stream if it's not empty.
|
159
|
+
*/
|
160
|
+
_flushList(listBuffer) {
|
161
|
+
if (
|
162
|
+
listBuffer &&
|
163
|
+
Array.isArray(listBuffer.items) &&
|
164
|
+
listBuffer.items.length > 0
|
165
|
+
) {
|
166
|
+
this.tokens.push(listBuffer);
|
167
|
+
}
|
168
|
+
}
|
169
|
+
|
170
|
+
/**
|
171
|
+
* Escapes HTML characters to prevent injection.
|
172
|
+
*/
|
173
|
+
static escapeHTML(text) {
|
174
|
+
return text
|
175
|
+
.replace(/&/g, "&")
|
176
|
+
.replace(/</g, "<")
|
177
|
+
.replace(/>/g, ">");
|
178
|
+
}
|
179
|
+
|
180
|
+
/**
|
181
|
+
* Filters out invalid characters in code language names.
|
182
|
+
*/
|
183
|
+
static safeCodeLang(lang) {
|
184
|
+
return typeof lang === "string"
|
185
|
+
? lang.replace(/[^a-zA-Z0-9\-_]/g, "").slice(0, 32)
|
186
|
+
: "";
|
187
|
+
}
|
188
|
+
|
189
|
+
/**
|
190
|
+
* Converts inline Markdown to HTML (bold, italic, code, links).
|
191
|
+
* Optionally escapes HTML.
|
192
|
+
*/
|
193
|
+
parseInline(text) {
|
194
|
+
let out = this.options.escapeHTML ? MarkdownToHTML.escapeHTML(text) : text;
|
195
|
+
|
196
|
+
// Markdown elements
|
197
|
+
out = out.replace(/\[([^\]]+)]\(([^)]+)\)/g, '<a href="$2">$1</a>'); // Links
|
198
|
+
out = out.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>"); // Bold
|
199
|
+
out = out.replace(/\*(.+?)\*/g, "<em>$1</em>"); // Italic
|
200
|
+
out = out.replace(/`(.+?)`/g, "<code>$1</code>"); // Inline code
|
201
|
+
return out;
|
202
|
+
}
|
203
|
+
|
204
|
+
/**
|
205
|
+
* Converts tokens to final HTML.
|
206
|
+
*/
|
207
|
+
render() {
|
208
|
+
if (this.tokens.length === 0) {
|
209
|
+
this.tokenize();
|
210
|
+
}
|
211
|
+
|
212
|
+
let html = "";
|
213
|
+
|
214
|
+
for (const token of this.tokens) {
|
215
|
+
switch (token.type) {
|
216
|
+
case "heading":
|
217
|
+
html += `<h${token.level}>${this.parseInline(token.content)}</h${token.level}>\n`;
|
218
|
+
break;
|
219
|
+
|
220
|
+
case "paragraph":
|
221
|
+
html += `<p>${this.parseInline(token.content)}</p>\n`;
|
222
|
+
break;
|
223
|
+
|
224
|
+
case "unordered-list":
|
225
|
+
html += "<ul>\n";
|
226
|
+
for (const item of token.items) {
|
227
|
+
if (typeof item === "string") {
|
228
|
+
html += ` <li>${this.parseInline(item)}</li>\n`;
|
229
|
+
} else if (item && item.type === "task") {
|
230
|
+
this._taskId += 1;
|
231
|
+
const inputId = `mdtask-${this._taskId}`;
|
232
|
+
const checked = item.checked ? " checked" : "";
|
233
|
+
const disabled = this.options.taskListDisabled ? " disabled" : "";
|
234
|
+
html += ` <li><input type="checkbox" id="${inputId}"${disabled}${checked}><label for="${inputId}"> ${this.parseInline(item.content)}</label></li>\n`;
|
235
|
+
}
|
236
|
+
}
|
237
|
+
html += "</ul>\n";
|
238
|
+
break;
|
239
|
+
|
240
|
+
case "ordered-list":
|
241
|
+
html += "<ol>\n";
|
242
|
+
for (const item of token.items) {
|
243
|
+
html += ` <li>${this.parseInline(item)}</li>\n`;
|
244
|
+
}
|
245
|
+
html += "</ol>\n";
|
246
|
+
break;
|
247
|
+
|
248
|
+
case "code-block": {
|
249
|
+
const safeLang = MarkdownToHTML.safeCodeLang(token.language);
|
250
|
+
const langClass = safeLang
|
251
|
+
? ` class="${this.options.codeHighlightClassPrefix}${safeLang}"`
|
252
|
+
: "";
|
253
|
+
const codeContent = this.options.escapeHTML
|
254
|
+
? MarkdownToHTML.escapeHTML(token.content)
|
255
|
+
: token.content;
|
256
|
+
html += `<pre><code${langClass}>${codeContent}</code></pre>\n`;
|
257
|
+
break;
|
258
|
+
}
|
259
|
+
|
260
|
+
case "blank":
|
261
|
+
html += "\n";
|
262
|
+
break;
|
263
|
+
}
|
264
|
+
}
|
265
|
+
|
266
|
+
return html.trim();
|
267
|
+
}
|
268
|
+
|
269
|
+
/**
|
270
|
+
* Convenience static method to render Markdown directly.
|
271
|
+
*/
|
272
|
+
static convert(markdown, options = {}) {
|
273
|
+
const converter = new MarkdownToHTML(markdown, options);
|
274
|
+
return converter.render();
|
275
|
+
}
|
264
276
|
}
|
package/source/types/version.mjs
CHANGED
package/test/cases/monster.mjs
CHANGED
package/test/web/import.js
CHANGED
@@ -21,6 +21,7 @@ import "../cases/components/host/details.mjs";
|
|
21
21
|
import "../cases/text/formatter.mjs";
|
22
22
|
import "../cases/text/generate-range-comparison-expression.mjs";
|
23
23
|
import "../cases/text/util.mjs";
|
24
|
+
import "../cases/text/markdown-parser.mjs";
|
24
25
|
import "../cases/text/bracketed-key-value-hash.mjs";
|
25
26
|
import "../cases/math/random.mjs";
|
26
27
|
import "../cases/util/trimspaces.mjs";
|
package/test/web/test.html
CHANGED
@@ -9,8 +9,8 @@
|
|
9
9
|
</head>
|
10
10
|
<body>
|
11
11
|
<div id="headline" style="display: flex;align-items: center;justify-content: center;flex-direction: column;">
|
12
|
-
<h1 style='margin-bottom: 0.1em;'>Monster 4.
|
13
|
-
<div id="lastupdate" style='font-size:0.7em'>last update
|
12
|
+
<h1 style='margin-bottom: 0.1em;'>Monster 4.23.6</h1>
|
13
|
+
<div id="lastupdate" style='font-size:0.7em'>last update Mi 25. Jun 11:28:48 CEST 2025</div>
|
14
14
|
</div>
|
15
15
|
<div id="mocha-errors"
|
16
16
|
style="color: red;font-weight: bold;display: flex;align-items: center;justify-content: center;flex-direction: column;margin:20px;"></div>
|