c24153c6ca
* Refactor form-field * Create new Emote Menu * Add Emotes * Add Emote Selector and Emote Comment creation ability * Fix and Split CSS
125 lines
3.5 KiB
JavaScript
125 lines
3.5 KiB
JavaScript
import { EMOTES_24px 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;
|