@imessaging/core 0.8.2 → 0.8.3
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/package.json +1 -1
- package/src/index.ts +2 -0
- package/src/markup.ts +50 -1
- package/src/types.ts +20 -1
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ export {
|
|
|
18
18
|
canMarkRead,
|
|
19
19
|
canReact,
|
|
20
20
|
canReceive,
|
|
21
|
+
canReceiveEdits,
|
|
21
22
|
canShowTyping,
|
|
22
23
|
hasCommandMenu,
|
|
23
24
|
isTelegramRecipient,
|
|
@@ -36,6 +37,7 @@ export type {
|
|
|
36
37
|
export type {
|
|
37
38
|
EditableMessage,
|
|
38
39
|
EditableMessageTransport,
|
|
40
|
+
EditedMessageTransport,
|
|
39
41
|
EditResult,
|
|
40
42
|
EmailRecipient,
|
|
41
43
|
HeadhunterRecipient,
|
package/src/markup.ts
CHANGED
|
@@ -13,7 +13,20 @@
|
|
|
13
13
|
/** Как написан исходный текст сообщения. */
|
|
14
14
|
export type TextFormat = "plain" | "markdown" | "html";
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Общая модель для всех каналов: Telegram и MAX отдают выделения со смещениями, WhatsApp — звёздочки в тексте,
|
|
18
|
+
* почта — HTML; транспорт переводит своё в эти типы, а для чего пары нет (упоминания, хэштеги) — отбрасывает.
|
|
19
|
+
*/
|
|
20
|
+
export type TextEntityType =
|
|
21
|
+
| "bold"
|
|
22
|
+
| "italic"
|
|
23
|
+
| "underline"
|
|
24
|
+
| "strike"
|
|
25
|
+
| "spoiler"
|
|
26
|
+
| "code"
|
|
27
|
+
| "pre"
|
|
28
|
+
| "link"
|
|
29
|
+
| "quote";
|
|
17
30
|
|
|
18
31
|
export type TextEntity = {
|
|
19
32
|
type: TextEntityType;
|
|
@@ -96,6 +109,14 @@ function parseInline(src: string, out: Builder): void {
|
|
|
96
109
|
}
|
|
97
110
|
|
|
98
111
|
const pair = src.slice(i, i + 2);
|
|
112
|
+
if (pair === "||" && !isSpace(src[i + 2])) {
|
|
113
|
+
const end = findCloser(src, i + 2, "||", (at) => at > i + 2 && !isSpace(src[at - 1]));
|
|
114
|
+
if (end !== -1) {
|
|
115
|
+
wrap(out, "spoiler", () => parseInline(src.slice(i + 2, end), out));
|
|
116
|
+
i = end + 2;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
99
120
|
if (pair === "**" || pair === "__" || pair === "~~") {
|
|
100
121
|
// `__` — жирный только на границе слова: `snake__case` остаётся как есть.
|
|
101
122
|
const bounded = pair === "__";
|
|
@@ -166,8 +187,27 @@ export function parseMarkdown(source: string): FormattedText {
|
|
|
166
187
|
|
|
167
188
|
function parseLines(block: string, out: Builder): void {
|
|
168
189
|
const lines = block.split("\n");
|
|
190
|
+
// Подряд идущие строки `> …` — одна цитата, как её показывает любой мессенджер.
|
|
191
|
+
let quote: { offset: number; at: number } | undefined;
|
|
192
|
+
const closeQuote = (end: number) => {
|
|
193
|
+
if (quote && end > quote.offset) {
|
|
194
|
+
out.entities.splice(quote.at, 0, {
|
|
195
|
+
type: "quote",
|
|
196
|
+
offset: quote.offset,
|
|
197
|
+
length: end - quote.offset,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
quote = undefined;
|
|
201
|
+
};
|
|
169
202
|
lines.forEach((line, n) => {
|
|
203
|
+
const quoted = /^>\s?(.*)$/.exec(line);
|
|
204
|
+
if (!quoted) closeQuote(out.text.length);
|
|
170
205
|
if (n > 0) out.text += "\n";
|
|
206
|
+
if (quoted) {
|
|
207
|
+
quote ??= { offset: out.text.length, at: out.entities.length };
|
|
208
|
+
parseInline(quoted[1]!, out);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
171
211
|
const heading = /^#{1,6}\s+(.*?)\s*#*$/.exec(line);
|
|
172
212
|
if (heading) {
|
|
173
213
|
wrap(out, "bold", () => parseInline(heading[1]!, out));
|
|
@@ -175,6 +215,7 @@ function parseLines(block: string, out: Builder): void {
|
|
|
175
215
|
}
|
|
176
216
|
parseInline(line.replace(/^(\s*)[*•●·]\s+/, "$1- "), out);
|
|
177
217
|
});
|
|
218
|
+
closeQuote(out.text.length);
|
|
178
219
|
}
|
|
179
220
|
|
|
180
221
|
const HTML_TAGS: Record<string, TextEntityType> = {
|
|
@@ -190,6 +231,8 @@ const HTML_TAGS: Record<string, TextEntityType> = {
|
|
|
190
231
|
code: "code",
|
|
191
232
|
pre: "pre",
|
|
192
233
|
a: "link",
|
|
234
|
+
"tg-spoiler": "spoiler",
|
|
235
|
+
blockquote: "quote",
|
|
193
236
|
};
|
|
194
237
|
|
|
195
238
|
function decodeHtml(s: string): string {
|
|
@@ -310,6 +353,10 @@ function openTag(e: TextEntity): string {
|
|
|
310
353
|
return e.language ? `<pre><code class="language-${escapeAttribute(e.language)}">` : "<pre>";
|
|
311
354
|
case "link":
|
|
312
355
|
return `<a href="${escapeAttribute(e.url ?? "")}">`;
|
|
356
|
+
case "spoiler":
|
|
357
|
+
return "<tg-spoiler>";
|
|
358
|
+
case "quote":
|
|
359
|
+
return "<blockquote>";
|
|
313
360
|
}
|
|
314
361
|
}
|
|
315
362
|
|
|
@@ -319,9 +366,11 @@ function closeTag(e: TextEntity): string {
|
|
|
319
366
|
italic: "i",
|
|
320
367
|
underline: "u",
|
|
321
368
|
strike: "s",
|
|
369
|
+
spoiler: "tg-spoiler",
|
|
322
370
|
code: "code",
|
|
323
371
|
pre: "pre",
|
|
324
372
|
link: "a",
|
|
373
|
+
quote: "blockquote",
|
|
325
374
|
}[e.type];
|
|
326
375
|
return e.type === "pre" && e.language ? "</code></pre>" : `</${name}>`;
|
|
327
376
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TextFormat } from "./markup";
|
|
1
|
+
import type { TextEntity, TextFormat } from "./markup";
|
|
2
2
|
|
|
3
3
|
export type UsernameRecipient = {
|
|
4
4
|
type: "username";
|
|
@@ -300,6 +300,11 @@ export type IncomingMessage = {
|
|
|
300
300
|
editedAt?: Date;
|
|
301
301
|
/** В сообщении упомянут аккаунт или это ответ на его сообщение (так Telegram определяет упоминание); нет — канал не сообщает. */
|
|
302
302
|
mentionsMe?: boolean;
|
|
303
|
+
/**
|
|
304
|
+
* Форматирование текста в той же модели, что исходящая разметка, смещения в UTF-16;
|
|
305
|
+
* undefined = канал не сообщает форматирование, [] = сообщил, форматирования нет.
|
|
306
|
+
*/
|
|
307
|
+
entities?: TextEntity[];
|
|
303
308
|
attachments: IncomingAttachment[];
|
|
304
309
|
/**
|
|
305
310
|
* Адреса из сообщения, в порядке появления.
|
|
@@ -348,6 +353,20 @@ export function canReceive(transport: MessageTransport): transport is IncomingMe
|
|
|
348
353
|
return typeof (transport as IncomingMessageTransport).onMessage === "function";
|
|
349
354
|
}
|
|
350
355
|
|
|
356
|
+
/**
|
|
357
|
+
* Канал, умеющий сообщать о правках сообщений.
|
|
358
|
+
*
|
|
359
|
+
* Отдельная подписка, потому что подписчики onMessage не ждут правок и сочтут повторный
|
|
360
|
+
* messageId за новое сообщение.
|
|
361
|
+
*/
|
|
362
|
+
export interface EditedMessageTransport extends MessageTransport {
|
|
363
|
+
onEdit(handler: (message: IncomingMessage) => void | Promise<void>): () => void;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export function canReceiveEdits(transport: MessageTransport): transport is EditedMessageTransport {
|
|
367
|
+
return typeof (transport as EditedMessageTransport).onEdit === "function";
|
|
368
|
+
}
|
|
369
|
+
|
|
351
370
|
/** Пункт меню команд канала: `/menu` и подпись рядом с ним. */
|
|
352
371
|
export type ChannelCommand = {
|
|
353
372
|
/** Без ведущей косой черты. */
|