,
params: HtmlUnparseOptions,
entitiesOffset = 0,
offset = 0,
length = text.length,
): string {
if (!text) return text
if (!entities.length || entities.length === entitiesOffset) {
return escape(text)
.replace(/\n/g, '
')
.replace(/ {2,}/g, (match) => {
return ' '.repeat(match.length)
})
}
const end = offset + length
const html: string[] = []
let lastOffset = 0
for (let i = entitiesOffset; i < entities.length; i++) {
const entity = entities[i]
if (entity.offset >= end) break
let entOffset = entity.offset
let length = entity.length
if (entOffset < 0) {
length += entOffset
entOffset = 0
}
let relativeOffset = entOffset - offset
if (relativeOffset > lastOffset) {
// add missing plain text
html.push(escape(text.substring(lastOffset, relativeOffset)))
} else if (relativeOffset < lastOffset) {
length -= lastOffset - relativeOffset
relativeOffset = lastOffset
}
if (length <= 0 || relativeOffset >= end || relativeOffset < 0) {
continue
}
let skip = false
const substr = text.substr(relativeOffset, length)
if (!substr) continue
const type = entity._
let entityText
if (type === 'messageEntityPre') {
entityText = substr
} else {
entityText = _unparse(substr, entities, params, i + 1, offset + relativeOffset, length)
}
switch (type) {
case 'messageEntityBold':
case 'messageEntityItalic':
case 'messageEntityUnderline':
case 'messageEntityStrike':
case 'messageEntityCode':
case 'messageEntityBlockquote':
case 'messageEntitySpoiler':
{
const tag = (
{
messageEntityBold: 'b',
messageEntityItalic: 'i',
messageEntityUnderline: 'u',
messageEntityStrike: 's',
messageEntityCode: 'code',
messageEntityBlockquote: 'blockquote',
messageEntitySpoiler: 'spoiler',
} as const
)[type]
html.push(`<${tag}>${entityText}${tag}>`)
}
break
case 'messageEntityPre':
html.push(
`${
params.syntaxHighlighter && entity.language ?
params.syntaxHighlighter(entityText, entity.language) :
entityText
}
`,
)
break
case 'messageEntityEmail':
html.push(`${entityText}`)
break
case 'messageEntityUrl':
html.push(`${entityText}`)
break
case 'messageEntityTextUrl':
html.push(`${entityText}`)
break
case 'messageEntityMentionName':
html.push(`${entityText}`)
break
default:
skip = true
break
}
lastOffset = relativeOffset + (skip ? 0 : length)
}
html.push(escape(text.substr(lastOffset)))
return html.join('')
}
/**
* Add HTML formatting to the text given the plain text and entities contained in it.
*/
function unparse(input: InputText, options?: HtmlUnparseOptions): string {
if (typeof input === 'string') {
return _unparse(input, [], options ?? {})
}
return _unparse(input.text, input.entities ?? [], options ?? {})
}
// typedoc doesn't support this yet, so we'll have to do it manually
// https://github.com/TypeStrong/typedoc/issues/2436
export const html: {
/**
* Tagged template based HTML-to-entities parser function
*
* Additionally, `md` function has two static methods:
* - `html.escape` - escape a string to be safely used in HTML
* (should not be needed in most cases, as `html` function itself handles all `string`s
* passed to it automatically as plain text)
* - `html.unparse` - add HTML formatting to the text given the plain text and entities contained in it
*
* @example
* ```typescript
* const text = html`${user.displayName}`
* ```
*/
(
strings: TemplateStringsArray,
...sub: (InputText | MessageEntity | boolean | number | undefined | null)[]
): TextWithEntities
/**
* A variant taking a plain JS string as input
* and parsing it.
*
* Useful for cases when you already have a string
* (e.g. from some server) and want to parse it.
*
* @example
* ```typescript
* const string = 'hello'
* const text = html(string)
* ```
*/
(string: string): TextWithEntities
escape: typeof escape
unparse: typeof unparse
} = Object.assign(parse, {
escape,
unparse,
})