feat(parse-mode): support spoiler entity
This commit is contained in:
parent
05be58f903
commit
1ef1c0669d
7 changed files with 76 additions and 50 deletions
|
@ -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'
|
||||
|
|
|
@ -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 |
|
||||
|
|
|
@ -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>`
|
||||
|
|
|
@ -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'
|
||||
)
|
||||
})
|
||||
|
||||
|
|
|
@ -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>||spoiler||</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><pre language="javascript"><br> const a = ``<br></pre></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><pre language="javascript"><br> const a = ``<br></pre></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><code>var a = \`hello\`</code></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><code>var a = \`hello\`</code></code> |
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
)
|
||||
})
|
||||
|
||||
|
|
Loading…
Reference in a new issue