discourse/app/assets/javascripts/pretty-text/engines/discourse-markdown/emoji.js.es6

116 lines
3.5 KiB
JavaScript

import { registerOption } from 'pretty-text/pretty-text';
import { buildEmojiUrl, isCustomEmoji } from 'pretty-text/emoji';
import { translations } from 'pretty-text/emoji/data';
let _unicodeReplacements;
let _unicodeRegexp;
export function setUnicodeReplacements(replacements) {
_unicodeReplacements = replacements;
if (replacements) {
// We sort and reverse to match longer emoji sequences first
_unicodeRegexp = new RegExp(Object.keys(replacements).sort().reverse().join("|"), "g");
}
};
function escapeRegExp(s) {
return s.replace(/[-/\\^$*+?.()|[\]{}]/gi, '\\$&');
}
function checkPrev(prev) {
if (prev && prev.length) {
const lastToken = prev[prev.length-1];
if (lastToken && lastToken.charAt) {
const lastChar = lastToken.charAt(lastToken.length-1);
if (!/\W/.test(lastChar)) return false;
}
}
return true;
}
registerOption((siteSettings, opts, state) => {
opts.features.emoji = !!siteSettings.enable_emoji;
opts.emojiSet = siteSettings.emoji_set || "";
opts.customEmoji = state.customEmoji;
});
export function setup(helper) {
helper.whiteList('img.emoji');
function imageFor(code) {
code = code.toLowerCase();
const opts = helper.getOptions();
const url = buildEmojiUrl(code, opts);
if (url) {
const title = `:${code}:`;
const classes = isCustomEmoji(code, opts) ? "emoji emoji-custom" : "emoji";
return ['img', { href: url, title, 'class': classes, alt: title }];
}
}
const translationsWithColon = {};
Object.keys(translations).forEach(t => {
if (t[0] === ':') {
translationsWithColon[t] = translations[t];
} else {
const replacement = translations[t];
helper.inlineReplace(t, (token, match, prev) => {
return checkPrev(prev) ? imageFor(replacement) : token;
});
}
});
const translationColonRegexp = new RegExp(Object.keys(translationsWithColon).map(t => `(${escapeRegExp(t)})`).join("|"));
helper.registerInline(':', (text, match, prev) => {
const endPos = text.indexOf(':', 1);
const firstSpace = text.search(/\s/);
if (!checkPrev(prev)) { return; }
// If there is no trailing colon, check our translations that begin with colons
if (endPos === -1 || (firstSpace !== -1 && endPos > firstSpace)) {
translationColonRegexp.lastIndex = 0;
const m = translationColonRegexp.exec(text);
if (m && m[0] && text.indexOf(m[0]) === 0) {
// Check outer edge
const lastChar = text.charAt(m[0].length);
if (lastChar && !/\s/.test(lastChar)) return;
const contents = imageFor(translationsWithColon[m[0]]);
if (contents) {
return [m[0].length, contents];
}
}
return;
}
let between;
const emojiNameMatch = text.match(/(?:.*?)(:(?!:).?[\w-]*(?::t\d)?:)/);
if (emojiNameMatch) {
between = emojiNameMatch[0].slice(1, -1);
} else {
between = text.slice(1, -1);
}
const contents = imageFor(between);
if (contents) {
return [text.indexOf(between, 1) + between.length + 1, contents];
}
});
helper.addPreProcessor(text => {
if (_unicodeReplacements) {
_unicodeRegexp.lastIndex = 0;
let m;
while ((m = _unicodeRegexp.exec(text)) !== null) {
let replacement = ":" + _unicodeReplacements[m[0]] + ":";
const before = text.charAt(m.index-1);
if (!/\B/.test(before)) {
replacement = "\u200b" + replacement;
}
text = text.replace(m[0], replacement);
}
}
return text;
});
}