telegram-md2html 1.0.0 → 1.0.2
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/README.md +325 -306
- package/dist/converter.d.ts +3 -0
- package/dist/index.cjs +15 -6
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.esm.js +419 -0
- package/dist/index.mjs +15 -6
- package/dist/index.umd.js +432 -0
- package/package.json +12 -17
package/dist/index.cjs
CHANGED
|
@@ -293,6 +293,8 @@ class MarkdownConverter {
|
|
|
293
293
|
}
|
|
294
294
|
/**
|
|
295
295
|
* Wrap token content in HTML tags
|
|
296
|
+
* FIXED: Removed extra newlines that were being added around code blocks and quotes
|
|
297
|
+
* Previously added \n before and after, now returns clean tags without extra whitespace
|
|
296
298
|
*/
|
|
297
299
|
wrapToken(type, content, language) {
|
|
298
300
|
switch (type) {
|
|
@@ -316,7 +318,8 @@ class MarkdownConverter {
|
|
|
316
318
|
}
|
|
317
319
|
const escapedCode = this.options.escapeHtml ? escapeHtml(content) : content;
|
|
318
320
|
const langAttr = language ? ` class="language-${language}"` : '';
|
|
319
|
-
|
|
321
|
+
// FIXED: Removed \n before and after - now returns just the tag
|
|
322
|
+
return `<pre><code${langAttr}>${escapedCode}</code></pre>`;
|
|
320
323
|
case 'link':
|
|
321
324
|
const url = language || '';
|
|
322
325
|
if (this.hasCustomLinkProcessor) {
|
|
@@ -326,9 +329,11 @@ class MarkdownConverter {
|
|
|
326
329
|
const escapedText = this.options.escapeHtml ? escapeHtml(content) : content;
|
|
327
330
|
return `<a href="${escapedUrl}">${escapedText}</a>`;
|
|
328
331
|
case 'quote':
|
|
329
|
-
|
|
332
|
+
// FIXED: Removed \n before and after - now returns just the tag
|
|
333
|
+
return `<blockquote>${content.trim()}</blockquote>`;
|
|
330
334
|
case 'expandable_quote':
|
|
331
|
-
|
|
335
|
+
// FIXED: Removed \n before and after - now returns just the tag
|
|
336
|
+
return `<blockquote expandable>${content.trim()}</blockquote>`;
|
|
332
337
|
default:
|
|
333
338
|
return content;
|
|
334
339
|
}
|
|
@@ -360,6 +365,7 @@ class MarkdownConverter {
|
|
|
360
365
|
}
|
|
361
366
|
/**
|
|
362
367
|
* Process blockquote markers
|
|
368
|
+
* FIXED: Removed extra newlines from the replacement strings
|
|
363
369
|
*/
|
|
364
370
|
processBlockquoteMarkers(text) {
|
|
365
371
|
let result = text;
|
|
@@ -367,13 +373,15 @@ class MarkdownConverter {
|
|
|
367
373
|
const expandableQuoteRegex = /\[EXPANDABLE_QUOTE\](.*?)(?=\n|$)/g;
|
|
368
374
|
result = result.replace(expandableQuoteRegex, (match, content) => {
|
|
369
375
|
const processedContent = this.convertRecursive(content);
|
|
370
|
-
|
|
376
|
+
// FIXED: Removed \n before and after
|
|
377
|
+
return `<blockquote expandable>${processedContent.trim()}</blockquote>`;
|
|
371
378
|
});
|
|
372
379
|
// Replace regular quote markers (process content recursively)
|
|
373
380
|
const quoteRegex = /\[QUOTE\](.*?)(?=\n|$)/g;
|
|
374
381
|
result = result.replace(quoteRegex, (match, content) => {
|
|
375
382
|
const processedContent = this.convertRecursive(content);
|
|
376
|
-
|
|
383
|
+
// FIXED: Removed \n before and after
|
|
384
|
+
return `<blockquote>${processedContent.trim()}</blockquote>`;
|
|
377
385
|
});
|
|
378
386
|
return result;
|
|
379
387
|
}
|
|
@@ -385,7 +393,8 @@ class MarkdownConverter {
|
|
|
385
393
|
defaultCodeBlockProcessor(code, language) {
|
|
386
394
|
const escapedCode = this.options.escapeHtml ? escapeHtml(code) : code;
|
|
387
395
|
const langAttr = language ? ` class="language-${language}"` : '';
|
|
388
|
-
|
|
396
|
+
// FIXED: Removed \n before and after in default processor too
|
|
397
|
+
return `<pre><code${langAttr}>${escapedCode}</code></pre>`;
|
|
389
398
|
}
|
|
390
399
|
}
|
|
391
400
|
|
package/dist/index.d.mts
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
class MarkdownTokenizer {
|
|
2
|
+
constructor(text) {
|
|
3
|
+
this.text = text;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Tokenize the markdown text
|
|
7
|
+
*/
|
|
8
|
+
tokenize() {
|
|
9
|
+
const tokens = [];
|
|
10
|
+
let pos = 0;
|
|
11
|
+
const text = this.text;
|
|
12
|
+
while (pos < text.length) {
|
|
13
|
+
// Skip if inside code block
|
|
14
|
+
if (this.isInsideCodeBlock(text, pos)) {
|
|
15
|
+
pos++;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
// Try to match each token type (from outermost to innermost)
|
|
19
|
+
const token = this.matchToken(pos);
|
|
20
|
+
if (token) {
|
|
21
|
+
tokens.push(token);
|
|
22
|
+
pos = token.end;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
pos++;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return tokens.sort((a, b) => a.start - b.start);
|
|
29
|
+
}
|
|
30
|
+
matchToken(start) {
|
|
31
|
+
const text = this.text;
|
|
32
|
+
const remaining = text.slice(start);
|
|
33
|
+
// Skip if we're inside a quote marker
|
|
34
|
+
if (remaining.startsWith('[QUOTE]') || remaining.startsWith('[EXPANDABLE_QUOTE]')) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
// Match code block (triple backticks) - highest priority
|
|
38
|
+
const codeBlockMatch = remaining.match(/^```(\w+)?\n([\s\S]*?)```/);
|
|
39
|
+
if (codeBlockMatch) {
|
|
40
|
+
return {
|
|
41
|
+
type: 'code_block',
|
|
42
|
+
content: codeBlockMatch[2],
|
|
43
|
+
language: codeBlockMatch[1],
|
|
44
|
+
start: start,
|
|
45
|
+
end: start + codeBlockMatch[0].length
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// Match inline code
|
|
49
|
+
const inlineCodeMatch = remaining.match(/^`([^`\n]+)`/);
|
|
50
|
+
if (inlineCodeMatch && !this.isInsideInlineCode(text, start)) {
|
|
51
|
+
return {
|
|
52
|
+
type: 'inline_code',
|
|
53
|
+
content: inlineCodeMatch[1],
|
|
54
|
+
start: start,
|
|
55
|
+
end: start + inlineCodeMatch[0].length
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
// Match spoiler
|
|
59
|
+
const spoilerMatch = remaining.match(/^\|\|([^|\n]+?)\|\|/);
|
|
60
|
+
if (spoilerMatch) {
|
|
61
|
+
return {
|
|
62
|
+
type: 'spoiler',
|
|
63
|
+
content: spoilerMatch[1],
|
|
64
|
+
start: start,
|
|
65
|
+
end: start + spoilerMatch[0].length
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// Match strikethrough
|
|
69
|
+
const strikethroughMatch = remaining.match(/^~~([^~\n]+?)~~/);
|
|
70
|
+
if (strikethroughMatch) {
|
|
71
|
+
return {
|
|
72
|
+
type: 'strikethrough',
|
|
73
|
+
content: strikethroughMatch[1],
|
|
74
|
+
start: start,
|
|
75
|
+
end: start + strikethroughMatch[0].length
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
// Match bold
|
|
79
|
+
const boldMatch = remaining.match(/^\*\*([^*\n]+?)\*\*/);
|
|
80
|
+
if (boldMatch) {
|
|
81
|
+
return {
|
|
82
|
+
type: 'bold',
|
|
83
|
+
content: boldMatch[1],
|
|
84
|
+
start: start,
|
|
85
|
+
end: start + boldMatch[0].length
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// Match underline
|
|
89
|
+
const underlineMatch = remaining.match(/^__([^_\n]+?)__/);
|
|
90
|
+
if (underlineMatch) {
|
|
91
|
+
return {
|
|
92
|
+
type: 'underline',
|
|
93
|
+
content: underlineMatch[1],
|
|
94
|
+
start: start,
|
|
95
|
+
end: start + underlineMatch[0].length
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// Match italic with asterisk
|
|
99
|
+
const italicAsteriskMatch = remaining.match(/^\*([^*\n][^*]*?)\*/);
|
|
100
|
+
if (italicAsteriskMatch && italicAsteriskMatch[1].trim().length > 0) {
|
|
101
|
+
// Don't match if it's part of bold (**)
|
|
102
|
+
if (start > 0 && text[start - 1] === '*' && start < text.length - 1 && text[start + 1] === '*') {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
type: 'italic',
|
|
107
|
+
content: italicAsteriskMatch[1],
|
|
108
|
+
start: start,
|
|
109
|
+
end: start + italicAsteriskMatch[0].length
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
// Match italic with underscore
|
|
113
|
+
const italicUnderscoreMatch = remaining.match(/^_([^_\n]+?)_/);
|
|
114
|
+
if (italicUnderscoreMatch && italicUnderscoreMatch[1].trim().length > 0) {
|
|
115
|
+
// Don't match if it's part of underline (__)
|
|
116
|
+
if (start > 0 && text[start - 1] === '_' && start < text.length - 1 && text[start + 1] === '_') {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
type: 'italic',
|
|
121
|
+
content: italicUnderscoreMatch[1],
|
|
122
|
+
start: start,
|
|
123
|
+
end: start + italicUnderscoreMatch[0].length
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
// Match link
|
|
127
|
+
const linkMatch = remaining.match(/^\[([^\]]+?)\]\(([^)]+?)\)/);
|
|
128
|
+
if (linkMatch) {
|
|
129
|
+
return {
|
|
130
|
+
type: 'link',
|
|
131
|
+
content: linkMatch[1],
|
|
132
|
+
start: start,
|
|
133
|
+
end: start + linkMatch[0].length,
|
|
134
|
+
language: linkMatch[2]
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
isInsideCodeBlock(text, position) {
|
|
140
|
+
// Check for code blocks
|
|
141
|
+
const codeBlockRegex = /```[\s\S]*?```/g;
|
|
142
|
+
let match;
|
|
143
|
+
while ((match = codeBlockRegex.exec(text)) !== null) {
|
|
144
|
+
if (position > match.index && position < match.index + match[0].length) {
|
|
145
|
+
// But allow matching the closing ``` itself
|
|
146
|
+
if (position >= match.index + match[0].length - 3) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
isInsideInlineCode(text, position) {
|
|
155
|
+
// Check for inline code
|
|
156
|
+
const inlineCodeRegex = /`[^`\n]*`/g;
|
|
157
|
+
let match;
|
|
158
|
+
while ((match = inlineCodeRegex.exec(text)) !== null) {
|
|
159
|
+
if (position > match.index && position < match.index + match[0].length) {
|
|
160
|
+
// But allow matching the closing ` itself
|
|
161
|
+
if (position === match.index + match[0].length - 1) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Escapes HTML special characters (but not double-escape)
|
|
173
|
+
*/
|
|
174
|
+
function escapeHtml(text) {
|
|
175
|
+
if (!text)
|
|
176
|
+
return text;
|
|
177
|
+
// Replace & first (but not if it's already an entity)
|
|
178
|
+
let result = text.replace(/&(?!#?\w+;)/g, '&');
|
|
179
|
+
result = result.replace(/</g, '<');
|
|
180
|
+
result = result.replace(/>/g, '>');
|
|
181
|
+
result = result.replace(/"/g, '"');
|
|
182
|
+
result = result.replace(/'/g, ''');
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Escapes Telegram HTML special characters
|
|
187
|
+
*/
|
|
188
|
+
function escapeTelegramHtml(text) {
|
|
189
|
+
if (!text)
|
|
190
|
+
return text;
|
|
191
|
+
// For Telegram, we only need to escape &, <, >, and "
|
|
192
|
+
let result = text.replace(/&(?!#?\w+;)/g, '&');
|
|
193
|
+
result = result.replace(/</g, '<');
|
|
194
|
+
result = result.replace(/>/g, '>');
|
|
195
|
+
result = result.replace(/"/g, '"');
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Appends missing code block delimiters
|
|
200
|
+
*/
|
|
201
|
+
function autoCloseCodeBlocks(text) {
|
|
202
|
+
// Count triple backticks
|
|
203
|
+
const tripleBacktickCount = (text.match(/```/g) || []).length;
|
|
204
|
+
// If odd number, add closing backticks
|
|
205
|
+
if (tripleBacktickCount % 2 === 1) {
|
|
206
|
+
return text + '\n```';
|
|
207
|
+
}
|
|
208
|
+
return text;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
class MarkdownConverter {
|
|
212
|
+
constructor(options = {}) {
|
|
213
|
+
this.hasCustomLinkProcessor = !!options.linkProcessor;
|
|
214
|
+
this.hasCustomCodeBlockProcessor = !!options.codeBlockProcessor;
|
|
215
|
+
this.options = {
|
|
216
|
+
escapeHtml: options.escapeHtml ?? true,
|
|
217
|
+
autoCloseCodeBlocks: options.autoCloseCodeBlocks ?? true,
|
|
218
|
+
linkProcessor: options.linkProcessor || this.defaultLinkProcessor.bind(this),
|
|
219
|
+
codeBlockProcessor: options.codeBlockProcessor || this.defaultCodeBlockProcessor.bind(this)
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Convert markdown text to Telegram HTML
|
|
224
|
+
*/
|
|
225
|
+
convert(text) {
|
|
226
|
+
// Auto-close code blocks if enabled
|
|
227
|
+
let processedText = this.options.autoCloseCodeBlocks
|
|
228
|
+
? autoCloseCodeBlocks(text)
|
|
229
|
+
: text;
|
|
230
|
+
// First pass: convert blockquotes (they should be at line starts)
|
|
231
|
+
processedText = this.preprocessBlockquotes(processedText);
|
|
232
|
+
// Convert the text recursively
|
|
233
|
+
let result = this.convertRecursive(processedText);
|
|
234
|
+
// Process blockquote markers
|
|
235
|
+
result = this.processBlockquoteMarkers(result);
|
|
236
|
+
// Only trim if there's actual content (not just whitespace)
|
|
237
|
+
if (result.trim() === '') {
|
|
238
|
+
return text; // Return original text (spaces) if result is empty
|
|
239
|
+
}
|
|
240
|
+
return result.trim();
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Recursively convert markdown, handling nested styles
|
|
244
|
+
*/
|
|
245
|
+
convertRecursive(text, depth = 0) {
|
|
246
|
+
if (depth > 10)
|
|
247
|
+
return text; // Prevent infinite recursion
|
|
248
|
+
// Tokenize the text
|
|
249
|
+
const tokenizer = new MarkdownTokenizer(text);
|
|
250
|
+
const tokens = tokenizer.tokenize();
|
|
251
|
+
// If no tokens found, return the text as-is (with HTML escaping)
|
|
252
|
+
if (tokens.length === 0) {
|
|
253
|
+
return this.options.escapeHtml ? escapeTelegramHtml(text) : text;
|
|
254
|
+
}
|
|
255
|
+
let result = '';
|
|
256
|
+
let lastPos = 0;
|
|
257
|
+
for (const token of tokens) {
|
|
258
|
+
// Add text before token
|
|
259
|
+
if (token.start > lastPos) {
|
|
260
|
+
const textBefore = text.slice(lastPos, token.start);
|
|
261
|
+
result += this.options.escapeHtml ? escapeTelegramHtml(textBefore) : textBefore;
|
|
262
|
+
}
|
|
263
|
+
// Handle code blocks specially (no recursive parsing inside)
|
|
264
|
+
if (token.type === 'code_block') {
|
|
265
|
+
const codeContent = this.options.escapeHtml ? escapeHtml(token.content) : token.content;
|
|
266
|
+
result += this.wrapToken(token.type, codeContent, token.language);
|
|
267
|
+
lastPos = token.end;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
// Handle inline code specially (no recursive parsing inside)
|
|
271
|
+
if (token.type === 'inline_code') {
|
|
272
|
+
const codeContent = this.options.escapeHtml ? escapeHtml(token.content) : token.content;
|
|
273
|
+
result += `<code>${codeContent}</code>`;
|
|
274
|
+
lastPos = token.end;
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
// Process other token content recursively
|
|
278
|
+
const tokenContent = this.convertRecursive(token.content, depth + 1);
|
|
279
|
+
// Wrap the content in appropriate HTML tags
|
|
280
|
+
result += this.wrapToken(token.type, tokenContent, token.language);
|
|
281
|
+
lastPos = token.end;
|
|
282
|
+
}
|
|
283
|
+
// Add remaining text
|
|
284
|
+
if (lastPos < text.length) {
|
|
285
|
+
const remainingText = text.slice(lastPos);
|
|
286
|
+
result += this.options.escapeHtml ? escapeTelegramHtml(remainingText) : remainingText;
|
|
287
|
+
}
|
|
288
|
+
return result;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Wrap token content in HTML tags
|
|
292
|
+
* FIXED: Removed extra newlines that were being added around code blocks and quotes
|
|
293
|
+
* Previously added \n before and after, now returns clean tags without extra whitespace
|
|
294
|
+
*/
|
|
295
|
+
wrapToken(type, content, language) {
|
|
296
|
+
switch (type) {
|
|
297
|
+
case 'bold':
|
|
298
|
+
return `<b>${content}</b>`;
|
|
299
|
+
case 'italic':
|
|
300
|
+
return `<i>${content}</i>`;
|
|
301
|
+
case 'underline':
|
|
302
|
+
return `<u>${content}</u>`;
|
|
303
|
+
case 'strikethrough':
|
|
304
|
+
return `<s>${content}</s>`;
|
|
305
|
+
case 'spoiler':
|
|
306
|
+
return `<span class="tg-spoiler">${content}</span>`;
|
|
307
|
+
case 'inline_code':
|
|
308
|
+
// Already handled above
|
|
309
|
+
return `<code>${content}</code>`;
|
|
310
|
+
case 'code_block':
|
|
311
|
+
// Already handled above, but handle custom processor
|
|
312
|
+
if (this.hasCustomCodeBlockProcessor) {
|
|
313
|
+
return this.options.codeBlockProcessor(content, language);
|
|
314
|
+
}
|
|
315
|
+
const escapedCode = this.options.escapeHtml ? escapeHtml(content) : content;
|
|
316
|
+
const langAttr = language ? ` class="language-${language}"` : '';
|
|
317
|
+
// FIXED: Removed \n before and after - now returns just the tag
|
|
318
|
+
return `<pre><code${langAttr}>${escapedCode}</code></pre>`;
|
|
319
|
+
case 'link':
|
|
320
|
+
const url = language || '';
|
|
321
|
+
if (this.hasCustomLinkProcessor) {
|
|
322
|
+
return this.options.linkProcessor(url, content);
|
|
323
|
+
}
|
|
324
|
+
const escapedUrl = this.options.escapeHtml ? escapeHtml(url) : url;
|
|
325
|
+
const escapedText = this.options.escapeHtml ? escapeHtml(content) : content;
|
|
326
|
+
return `<a href="${escapedUrl}">${escapedText}</a>`;
|
|
327
|
+
case 'quote':
|
|
328
|
+
// FIXED: Removed \n before and after - now returns just the tag
|
|
329
|
+
return `<blockquote>${content.trim()}</blockquote>`;
|
|
330
|
+
case 'expandable_quote':
|
|
331
|
+
// FIXED: Removed \n before and after - now returns just the tag
|
|
332
|
+
return `<blockquote expandable>${content.trim()}</blockquote>`;
|
|
333
|
+
default:
|
|
334
|
+
return content;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Preprocess blockquotes to mark them before other parsing
|
|
339
|
+
*/
|
|
340
|
+
preprocessBlockquotes(text) {
|
|
341
|
+
const lines = text.split('\n');
|
|
342
|
+
const processedLines = [];
|
|
343
|
+
for (const line of lines) {
|
|
344
|
+
const trimmedLine = line.trim();
|
|
345
|
+
// Only treat lines starting with > at the beginning of line as blockquotes
|
|
346
|
+
if (trimmedLine.startsWith('**>')) {
|
|
347
|
+
// Expandable blockquote
|
|
348
|
+
const content = trimmedLine.substring(3).trim();
|
|
349
|
+
processedLines.push(`[EXPANDABLE_QUOTE]${content}`);
|
|
350
|
+
}
|
|
351
|
+
else if (trimmedLine.startsWith('>')) {
|
|
352
|
+
// Regular blockquote
|
|
353
|
+
const content = trimmedLine.substring(1).trim();
|
|
354
|
+
processedLines.push(`[QUOTE]${content}`);
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
processedLines.push(line);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return processedLines.join('\n');
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Process blockquote markers
|
|
364
|
+
* FIXED: Removed extra newlines from the replacement strings
|
|
365
|
+
*/
|
|
366
|
+
processBlockquoteMarkers(text) {
|
|
367
|
+
let result = text;
|
|
368
|
+
// Replace expandable quote markers (process content recursively)
|
|
369
|
+
const expandableQuoteRegex = /\[EXPANDABLE_QUOTE\](.*?)(?=\n|$)/g;
|
|
370
|
+
result = result.replace(expandableQuoteRegex, (match, content) => {
|
|
371
|
+
const processedContent = this.convertRecursive(content);
|
|
372
|
+
// FIXED: Removed \n before and after
|
|
373
|
+
return `<blockquote expandable>${processedContent.trim()}</blockquote>`;
|
|
374
|
+
});
|
|
375
|
+
// Replace regular quote markers (process content recursively)
|
|
376
|
+
const quoteRegex = /\[QUOTE\](.*?)(?=\n|$)/g;
|
|
377
|
+
result = result.replace(quoteRegex, (match, content) => {
|
|
378
|
+
const processedContent = this.convertRecursive(content);
|
|
379
|
+
// FIXED: Removed \n before and after
|
|
380
|
+
return `<blockquote>${processedContent.trim()}</blockquote>`;
|
|
381
|
+
});
|
|
382
|
+
return result;
|
|
383
|
+
}
|
|
384
|
+
defaultLinkProcessor(url, text) {
|
|
385
|
+
const escapedUrl = this.options.escapeHtml ? escapeHtml(url) : url;
|
|
386
|
+
const escapedText = this.options.escapeHtml ? escapeHtml(text) : text;
|
|
387
|
+
return `<a href="${escapedUrl}">${escapedText}</a>`;
|
|
388
|
+
}
|
|
389
|
+
defaultCodeBlockProcessor(code, language) {
|
|
390
|
+
const escapedCode = this.options.escapeHtml ? escapeHtml(code) : code;
|
|
391
|
+
const langAttr = language ? ` class="language-${language}"` : '';
|
|
392
|
+
// FIXED: Removed \n before and after in default processor too
|
|
393
|
+
return `<pre><code${langAttr}>${escapedCode}</code></pre>`;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Convert Telegram-style Markdown to HTML
|
|
399
|
+
* @param text - Markdown text to convert
|
|
400
|
+
* @param options - Conversion options
|
|
401
|
+
* @returns Telegram-compatible HTML
|
|
402
|
+
*/
|
|
403
|
+
function markdownToHtml(text, options) {
|
|
404
|
+
const converter = new MarkdownConverter(options);
|
|
405
|
+
return converter.convert(text);
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Create a converter instance with custom options
|
|
409
|
+
*/
|
|
410
|
+
function createConverter(options) {
|
|
411
|
+
return new MarkdownConverter(options);
|
|
412
|
+
}
|
|
413
|
+
var index = {
|
|
414
|
+
markdownToHtml,
|
|
415
|
+
createConverter,
|
|
416
|
+
MarkdownConverter
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
export { MarkdownConverter, createConverter, index as default, markdownToHtml };
|
package/dist/index.mjs
CHANGED
|
@@ -289,6 +289,8 @@ class MarkdownConverter {
|
|
|
289
289
|
}
|
|
290
290
|
/**
|
|
291
291
|
* Wrap token content in HTML tags
|
|
292
|
+
* FIXED: Removed extra newlines that were being added around code blocks and quotes
|
|
293
|
+
* Previously added \n before and after, now returns clean tags without extra whitespace
|
|
292
294
|
*/
|
|
293
295
|
wrapToken(type, content, language) {
|
|
294
296
|
switch (type) {
|
|
@@ -312,7 +314,8 @@ class MarkdownConverter {
|
|
|
312
314
|
}
|
|
313
315
|
const escapedCode = this.options.escapeHtml ? escapeHtml(content) : content;
|
|
314
316
|
const langAttr = language ? ` class="language-${language}"` : '';
|
|
315
|
-
|
|
317
|
+
// FIXED: Removed \n before and after - now returns just the tag
|
|
318
|
+
return `<pre><code${langAttr}>${escapedCode}</code></pre>`;
|
|
316
319
|
case 'link':
|
|
317
320
|
const url = language || '';
|
|
318
321
|
if (this.hasCustomLinkProcessor) {
|
|
@@ -322,9 +325,11 @@ class MarkdownConverter {
|
|
|
322
325
|
const escapedText = this.options.escapeHtml ? escapeHtml(content) : content;
|
|
323
326
|
return `<a href="${escapedUrl}">${escapedText}</a>`;
|
|
324
327
|
case 'quote':
|
|
325
|
-
|
|
328
|
+
// FIXED: Removed \n before and after - now returns just the tag
|
|
329
|
+
return `<blockquote>${content.trim()}</blockquote>`;
|
|
326
330
|
case 'expandable_quote':
|
|
327
|
-
|
|
331
|
+
// FIXED: Removed \n before and after - now returns just the tag
|
|
332
|
+
return `<blockquote expandable>${content.trim()}</blockquote>`;
|
|
328
333
|
default:
|
|
329
334
|
return content;
|
|
330
335
|
}
|
|
@@ -356,6 +361,7 @@ class MarkdownConverter {
|
|
|
356
361
|
}
|
|
357
362
|
/**
|
|
358
363
|
* Process blockquote markers
|
|
364
|
+
* FIXED: Removed extra newlines from the replacement strings
|
|
359
365
|
*/
|
|
360
366
|
processBlockquoteMarkers(text) {
|
|
361
367
|
let result = text;
|
|
@@ -363,13 +369,15 @@ class MarkdownConverter {
|
|
|
363
369
|
const expandableQuoteRegex = /\[EXPANDABLE_QUOTE\](.*?)(?=\n|$)/g;
|
|
364
370
|
result = result.replace(expandableQuoteRegex, (match, content) => {
|
|
365
371
|
const processedContent = this.convertRecursive(content);
|
|
366
|
-
|
|
372
|
+
// FIXED: Removed \n before and after
|
|
373
|
+
return `<blockquote expandable>${processedContent.trim()}</blockquote>`;
|
|
367
374
|
});
|
|
368
375
|
// Replace regular quote markers (process content recursively)
|
|
369
376
|
const quoteRegex = /\[QUOTE\](.*?)(?=\n|$)/g;
|
|
370
377
|
result = result.replace(quoteRegex, (match, content) => {
|
|
371
378
|
const processedContent = this.convertRecursive(content);
|
|
372
|
-
|
|
379
|
+
// FIXED: Removed \n before and after
|
|
380
|
+
return `<blockquote>${processedContent.trim()}</blockquote>`;
|
|
373
381
|
});
|
|
374
382
|
return result;
|
|
375
383
|
}
|
|
@@ -381,7 +389,8 @@ class MarkdownConverter {
|
|
|
381
389
|
defaultCodeBlockProcessor(code, language) {
|
|
382
390
|
const escapedCode = this.options.escapeHtml ? escapeHtml(code) : code;
|
|
383
391
|
const langAttr = language ? ` class="language-${language}"` : '';
|
|
384
|
-
|
|
392
|
+
// FIXED: Removed \n before and after in default processor too
|
|
393
|
+
return `<pre><code${langAttr}>${escapedCode}</code></pre>`;
|
|
385
394
|
}
|
|
386
395
|
}
|
|
387
396
|
|