diff --git a/packages/core/src/highlevel/utils/entities.test.ts b/packages/core/src/highlevel/utils/entities.test.ts new file mode 100644 index 00000000..9ac4f245 --- /dev/null +++ b/packages/core/src/highlevel/utils/entities.test.ts @@ -0,0 +1,61 @@ +import { describe, expect, it } from 'vitest' + +import { tl } from '@mtcute/tl' + +import { joinTextWithEntities } from './entities.js' + +const createEntity = (offset: number, length: number): tl.TypeMessageEntity => { + return { + _: 'messageEntityBold', + offset, + length, + } +} + +describe('joinTextWithEntities', () => { + it('should join text with entities using a string delimiter', () => { + expect( + joinTextWithEntities( + [ + { text: 'foo bar baz', entities: [createEntity(0, 3), createEntity(4, 3), createEntity(8, 3)] }, + { text: 'egg spam', entities: [createEntity(4, 4)] }, + { text: 'very spam', entities: [createEntity(0, 4)] }, + ], + ' 🚀 ', + ), + ).toEqual({ + text: 'foo bar baz 🚀 egg spam 🚀 very spam', + entities: [ + createEntity(0, 3), + createEntity(4, 3), + createEntity(8, 3), + createEntity(19, 4), + createEntity(27, 4), + ], + }) + }) + + it('should join text with entities using a TextWithEntities delimiter', () => { + expect( + joinTextWithEntities( + [ + { text: 'foo bar baz', entities: [createEntity(0, 3), createEntity(4, 3), createEntity(8, 3)] }, + { text: 'egg spam', entities: [createEntity(4, 4)] }, + { text: 'very spam', entities: [createEntity(0, 4)] }, + ], + { text: ' 🚀 ', entities: [createEntity(1, 2)] }, + ), + ).toEqual({ + text: 'foo bar baz 🚀 egg spam 🚀 very spam', + entities: [ + createEntity(0, 3), + createEntity(4, 3), + createEntity(8, 3), + createEntity(12, 2), + createEntity(19, 4), + createEntity(24, 2), + createEntity(27, 4), + ], + }) + }) +}) diff --git a/packages/core/src/highlevel/utils/entities.ts b/packages/core/src/highlevel/utils/entities.ts new file mode 100644 index 00000000..9325b46f --- /dev/null +++ b/packages/core/src/highlevel/utils/entities.ts @@ -0,0 +1,62 @@ +import { tl } from '@mtcute/tl' + +import { TextWithEntities } from '../types/misc/entities.js' + +/** + * Join multiple text parts with entities into a single text with entities, + * adjusting the entities' offsets accordingly. + * + * @param parts List of text parts with entities + * @param delim Delimiter to insert between parts + * @returns A single text with entities + * @example + * + * ```ts + * const scoreboardText = joinTextWithEntities( + * apiResult.scoreboard.map((entry) => html`${entry.name}: ${entry.score}`), + * html`
` + * ) + * await tg.sendText(chatId, html`scoreboard:

${scoreboardText}`) + * ``` + */ +export function joinTextWithEntities( + parts: (string | TextWithEntities)[], + delim: string | TextWithEntities = '', +): TextWithEntities { + const textParts: string[] = [] + const newEntities: tl.TypeMessageEntity[] = [] + + let position = 0 + + if (typeof delim === 'string') { + delim = { text: delim } + } + + const pushPart = (part: TextWithEntities) => { + textParts.push(part.text) + const entitiesOffset = position + position += part.text.length + + if (part.entities) { + for (const entity of part.entities) { + newEntities.push({ + ...entity, + offset: entity.offset + entitiesOffset, + }) + } + } + } + + for (const part of parts) { + if (position > 0) { + pushPart(delim) + } + + pushPart(typeof part === 'string' ? { text: part } : part) + } + + return { + text: textParts.join(''), + entities: newEntities, + } +} diff --git a/packages/core/src/highlevel/utils/index.ts b/packages/core/src/highlevel/utils/index.ts index 705c5131..62cc4319 100644 --- a/packages/core/src/highlevel/utils/index.ts +++ b/packages/core/src/highlevel/utils/index.ts @@ -1,5 +1,6 @@ // todo: merge this with the main utils dir? +export * from './entities.js' export * from './file-utils.js' export * from './inline-utils.js' export * from './inspectable.js'