diff --git a/index.js b/index.js index 0fb6c27..d04a823 100644 --- a/index.js +++ b/index.js @@ -12,7 +12,7 @@ class EntityMessage { // validate params if (!text || typeof text !== "string" || !entities || !entities.length) { throw new Error( - `Expected string in field text - got ${typeof text}, array/object in field entities - got ${typeof entities}` + `Expected string in field text - got ${typeof text}, array/object in field entities - got ${typeof entities}` ); } this.text = text; @@ -24,19 +24,7 @@ class EntityMessage { * @return {string} HTML formatted text */ get html() { - let content = this.text; - let totalOffset = 0; - for (let entity of this.entities) { - let replacement = formatEntity(this.text, entity, "html"); - content = spliceString( - content, - totalOffset + entity.offset, - entity.length, - replacement - ); - totalOffset += replacement.length - entity.length; - } - return content; + return applyEntity(this.text, this.entities, "html"); } /** @@ -44,108 +32,138 @@ class EntityMessage { * @return {string} Markdown formatted text */ get markdown() { - let content = this.text; - let totalOffset = 0; - for (let entity of this.entities) { - let replacement = formatEntity(this.text, entity, "markdown"); - content = spliceString( - content, - entity.offset, - entity.length, - replacement - ); - totalOffset += replacement.length - entity.length; - } - return content; + return applyEntity(this.text, this.entities, "markdown"); } } // Helper functions -function formatEntity(text, entity, format) { - let entityText = text.substring(entity.offset, entity.offset + entity.length); - let options = {}; - switch (entity.type) { - case "text_link": - options.url = entity.url; - break; - case "text_mention": - options.user = entity.user; - break; - case "pre": - options.language = entity.language; - break; - case "custom_emoji": - options.custom_emoji_id = entity.custom_emoji_id; - break; +function applyEntity(mainText = "", entities, markupType = "html") { + if (markupType !== "html" || markupType !== "markdown") return mainText; + if (!entities.length) return mainText; + if (!mainText.length) return mainText; + + let content = mainText; + const addedTags = []; + for (let entity of entities) { + const { + type, + offset, + length, + url, + user, + language, + custom_emoji_id + } = entity; + const text = mainText.slice(offset, offset + length); + const [opening, closing] = (entityTypes(text, { + url, + userId: user?.id, + custom_emoji_id, + language + })[type][markupType] || ["", ""]); + + if (opening !== "" || closing !== "") { + const {start: beforeStart, end: beforeEnd} = addedTags.reduce((t, {startAt, tag}) => { + let {start, end} = t; + if (startAt <= offset) { + start += tag.length; + } + if (startAt < (offset + length)) { + end += tag.length + } + return {start, end}; + }, { + start: 0, end: 0 + }) + + const start = offset + beforeStart; + const end = offset + length + beforeEnd; + addedTags.push({startAt: offset, tag: opening}); + addedTags.push({startAt: offset + length, tag: closing}); + content = content.slice(0, start) + opening + content.slice(start, end) + closing + content.slice(end); + } } - return entityTypes[entity.type][format](entityText, options); + return content; } -const entityTypes = { - mention: { html: (text) => text, markdown: (text) => text }, - - hashtag: { html: (text) => text, markdown: (text) => text }, - - cashtag: { html: (text) => text, markdown: (text) => text }, - - bot_command: { html: (text) => text, markdown: (text) => text }, - - url: { html: (text) => text, markdown: (text) => text }, - - email: { html: (text) => text, markdown: (text) => text }, - - phone_number: { html: (text) => text, markdown: (text) => text }, - - bold: { html: (text) => `${text}`, markdown: (text) => `**${text}**` }, +const entityTypes = (text, {url, userId, custom_emoji_id}) => { - italic: { html: (text) => `${text}`, markdown: (text) => `*${text}*` }, - - underline: { html: (text) => `${text}`, markdown: (text) => text }, - - strikethrough: { - html: (text) => `${text}`, - markdown: (text) => text, - }, - - spoiler: { html: (text) => text, markdown: (text) => text }, - - code: { - html: (text) => `${text}`, - markdown: (text) => `\`${text}\``, - }, - - pre: { - html: (text, { language }) => `
${text}
`, - markdown: (text, { language }) => `\`\`\`${text}\`\`\``, - }, - - text_link: { - html: (text, { url }) => `${text}`, - markdown: (text, { url }) => `[${text}](${url})`, - }, - - text_mention: { - html: (text, { user }) => text, - markdown: (text, { user }) => text, - }, - - custom_emoji: { - html: (text, { custom_emoji_id }) => "", - markdown: (text, { custom_emoji_id }) => "", - }, -}; - -// Utility function -function spliceString(str, index, count, add) { - // We cannot pass negative indexes directly to the 2nd slicing operation. - if (index < 0) { - index = str.length + index; - if (index < 0) { - index = 0; - } + /** + * [markUpType]: { + * [markUpName]: [openingTag, closingTag], + * }, + * */ + + return { + mention: { + html: [``, ``], + markdown: [`[`, `](https://t.me/${text.slice(1)})`], + }, + hashtag: { + html: [``, ``], + markdown: [``, ``], + }, + cashtag: { + html: [``, ``], + markdown: [``, ``], + }, + bot_command: { + html: [``, ``], + markdown: [``, ``], + }, + url: { + html: [``, ``], + markdown: [`[`, `](${text})`], + }, + email: { + html: [``, ``], + markdown: [`[`, `](mailto:${text})`], + }, + phone_number: { + html: [``, ``], + markdown: [`[`, `](tel:${text})`], + }, + bold: { + html: [``, ``], + markdown: [`**`, `**`], + }, + italic: { + html: [``, ``], + markdown: [`*`, `*`], + }, + underline: { + html: [``, ``], + markdown: [``, ``], + }, + strikethrough: { + html: [``, ``], + markdown: [``, ``], + }, + spoiler: { + html: [``, ``], + markdown: [``, ``], + }, + code: { + html: [``, ``], + markdown: [``, ``], + }, + pre: { + html: [`
`, `
`], + markdown: [`\`\`\``, `\`\`\``], + }, + text_link: { + html: [``, ``], + markdown: [`[`, `](${url})`], + }, + text_mention: { + html: [``, ``], + markdown: [`[`, `](tg://user?id=${userId})`], + }, + custom_emoji: { + html: [``, ``], + markdown: [``, ``], + }, } - - return str.slice(0, index) + (add || "") + str.slice(index + count); } -module.exports = { EntityMessage }; +module.exports = {EntityMessage};