@mtcute/markdown-parser 0.16.7 → 0.17.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/index.cjs +352 -0
- package/{cjs/index.d.ts → index.d.cts} +2 -2
- package/index.d.ts +47 -1
- package/index.js +347 -1
- package/markdown-parser.test.d.ts +1 -0
- package/package.json +28 -24
- package/cjs/index.js +0 -415
- package/cjs/index.js.map +0 -1
- package/cjs/package.json +0 -3
- package/esm/index.d.ts +0 -47
- package/esm/index.js +0 -403
- package/esm/index.js.map +0 -1
package/index.js
CHANGED
|
@@ -1 +1,347 @@
|
|
|
1
|
-
|
|
1
|
+
import Long from "long";
|
|
2
|
+
const MENTION_REGEX = /^tg:\/\/user\?id=(\d+)(?:&hash=(-?[0-9a-fA-F]+)(?:&|$)|&|$)/;
|
|
3
|
+
const EMOJI_REGEX = /^tg:\/\/emoji\?id=(-?\d+)/;
|
|
4
|
+
const TAG_BOLD = "**";
|
|
5
|
+
const TAG_ITALIC = "__";
|
|
6
|
+
const TAG_UNDERLINE = "--";
|
|
7
|
+
const TAG_STRIKE = "~~";
|
|
8
|
+
const TAG_SPOILER = "||";
|
|
9
|
+
const TAG_CODE = "`";
|
|
10
|
+
const TAG_PRE = "```";
|
|
11
|
+
const TO_BE_ESCAPED = /[*_\-~`[\\\]|]/g;
|
|
12
|
+
function escape(str) {
|
|
13
|
+
return str.replace(TO_BE_ESCAPED, (s) => `\\${s}`);
|
|
14
|
+
}
|
|
15
|
+
function unparse(input) {
|
|
16
|
+
if (typeof input === "string") return escape(input);
|
|
17
|
+
let text = input.text;
|
|
18
|
+
const entities = input.entities ?? [];
|
|
19
|
+
const escaped = [];
|
|
20
|
+
text = text.replace(TO_BE_ESCAPED, (s, pos) => {
|
|
21
|
+
escaped.push(pos);
|
|
22
|
+
return `\\${s}`;
|
|
23
|
+
});
|
|
24
|
+
const hasEscaped = escaped.length > 0;
|
|
25
|
+
const insert = [];
|
|
26
|
+
for (const entity of entities) {
|
|
27
|
+
const type = entity._;
|
|
28
|
+
let start = entity.offset;
|
|
29
|
+
let end = start + entity.length;
|
|
30
|
+
if (start > text.length) continue;
|
|
31
|
+
if (start < 0) start = 0;
|
|
32
|
+
if (end > text.length) end = text.length;
|
|
33
|
+
if (hasEscaped) {
|
|
34
|
+
let escapedPos = 0;
|
|
35
|
+
while (escapedPos < escaped.length && escaped[escapedPos] < start) {
|
|
36
|
+
escapedPos += 1;
|
|
37
|
+
}
|
|
38
|
+
start += escapedPos;
|
|
39
|
+
while (escapedPos < escaped.length && escaped[escapedPos] <= end) {
|
|
40
|
+
escapedPos += 1;
|
|
41
|
+
}
|
|
42
|
+
end += escapedPos;
|
|
43
|
+
}
|
|
44
|
+
let startTag;
|
|
45
|
+
let endTag;
|
|
46
|
+
switch (type) {
|
|
47
|
+
case "messageEntityBold":
|
|
48
|
+
startTag = endTag = TAG_BOLD;
|
|
49
|
+
break;
|
|
50
|
+
case "messageEntityItalic":
|
|
51
|
+
startTag = endTag = TAG_ITALIC;
|
|
52
|
+
break;
|
|
53
|
+
case "messageEntityUnderline":
|
|
54
|
+
startTag = endTag = TAG_UNDERLINE;
|
|
55
|
+
break;
|
|
56
|
+
case "messageEntityStrike":
|
|
57
|
+
startTag = endTag = TAG_STRIKE;
|
|
58
|
+
break;
|
|
59
|
+
case "messageEntitySpoiler":
|
|
60
|
+
startTag = endTag = TAG_SPOILER;
|
|
61
|
+
break;
|
|
62
|
+
case "messageEntityCode":
|
|
63
|
+
startTag = endTag = TAG_CODE;
|
|
64
|
+
break;
|
|
65
|
+
case "messageEntityPre":
|
|
66
|
+
startTag = TAG_PRE;
|
|
67
|
+
if (entity.language) {
|
|
68
|
+
startTag += entity.language;
|
|
69
|
+
}
|
|
70
|
+
startTag += "\n";
|
|
71
|
+
endTag = `
|
|
72
|
+
${TAG_PRE}`;
|
|
73
|
+
break;
|
|
74
|
+
case "messageEntityTextUrl":
|
|
75
|
+
startTag = "[";
|
|
76
|
+
endTag = `](${entity.url})`;
|
|
77
|
+
break;
|
|
78
|
+
case "messageEntityMentionName":
|
|
79
|
+
startTag = "[";
|
|
80
|
+
endTag = `](tg://user?id=${entity.userId})`;
|
|
81
|
+
break;
|
|
82
|
+
case "messageEntityCustomEmoji":
|
|
83
|
+
startTag = "[";
|
|
84
|
+
endTag = `](tg://emoji?id=${entity.documentId.toString()})`;
|
|
85
|
+
break;
|
|
86
|
+
default:
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
insert.push([start, startTag]);
|
|
90
|
+
insert.push([end, endTag]);
|
|
91
|
+
}
|
|
92
|
+
insert.sort((a, b) => b[0] - a[0]);
|
|
93
|
+
for (const [offset, tag] of insert) {
|
|
94
|
+
text = text.substr(0, offset) + tag + text.substr(offset);
|
|
95
|
+
}
|
|
96
|
+
return text;
|
|
97
|
+
}
|
|
98
|
+
function parse(strings, ...sub) {
|
|
99
|
+
const entities = [];
|
|
100
|
+
let result = "";
|
|
101
|
+
const stacks = {};
|
|
102
|
+
let insideCode = false;
|
|
103
|
+
let insidePre = false;
|
|
104
|
+
let insideLink = false;
|
|
105
|
+
let insideLinkUrl = false;
|
|
106
|
+
let pendingLinkUrl = "";
|
|
107
|
+
function feed(text) {
|
|
108
|
+
const len = text.length;
|
|
109
|
+
let pos = 0;
|
|
110
|
+
while (pos < len) {
|
|
111
|
+
const c = text[pos];
|
|
112
|
+
if (c === "\\") {
|
|
113
|
+
if (insideLinkUrl) {
|
|
114
|
+
pendingLinkUrl += text[pos + 1];
|
|
115
|
+
} else {
|
|
116
|
+
result += text[pos + 1];
|
|
117
|
+
}
|
|
118
|
+
pos += 2;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (insideCode) {
|
|
122
|
+
if (c === "`") {
|
|
123
|
+
const ent = stacks.code.pop();
|
|
124
|
+
ent.length = result.length - ent.offset;
|
|
125
|
+
entities.push(ent);
|
|
126
|
+
insideCode = false;
|
|
127
|
+
pos += 1;
|
|
128
|
+
} else {
|
|
129
|
+
pos += 1;
|
|
130
|
+
result += c;
|
|
131
|
+
}
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (insidePre) {
|
|
135
|
+
if (c === "`" || c === "\n" && text[pos + 1] === "`") {
|
|
136
|
+
if (c === "\n") pos += 1;
|
|
137
|
+
if (text[pos + 1] === "`" && text[pos + 2] === "`") {
|
|
138
|
+
const ent = stacks.pre.pop();
|
|
139
|
+
ent.length = result.length - ent.offset;
|
|
140
|
+
entities.push(ent);
|
|
141
|
+
insidePre = false;
|
|
142
|
+
pos += 3;
|
|
143
|
+
continue;
|
|
144
|
+
} else if (c === "\n") {
|
|
145
|
+
pos -= 1;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
pos += 1;
|
|
149
|
+
result += c;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (insideLink && c === "]") {
|
|
153
|
+
const ent = stacks.link.pop();
|
|
154
|
+
if (text[pos + 1] !== "(") {
|
|
155
|
+
result = `${result.substr(0, ent.offset)}[${result.substr(ent.offset)}]`;
|
|
156
|
+
pos += 1;
|
|
157
|
+
insideLink = false;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
pos += 2;
|
|
161
|
+
insideLink = false;
|
|
162
|
+
insideLinkUrl = true;
|
|
163
|
+
stacks.link.push(ent);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (insideLinkUrl) {
|
|
167
|
+
pos += 1;
|
|
168
|
+
if (c !== ")") {
|
|
169
|
+
pendingLinkUrl += c;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const ent = stacks.link.pop();
|
|
173
|
+
let url = pendingLinkUrl;
|
|
174
|
+
pendingLinkUrl = "";
|
|
175
|
+
insideLinkUrl = false;
|
|
176
|
+
if (!url.length) continue;
|
|
177
|
+
ent.length = result.length - ent.offset;
|
|
178
|
+
let m = url.match(MENTION_REGEX);
|
|
179
|
+
if (m) {
|
|
180
|
+
const userId = Number.parseInt(m[1]);
|
|
181
|
+
const accessHash = m[2];
|
|
182
|
+
if (accessHash) {
|
|
183
|
+
ent._ = "inputMessageEntityMentionName";
|
|
184
|
+
ent.userId = {
|
|
185
|
+
_: "inputUser",
|
|
186
|
+
userId,
|
|
187
|
+
accessHash: Long.fromString(accessHash, false, 16)
|
|
188
|
+
};
|
|
189
|
+
} else {
|
|
190
|
+
ent._ = "messageEntityMentionName";
|
|
191
|
+
ent.userId = userId;
|
|
192
|
+
}
|
|
193
|
+
} else if (m = EMOJI_REGEX.exec(url)) {
|
|
194
|
+
ent._ = "messageEntityCustomEmoji";
|
|
195
|
+
ent.documentId = Long.fromString(m[1]);
|
|
196
|
+
} else {
|
|
197
|
+
if (url.match(/^\/\//)) url = `http:${url}`;
|
|
198
|
+
ent._ = "messageEntityTextUrl";
|
|
199
|
+
ent.url = url;
|
|
200
|
+
}
|
|
201
|
+
entities.push(ent);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (c === "[" && !insideLink) {
|
|
205
|
+
pos += 1;
|
|
206
|
+
insideLink = true;
|
|
207
|
+
if (!("link" in stacks)) stacks.link = [];
|
|
208
|
+
stacks.link.push({
|
|
209
|
+
offset: result.length,
|
|
210
|
+
length: 0
|
|
211
|
+
});
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (c === "`") {
|
|
215
|
+
const isPre = text[pos + 1] === "`" && text[pos + 2] === "`";
|
|
216
|
+
if (isPre) {
|
|
217
|
+
pos += 3;
|
|
218
|
+
let language = "";
|
|
219
|
+
while (pos < text.length && text[pos] !== "\n") {
|
|
220
|
+
language += text[pos++];
|
|
221
|
+
}
|
|
222
|
+
pos += 1;
|
|
223
|
+
if (pos > text.length) {
|
|
224
|
+
throw new Error("Malformed PRE entity, expected LF after ```");
|
|
225
|
+
}
|
|
226
|
+
if (!("pre" in stacks)) stacks.pre = [];
|
|
227
|
+
stacks.pre.push({
|
|
228
|
+
_: "messageEntityPre",
|
|
229
|
+
offset: result.length,
|
|
230
|
+
length: 0,
|
|
231
|
+
language
|
|
232
|
+
});
|
|
233
|
+
insidePre = true;
|
|
234
|
+
} else {
|
|
235
|
+
pos += 1;
|
|
236
|
+
if (!("code" in stacks)) stacks.code = [];
|
|
237
|
+
stacks.code.push({
|
|
238
|
+
_: "messageEntityCode",
|
|
239
|
+
offset: result.length,
|
|
240
|
+
length: 0
|
|
241
|
+
});
|
|
242
|
+
insideCode = true;
|
|
243
|
+
}
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (c === text[pos + 1]) {
|
|
247
|
+
let type = null;
|
|
248
|
+
switch (c) {
|
|
249
|
+
case "_":
|
|
250
|
+
type = "Italic";
|
|
251
|
+
break;
|
|
252
|
+
case "*":
|
|
253
|
+
type = "Bold";
|
|
254
|
+
break;
|
|
255
|
+
case "-":
|
|
256
|
+
type = "Underline";
|
|
257
|
+
break;
|
|
258
|
+
case "~":
|
|
259
|
+
type = "Strike";
|
|
260
|
+
break;
|
|
261
|
+
case "|":
|
|
262
|
+
type = "Spoiler";
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
if (type) {
|
|
266
|
+
if (!(type in stacks)) stacks[type] = [];
|
|
267
|
+
const isBegin = stacks[type].length === 0;
|
|
268
|
+
if (isBegin) {
|
|
269
|
+
stacks[type].push({
|
|
270
|
+
_: `messageEntity${type}`,
|
|
271
|
+
offset: result.length,
|
|
272
|
+
length: 0
|
|
273
|
+
});
|
|
274
|
+
} else {
|
|
275
|
+
const ent = stacks[type].pop();
|
|
276
|
+
ent.length = result.length - ent.offset;
|
|
277
|
+
entities.push(ent);
|
|
278
|
+
}
|
|
279
|
+
pos += 2;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (c === "\n") {
|
|
284
|
+
if (pos !== 0) {
|
|
285
|
+
result += "\n";
|
|
286
|
+
}
|
|
287
|
+
const nonWhitespace = text.slice(pos + 1).search(/[^ \t]/);
|
|
288
|
+
if (nonWhitespace !== -1) {
|
|
289
|
+
pos += nonWhitespace + 1;
|
|
290
|
+
} else {
|
|
291
|
+
pos = len;
|
|
292
|
+
result = result.trimEnd();
|
|
293
|
+
}
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
result += c;
|
|
297
|
+
pos += 1;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (typeof strings === "string") strings = [strings];
|
|
301
|
+
sub.forEach((it, idx) => {
|
|
302
|
+
feed(strings[idx]);
|
|
303
|
+
if (typeof it === "boolean" || !it) return;
|
|
304
|
+
if (insideLinkUrl) {
|
|
305
|
+
if (typeof it === "string" || typeof it === "number") {
|
|
306
|
+
pendingLinkUrl += it;
|
|
307
|
+
} else if (Long.isLong(it)) {
|
|
308
|
+
pendingLinkUrl += it.toString(10);
|
|
309
|
+
} else {
|
|
310
|
+
pendingLinkUrl += it.text;
|
|
311
|
+
}
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (typeof it === "string" || typeof it === "number") {
|
|
315
|
+
result += it;
|
|
316
|
+
} else if (Long.isLong(it)) {
|
|
317
|
+
result += it.toString(10);
|
|
318
|
+
} else {
|
|
319
|
+
const text = it.text;
|
|
320
|
+
const innerEntities = "raw" in it ? [it.raw] : it.entities;
|
|
321
|
+
const baseOffset = result.length;
|
|
322
|
+
result += text;
|
|
323
|
+
if (innerEntities) {
|
|
324
|
+
for (const ent of innerEntities) {
|
|
325
|
+
entities.push({ ...ent, offset: ent.offset + baseOffset });
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
feed(strings[strings.length - 1]);
|
|
331
|
+
for (const [name, stack] of Object.entries(stacks)) {
|
|
332
|
+
if (stack.length) {
|
|
333
|
+
throw new Error(`Unterminated ${name} entity`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
text: result,
|
|
338
|
+
entities
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
const md = Object.assign(parse, {
|
|
342
|
+
escape,
|
|
343
|
+
unparse
|
|
344
|
+
});
|
|
345
|
+
export {
|
|
346
|
+
md
|
|
347
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,27 +1,31 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
2
|
+
"name": "@mtcute/markdown-parser",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.17.0",
|
|
5
|
+
"description": "Markdown entities parser for mtcute",
|
|
6
|
+
"author": "alina sireneva <alina@tei.su>",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./index.d.ts",
|
|
13
|
+
"default": "./index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./index.d.cts",
|
|
17
|
+
"default": "./index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"scripts": {},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@mtcute/core": "^0.17.0",
|
|
24
|
+
"long": "5.2.3"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://mtcute.dev",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/mtcute/mtcute"
|
|
13
30
|
}
|
|
14
|
-
},
|
|
15
|
-
"scripts": {},
|
|
16
|
-
"dependencies": {
|
|
17
|
-
"@mtcute/core": "^0.16.7",
|
|
18
|
-
"long": "5.2.3"
|
|
19
|
-
},
|
|
20
|
-
"main": "cjs/index.js",
|
|
21
|
-
"module": "esm/index.js",
|
|
22
|
-
"homepage": "https://mtcute.dev",
|
|
23
|
-
"repository": {
|
|
24
|
-
"type": "git",
|
|
25
|
-
"url": "https://github.com/mtcute/mtcute"
|
|
26
|
-
}
|
|
27
31
|
}
|