Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Emote Selector and Emote Comment creation ability
- Loading branch information
saltrafael
committed
Oct 25, 2021
1 parent
cb8e790
commit 9112fea
Showing
6 changed files
with
241 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
// @flow | ||
import React from 'react'; | ||
import emoji from 'emoji-dictionary'; | ||
import Button from 'component/button'; | ||
import OptimizedImage from 'component/optimizedImage'; | ||
import * as EMOTES from 'constants/emotes'; | ||
|
||
const OLD_QUICK_EMOJIS = [ | ||
emoji.getUnicode('rocket'), | ||
emoji.getUnicode('jeans'), | ||
emoji.getUnicode('fire'), | ||
emoji.getUnicode('heart'), | ||
emoji.getUnicode('open_mouth'), | ||
]; | ||
|
||
type Props = { commentValue: string, setCommentValue: (string) => void }; | ||
|
||
export default function EmoteSelector(props: Props) { | ||
const { commentValue, setCommentValue } = props; | ||
|
||
function addEmoteToComment(emote: string) { | ||
setCommentValue( | ||
commentValue + (commentValue && commentValue.charAt(commentValue.length - 1) !== ' ' ? ` ${emote} ` : `${emote} `) | ||
); | ||
} | ||
|
||
return ( | ||
<div className="emote__selector"> | ||
<div className="emotes-list"> | ||
<div className="emotes-list--row"> | ||
<div className="emotes-list--row-title">{__('Old')}</div> | ||
<div className="emotes-list--row-items"> | ||
{OLD_QUICK_EMOJIS.map((emoji) => ( | ||
<Button | ||
key={emoji} | ||
label={emoji} | ||
button="alt" | ||
className="button--file-action" | ||
onClick={() => addEmoteToComment(emoji)} | ||
/> | ||
))} | ||
</div> | ||
</div> | ||
|
||
<div className="emotes-list--row"> | ||
<div className="emotes-list--row-title">{__('Global')}</div> | ||
<div className="emotes-list--row-items"> | ||
{Object.keys(EMOTES).map((emote) => ( | ||
<Button | ||
key={String(emote)} | ||
title={`:${emote.toLowerCase()}:`} | ||
button="alt" | ||
className="button--file-action" | ||
onClick={() => addEmoteToComment(`:${emote.toLowerCase()}:`)} | ||
> | ||
<OptimizedImage src={String(EMOTES[emote])} /> | ||
</Button> | ||
))} | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import * 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 (Object.keys(EMOTES).some((emote) => str.replaceAll(':', '').toUpperCase() === emote)) { | ||
// 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; | ||
|
||
node.type = 'image'; | ||
node.url = EMOTES[emoteStr.replaceAll(':', '').toUpperCase()]; | ||
node.title = emoteStr; | ||
node.children = [{ type: 'text', value: emoteStr }]; | ||
} | ||
}; | ||
|
||
const transform = (tree) => visit(tree, [EMOTE_NODE_TYPE], transformer); | ||
|
||
export const formattedEmote = () => transform; |