126 lines
3.5 KiB
JavaScript
126 lines
3.5 KiB
JavaScript
|
import { EMOTES_48px as EMOTES } from 'constants/emotes';
|
||
|
import visit from 'unist-util-visit';
|
||
|
|
||
|
const EMOTE_NODE_TYPE = 'emote';
|
||
|
const RE_EMOTE = /:\+1:|:-1:|:[\w-]+:/;
|
||
|
|
||
|
// ***************************************************************************
|
||
|
// Tokenize emote
|
||
|
// ***************************************************************************
|
||
|
|
||
|
function findNextEmote(value, fromIndex, strictlyFromIndex) {
|
||
|
let begin = 0;
|
||
|
|
||
|
while (begin < value.length) {
|
||
|
const match = value.substring(begin).match(RE_EMOTE);
|
||
|
|
||
|
if (!match) return null;
|
||
|
|
||
|
match.index += begin;
|
||
|
|
||
|
if (strictlyFromIndex && match.index !== fromIndex) {
|
||
|
if (match.index > fromIndex) {
|
||
|
// Already gone past desired index. Skip the rest.
|
||
|
return null;
|
||
|
} else {
|
||
|
// Next match might fit 'fromIndex'.
|
||
|
begin = match.index + match[0].length;
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (fromIndex > 0 && fromIndex > match.index && fromIndex < match.index + match[0].length) {
|
||
|
// Skip previously-rejected word
|
||
|
// This assumes that a non-zero 'fromIndex' means that a previous lookup has failed.
|
||
|
begin = match.index + match[0].length;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
const str = match[0];
|
||
|
|
||
|
if (EMOTES.some(({ name }) => str.toUpperCase() === name)) {
|
||
|
// Profit!
|
||
|
return { text: str, index: match.index };
|
||
|
}
|
||
|
|
||
|
if (strictlyFromIndex && match.index >= fromIndex) {
|
||
|
return null; // Since it failed and we've gone past the desired index, skip the rest.
|
||
|
}
|
||
|
|
||
|
begin = match.index + match[0].length;
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
function locateEmote(value, fromIndex) {
|
||
|
const emote = findNextEmote(value, fromIndex, false);
|
||
|
return emote ? emote.index : -1;
|
||
|
}
|
||
|
|
||
|
// Generate 'emote' markdown node
|
||
|
const createEmoteNode = (text) => ({
|
||
|
type: EMOTE_NODE_TYPE,
|
||
|
value: text,
|
||
|
children: [{ type: 'text', value: text }],
|
||
|
});
|
||
|
|
||
|
// Generate a markdown image from emote
|
||
|
function tokenizeEmote(eat, value, silent) {
|
||
|
if (silent) return true;
|
||
|
|
||
|
const emote = findNextEmote(value, 0, true);
|
||
|
if (emote) {
|
||
|
try {
|
||
|
const text = emote.text;
|
||
|
return eat(text)(createEmoteNode(text));
|
||
|
} catch (e) {}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
tokenizeEmote.locator = locateEmote;
|
||
|
|
||
|
export function inlineEmote() {
|
||
|
const Parser = this.Parser;
|
||
|
const tokenizers = Parser.prototype.inlineTokenizers;
|
||
|
const methods = Parser.prototype.inlineMethods;
|
||
|
|
||
|
// Add an inline tokenizer (defined in the following example).
|
||
|
tokenizers.emote = tokenizeEmote;
|
||
|
|
||
|
// Run it just before `text`.
|
||
|
methods.splice(methods.indexOf('text'), 0, 'emote');
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
// Format emote
|
||
|
// ***************************************************************************
|
||
|
|
||
|
const transformer = (node, index, parent) => {
|
||
|
if (node.type === EMOTE_NODE_TYPE && parent && parent.type === 'paragraph') {
|
||
|
const emoteStr = node.value;
|
||
|
const emote = EMOTES.find(({ name }) => emoteStr.toUpperCase() === name);
|
||
|
|
||
|
node.type = 'image';
|
||
|
node.url = emote.url;
|
||
|
node.title = emoteStr;
|
||
|
node.children = [{ type: 'text', value: emoteStr }];
|
||
|
if (!node.data || !node.data.hProperties) {
|
||
|
// Create new node data
|
||
|
node.data = {
|
||
|
hProperties: { emote: true },
|
||
|
};
|
||
|
} else if (node.data.hProperties) {
|
||
|
// Don't overwrite current attributes
|
||
|
node.data.hProperties = {
|
||
|
emote: true,
|
||
|
...node.data.hProperties,
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
const transform = (tree) => visit(tree, [EMOTE_NODE_TYPE], transformer);
|
||
|
|
||
|
export const formattedEmote = () => transform;
|