Skip to content

Commit

Permalink
Merge pull request #1 from TMB-01/master
Browse files Browse the repository at this point in the history
[fix] changed algorithm of Applying markup. as previous one was not working well in nested entities.
  • Loading branch information
mcpeblocker authored Dec 8, 2022
2 parents 8999696 + cc14af4 commit a0449cb
Showing 1 changed file with 126 additions and 108 deletions.
234 changes: 126 additions & 108 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -24,128 +24,146 @@ 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");
}

/**
* Get the Markdown format.
* @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) => `<b>${text}</b>`, markdown: (text) => `**${text}**` },
const entityTypes = (text, {url, userId, custom_emoji_id}) => {

italic: { html: (text) => `<i>${text}</i>`, markdown: (text) => `*${text}*` },

underline: { html: (text) => `<u>${text}</u>`, markdown: (text) => text },

strikethrough: {
html: (text) => `<strike>${text}</strike>`,
markdown: (text) => text,
},

spoiler: { html: (text) => text, markdown: (text) => text },

code: {
html: (text) => `<code>${text}</code>`,
markdown: (text) => `\`${text}\``,
},

pre: {
html: (text, { language }) => `<pre>${text}</pre>`,
markdown: (text, { language }) => `\`\`\`${text}\`\`\``,
},

text_link: {
html: (text, { url }) => `<a href="${url}">${text}</a>`,
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: [`<a href="https://t.me/${text.slice(1)}">`, `</a>`],
markdown: [`[`, `](https://t.me/${text.slice(1)})`],
},
hashtag: {
html: [``, ``],
markdown: [``, ``],
},
cashtag: {
html: [``, ``],
markdown: [``, ``],
},
bot_command: {
html: [``, ``],
markdown: [``, ``],
},
url: {
html: [`<a href="${text}">`, `</a>`],
markdown: [`[`, `](${text})`],
},
email: {
html: [`<a href="mailto:${text}">`, `</a>`],
markdown: [`[`, `](mailto:${text})`],
},
phone_number: {
html: [`<a href="tel:${text}">`, `</a>`],
markdown: [`[`, `](tel:${text})`],
},
bold: {
html: [`<b>`, `</b>`],
markdown: [`**`, `**`],
},
italic: {
html: [`<i>`, `</i>`],
markdown: [`*`, `*`],
},
underline: {
html: [`<u>`, `</u>`],
markdown: [``, ``],
},
strikethrough: {
html: [`<s>`, `</s>`],
markdown: [``, ``],
},
spoiler: {
html: [`<span class="tg-spoiler">`, `</span>`],
markdown: [``, ``],
},
code: {
html: [`<code>`, `</code>`],
markdown: [``, ``],
},
pre: {
html: [`<pre>`, `</pre>`],
markdown: [`\`\`\``, `\`\`\``],
},
text_link: {
html: [`<a href="${url}">`, `</a>`],
markdown: [`[`, `](${url})`],
},
text_mention: {
html: [`<a href="tg://user?id=${userId}">`, `</a>`],
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};

0 comments on commit a0449cb

Please sign in to comment.