feat(parse-mode): support spoiler entity

This commit is contained in:
teidesu 2022-05-06 00:40:47 +03:00
parent 05be58f903
commit 1ef1c0669d
7 changed files with 76 additions and 50 deletions

View file

@ -17,6 +17,7 @@ const entityToType: Partial<
messageEntityPhone: 'phone_number',
messageEntityPre: 'pre',
messageEntityStrike: 'strikethrough',
messageEntitySpoiler: 'spoiler',
messageEntityTextUrl: 'text_link',
messageEntityUnderline: 'underline',
messageEntityUrl: 'url',
@ -54,6 +55,7 @@ export namespace MessageEntity {
| 'italic'
| 'underline'
| 'strikethrough'
| 'spoiler'
| 'code'
| 'pre'
| 'text_link'

View file

@ -47,6 +47,7 @@ Inline entities are entities that are in-line with other text. We support these
| Italic | `<b>text</b>` | _text_ |
| Underline | `<u>text</u>` | <u>text</u> |
| Strikethrough | `<s>text</s>` | ~~text~~ |
| Spoiler | `<spoiler>text</spoiler>` | N/A |
| Monospace (code) | `<code>text</code>` | `text` |
| Text link | `<a href="https://google.com">Google</a>` | [Google](https://google.com) |
| Text mention | `<a href="tg://user?id=1234567">Name</a>` | N/A |

View file

@ -178,6 +178,13 @@ export class HtmlMessageEntityParser implements IMessageEntityParser {
language: attribs.language ?? '',
}
break
case 'spoiler':
entity = {
_: 'messageEntitySpoiler',
offset: plainText.length,
length: 0,
}
break
case 'a': {
let url = attribs.href
if (!url) return
@ -343,22 +350,25 @@ export class HtmlMessageEntityParser implements IMessageEntityParser {
break
case 'code':
case 'pre':
case 'blockquote':
html.push(
`<${type}${
type === 'pre' && entity.language
entity.language
? ` language="${entity.language}"`
: ''
}>${
this._syntaxHighlighter && entity.language
? this._syntaxHighlighter(
entityText,
entity.language!
entity.language
)
: entityText
}</${type}>`
)
break
case 'blockquote':
case 'spoiler':
html.push(`<${type}>${entityText}</${type}>`)
break
case 'email':
html.push(
`<a href="mailto:${entityText}">${entityText}</a>`

View file

@ -59,15 +59,16 @@ describe('HtmlMessageEntityParser', () => {
)
})
it('should handle <code>, <pre>, <blockquote> tags', () => {
it('should handle <code>, <pre>, <blockquote>, <spoiler> tags', () => {
test(
'plain code pre blockquote plain',
'plain code pre blockquote spoiler plain',
[
createEntity('messageEntityCode', 6, 4),
createEntity('messageEntityPre', 11, 3),
createEntity('messageEntityBlockquote', 15, 10),
createEntity('messageEntitySpoiler', 26, 7),
],
'plain <code>code</code> <pre>pre</pre> <blockquote>blockquote</blockquote> plain'
'plain <code>code</code> <pre>pre</pre> <blockquote>blockquote</blockquote> <spoiler>spoiler</spoiler> plain'
)
})
@ -309,15 +310,16 @@ describe('HtmlMessageEntityParser', () => {
)
})
it('should handle <code>, <pre>, <blockquote> tags', () => {
it('should handle <code>, <pre>, <blockquote>, <spoiler> tags', () => {
test(
'plain <code>code</code> <pre>pre</pre> <blockquote>blockquote</blockquote> plain',
'plain <code>code</code> <pre>pre</pre> <blockquote>blockquote</blockquote> <spoiler>spoiler</spoiler> plain',
[
createEntity('messageEntityCode', 6, 4),
createEntity('messageEntityPre', 11, 3, { language: '' }),
createEntity('messageEntityBlockquote', 15, 10),
createEntity('messageEntitySpoiler', 26, 7),
],
'plain code pre blockquote plain'
'plain code pre blockquote spoiler plain'
)
})

View file

@ -35,6 +35,7 @@ Supported entities:
- Italic, tag is `__`
- Underline, tag is `--` (_NON-STANDARD_)
- Strikethrough, tag is `~~`
- Spoiler, tag is `||` (_NON-STANDARD_)
- Code (monospaced font), tag is <code>`</code>
- Note that escaping text works differently inside code, see below.
@ -44,16 +45,17 @@ Supported entities:
>
> This eliminates a lot of confusion, like: `_bold_`_bold_, `**italic**` → **italic**
| Code | Result (visual) | Result (as HTML)
|---|---|---|
| `**bold**` | **bold** | `<b>bold</b>`
| `__italic__` | __italic__ | `<i>italic</i>`
| `--underline` | <u>underline</u> | `<u>underline</u>`
| `~~strikethrough~~` | ~~strikethrough~~ | `<s>strikethrough</s>`
| `*whatever*` | \*whatever\* | `*whatever*`
| `_whatever_` | \_whatever\_ | `_whatever_`
| <code>\`hello world\`</code> | `hello world` | `<code>hello world</code>`
| <code>\`__text__\`</code> | `__text__` | `<code>__text__</code>`
| Code | Result (visual) | Result (as HTML) |
|----------------------------------------------|-------------------|------------------------------|
| `**bold**` | **bold** | `<b>bold</b>` |
| `__italic__` | __italic__ | `<i>italic</i>` |
| `--underline--` | <u>underline</u> | `<u>underline</u>` |
| `~~strikethrough~~` | ~~strikethrough~~ | `<s>strikethrough</s>` |
| <code>&#124;&#124;spoiler&#124;&#124;</code> | N/A | `<spoiler>spoiler</spoiler>` |
| `*whatever*` | \*whatever\* | `*whatever*` |
| `_whatever_` | \_whatever\_ | `_whatever_` |
| <code>\`hello world\`</code> | `hello world` | `<code>hello world</code>` |
| <code>\`__text__\`</code> | `__text__` | `<code>__text__</code>` |
### Pre
@ -62,11 +64,11 @@ Pre represents a single block of code, optionally with a language.
This entity starts with <code>\`\`\`</code> (triple backtick), optionally followed with language name and a must be
followed with a line break, and ends with <code>\`\`\`</code> (triple backtick), optionally preceded with a line break.
| Code | Result (visual) | Result (as HTML)
|---|---|---|
| <pre><code>\`\`\`<br>hello<br>\`\`\`</code></pre> | `hello` | `<pre>hello</pre>`
| <pre><code>\`\`\`<br>hello\`\`\`</code></pre> | `hello` | `<pre>hello</pre>`
| <pre><code>\`\`\`javascript<br>const a = ``<br>\`\`\`</code></pre> | <code>const a = ``</code> | <pre><code>&lt;pre language="javascript"&gt;<br> const a = ``<br>&lt;/pre&gt;</code></pre>
| Code | Result (visual) | Result (as HTML) |
|--------------------------------------------------------------------|---------------------------|---------------------------------------------------------------------------------------------|
| <pre><code>\`\`\`<br>hello<br>\`\`\`</code></pre> | `hello` | `<pre>hello</pre>` |
| <pre><code>\`\`\`<br>hello\`\`\`</code></pre> | `hello` | `<pre>hello</pre>` |
| <pre><code>\`\`\`javascript<br>const a = ``<br>\`\`\`</code></pre> | <code>const a = ``</code> | <pre><code>&lt;pre language="javascript"&gt;<br> const a = ``<br>&lt;/pre&gt;</code></pre> |
### Links
@ -87,13 +89,13 @@ where `1234567` is the ID of the user you want to mention
> where `abc` is user's access hash written as a base-16 *unsigned* integer.
> Order of the parameters does matter, i.e. `tg://user?hash=abc&id=1234567` will not be processed as expected.
| Code | Result (visual) | Result (as HTML)
|---|---|---|
| `[Google](https://google.com)` | [Google](https://google.com) | `<a href="https://google.com">Google</a>`
| `[__Google__](https://google.com)` | [_Google_](https://google.com) | `<a href="https://google.com"><i>Google</i></a>`
| `[empty link]()` | empty link | `empty link`
| `[empty link]` | [empty link] | `[empty link]`
| `[User](tg://user?id=1234567)` | N/A | N/A
| Code | Result (visual) | Result (as HTML) |
|------------------------------------|--------------------------------|--------------------------------------------------|
| `[Google](https://google.com)` | [Google](https://google.com) | `<a href="https://google.com">Google</a>` |
| `[__Google__](https://google.com)` | [_Google_](https://google.com) | `<a href="https://google.com"><i>Google</i></a>` |
| `[empty link]()` | empty link | `empty link` |
| `[empty link]` | [empty link] | `[empty link]` |
| `[User](tg://user?id=1234567)` | N/A | N/A |
### Nested and overlapping entities
@ -103,10 +105,10 @@ code) can be overlapped.
Since inline entities are only defined by their tag, and nesting same entities doesn't make sense, you can think of the
tags just as start/end markers, and not in terms of nesting.
| Code | Result (visual) | Result (as HTML)
|---|---|---|
| `**Welcome back, __User__!**` | **Welcome back, _User_!** | `<b>Welcome back, <i>User</i>!</b>`
| `**bold __and** italic__` | **bold _and_** _italic_ | `<b>bold <i>and</i></b><i> italic</i>`
| Code | Result (visual) | Result (as HTML) |
|-------------------------------|---------------------------|----------------------------------------|
| `**Welcome back, __User__!**` | **Welcome back, _User_!** | `<b>Welcome back, <i>User</i>!</b>` |
| `**bold __and** italic__` | **bold _and_** _italic_ | `<b>bold <i>and</i></b><i> italic</i>` |
## Escaping
@ -128,10 +130,10 @@ like `"\\_\\_not italic\\_\\_`.
> In theory, you could escape every single non-markup character, but why would you want to do that 😜
| Code | Result (visual) | Result (as HTML)
|---|---|---|
| `\_\_not italic\_\_` | \_\_not italic\_\_ | `__not italic__`
| `__italic \_ text__` | _italic \_ text_ | `<i>italic _ text </i>`
| <code>\`__not italic__\`</code> | `__not italic__` | `<code>__not italic__</code>`
| <code>C:\\\\Users\\\\Guest</code> | C:\Users\Guest | `C:\Users\Guest`
| <code>\`var a = \\\`hello\\\`\`</code> | <code>var a = \`hello\`</code> | <code>&lt;code&gt;var a = \`hello\`&lt;/code&gt;</code>
| Code | Result (visual) | Result (as HTML) |
|----------------------------------------|--------------------------------|---------------------------------------------------------|
| `\_\_not italic\_\_` | \_\_not italic\_\_ | `__not italic__` |
| `__italic \_ text__` | _italic \_ text_ | `<i>italic _ text </i>` |
| <code>\`__not italic__\`</code> | `__not italic__` | `<code>__not italic__</code>` |
| <code>C:\\\\Users\\\\Guest</code> | C:\Users\Guest | `C:\Users\Guest` |
| <code>\`var a = \\\`hello\\\`\`</code> | <code>var a = \`hello\`</code> | <code>&lt;code&gt;var a = \`hello\`&lt;/code&gt;</code> |

View file

@ -10,10 +10,11 @@ const TAG_BOLD = '**'
const TAG_ITALIC = '__'
const TAG_UNDERLINE = '--'
const TAG_STRIKE = '~~'
const TAG_SPOILER = '||'
const TAG_CODE = '`'
const TAG_PRE = '```'
const TO_BE_ESCAPED = /[*_\-~`[\\\]]/g
const TO_BE_ESCAPED = /[*_\-~`[\\\]|]/g
/**
* Tagged template based helper for escaping entities in Markdown
@ -248,7 +249,7 @@ export class MarkdownMessageEntityParser implements IMessageEntityParser {
if (c === text[pos + 1]) {
// maybe (?) start or end of an entity
let type: 'Italic' | 'Bold' | 'Underline' | 'Strike' | null =
let type: 'Italic' | 'Bold' | 'Underline' | 'Strike' | 'Spoiler' | null =
null
switch (c) {
case '_':
@ -263,6 +264,9 @@ export class MarkdownMessageEntityParser implements IMessageEntityParser {
case '~':
type = 'Strike'
break
case '|':
type = 'Spoiler'
break
}
if (type) {
@ -352,6 +356,9 @@ export class MarkdownMessageEntityParser implements IMessageEntityParser {
case 'strikethrough':
startTag = endTag = TAG_STRIKE
break
case 'spoiler':
startTag = endTag = TAG_SPOILER
break
case 'code':
startTag = endTag = TAG_CODE
break

View file

@ -51,16 +51,17 @@ describe('MarkdownMessageEntityParser', () => {
test('some text', [], 'some text')
})
it('should handle bold, italic, underline and strikethrough', () => {
it('should handle bold, italic, underline, strikethrough and spoiler', () => {
test(
'plain bold italic underline strikethrough plain',
'plain bold italic underline strikethrough spoiler plain',
[
createEntity('messageEntityBold', 6, 4),
createEntity('messageEntityItalic', 11, 6),
createEntity('messageEntityUnderline', 18, 9),
createEntity('messageEntityStrike', 28, 13),
createEntity('messageEntitySpoiler', 42, 7),
],
'plain **bold** __italic__ --underline-- ~~strikethrough~~ plain'
'plain **bold** __italic__ --underline-- ~~strikethrough~~ ||spoiler|| plain'
)
})
@ -294,16 +295,17 @@ describe('MarkdownMessageEntityParser', () => {
}
}
it('should handle bold, italic, underline and strikethrough', () => {
it('should handle bold, italic, underline, spoiler and strikethrough', () => {
test(
'plain **bold** __italic__ --underline-- ~~strikethrough~~ plain',
'plain **bold** __italic__ --underline-- ~~strikethrough~~ ||spoiler|| plain',
[
createEntity('messageEntityBold', 6, 4),
createEntity('messageEntityItalic', 11, 6),
createEntity('messageEntityUnderline', 18, 9),
createEntity('messageEntityStrike', 28, 13),
createEntity('messageEntitySpoiler', 42, 7),
],
'plain bold italic underline strikethrough plain'
'plain bold italic underline strikethrough spoiler plain'
)
})