1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
|
/**
* Parses a limited subset of Markdown and converts it to HTML.
*
* Supported Markdown elements:
* - Headings (#, ##, ###)
* - Bold text (**text** or __text__)
* - Unordered lists (- or *)
* - Ordered lists (1., 2., etc.)
* - Paragraphs
*
* @param {string} markdown - The Markdown-formatted string.
* @returns {string} - The resulting HTML string.
*/
function parseMarkdown(markdown) {
const lines = markdown.split('\n');
const html = [];
// Stack of open lists: [{ type: "ul"|"ol", indent: number }]
const listStack = [];
function closeListsToIndent(indent) {
while (listStack.length > 0 && listStack[listStack.length - 1].indent >= indent) {
const last = listStack.pop();
html.push(`</${last.type}>`);
}
}
function openList(type, indent, startNumber = null) {
listStack.push({ type, indent });
if (type === "ol" && startNumber != null && startNumber !== 1)
html.push(`<ol start="${startNumber}">`);
else
html.push(`<${type}>`);
}
lines.forEach(line => {
// Detect indentation level (2 spaces = one indent)
const indentSpaces = line.match(/^ */)[0].length;
const indent = Math.floor(indentSpaces / 2);
const trimmed = line.trim();
if (trimmed === "") {
// Close all lists for blank lines, do NOT output <p>
closeListsToIndent(0);
return;
}
// --------
// Headings
// --------
if (/^###\s+/.test(trimmed)) {
closeListsToIndent(0);
html.push(`<h3>${escapeHtml(trimmed.replace(/^###\s+/, ''))}</h3>`);
return;
}
if (/^##\s+/.test(trimmed)) {
closeListsToIndent(0);
html.push(`<h2>${escapeHtml(trimmed.replace(/^##\s+/, ''))}</h2>`);
return;
}
if (/^#\s+/.test(trimmed)) {
closeListsToIndent(0);
html.push(`<h1>${escapeHtml(trimmed.replace(/^#\s+/, ''))}</h1>`);
return;
}
// ------------------------
// Ordered lists: "N. text"
// ------------------------
let mOrdered = trimmed.match(/^(\d+)\.\s+(.*)/);
if (mOrdered) {
const num = parseInt(mOrdered[1], 10);
const content = mOrdered[2];
const last = listStack[listStack.length - 1];
if (!last || last.indent < indent || last.type !== "ol") {
// NEW ordered list
closeListsToIndent(indent);
openList("ol", indent, num);
}
// ELSE: same indent, same list → continue existing OL without closing/opening
html.push(`<li>${parseInlineMarkdown(escapeHtml(content))}</li>`);
return;
}
// -------------------------------------
// Unordered lists: "- text" or "* text"
// -------------------------------------
let mUnordered = trimmed.match(/^[-*]\s+(.*)/);
if (mUnordered) {
const content = mUnordered[1];
const last = listStack[listStack.length - 1];
if (!last || last.indent < indent || last.type !== "ul") {
closeListsToIndent(indent);
openList("ul", indent);
}
html.push(`<li>${parseInlineMarkdown(escapeHtml(content))}</li>`);
return;
}
// ---------
// Paragraph
// ---------
closeListsToIndent(0);
html.push(`<p>${parseInlineMarkdown(escapeHtml(trimmed))}</p>`);
});
// Close all remaining lists
closeListsToIndent(0);
return html.join('\n');
}
/**
* Parses inline Markdown elements like bold text.
*
* Supported inline elements:
* - Bold text (**text** or __text__)
*
* @param {string} text - The text to parse.
* @returns {string} - The text with inline Markdown converted to HTML.
*/
function parseInlineMarkdown(text) {
// Convert **text** and __text__ to <strong>text</strong>
return text
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
.replace(/__(.+?)__/g, '<strong>$1</strong>');
}
/**
* Escapes HTML special characters to prevent XSS attacks.
*
* @param {string} text - The text to escape.
* @returns {string} - The escaped text.
*/
function escapeHtml(text) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
};
return text.replace(/[&<>"']/g, function(m) {
return map[m];
});
}
return L.Class.extend({
parseMarkdown: parseMarkdown,
});
|