use twemoji (#16)

* use twemoji

* insert emoji instead of shortcode

* cleanups

* generate emojiMap using emojibase

* remove redunant spaces

* remove dead codes

* use prebuild and prewatch

* update dependencies
This commit is contained in:
Sajjad Hashemian 2018-09-26 23:36:08 +03:30 committed by Franz Liedke
parent 680c5c3784
commit ab44c2b629
10 changed files with 151 additions and 2097 deletions

View File

@ -19,18 +19,14 @@ return [
(new Extend\Formatter) (new Extend\Formatter)
->configure(function (Configurator $config) { ->configure(function (Configurator $config) {
$config->Emoji->useEmojiOne(); $config->Emoticons->add(':)', '🙂');
$config->Emoji->omitImageSize(); $config->Emoticons->add(':D', '😃');
$config->Emoji->useSVG(); $config->Emoticons->add(':P', '😛');
$config->Emoticons->add(':(', '🙁');
$config->Emoji->addAlias(':)', '🙂'); $config->Emoticons->add(':|', '😐');
$config->Emoji->addAlias(':D', '😃'); $config->Emoticons->add(';)', '😉');
$config->Emoji->addAlias(':P', '😛'); $config->Emoticons->add(':\'(', '😢');
$config->Emoji->addAlias(':(', '🙁'); $config->Emoticons->add(':O', '😮');
$config->Emoji->addAlias(':|', '😐'); $config->Emoticons->add('>:(', '😡');
$config->Emoji->addAlias(';)', '😉');
$config->Emoji->addAlias(':\'(', '😢');
$config->Emoji->addAlias(':O', '😮');
$config->Emoji->addAlias('>:(', '😡');
}) })
]; ];

View File

@ -1726,6 +1726,11 @@
"minimalistic-crypto-utils": "^1.0.0" "minimalistic-crypto-utils": "^1.0.0"
} }
}, },
"emojibase-data": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/emojibase-data/-/emojibase-data-3.1.0.tgz",
"integrity": "sha1-XRKHADz/s7wTMGThto272slA/yA="
},
"emojis-list": { "emojis-list": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
@ -4296,6 +4301,11 @@
"resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
"integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=" "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY="
}, },
"twemoji": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/twemoji/-/twemoji-11.0.1.tgz",
"integrity": "sha512-Z0bRyZ1yO7cqa69oRhLZWmaLM0e9eeTugUXbnNQY3XPgRHJcSF8PKfp/dx3vHWtz9Y6o8fi3Ryjfpq4vBCeXjA=="
},
"typedarray": { "typedarray": {
"version": "0.0.6", "version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",

View File

@ -2,13 +2,18 @@
"name": "@flarum/flarum-ext-emoji", "name": "@flarum/flarum-ext-emoji",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"emojibase-data": "^3.1.0",
"flarum-webpack-config": "0.1.0-beta.9", "flarum-webpack-config": "0.1.0-beta.9",
"textarea-caret": "^3.1.0", "textarea-caret": "^3.1.0",
"twemoji": "^11.0.1",
"webpack": "^4.19.1", "webpack": "^4.19.1",
"webpack-cli": "^3.1.0" "webpack-cli": "^3.1.0"
}, },
"scripts": { "scripts": {
"build": "webpack --mode production", "build": "webpack --mode production",
"watch": "webpack --mode development --watch" "watch": "webpack --mode development --watch",
"prebuild": "npm run generate-emoji-map",
"prewatch": "npm run generate-emoji-map",
"generate-emoji-map": "node ./scripts/generate-emoji-map.js"
} }
} }

View File

@ -0,0 +1,45 @@
const fs = require('fs');
const path = require('path');
const twemoji = require('twemoji/2/twemoji.npm');
const outputPath = './src/forum/generated/emojiMap.json';
const data = require('emojibase-data/en/data.json');
const twemojiFileNames = fs.readdirSync('./node_modules/twemoji/2/svg')
.map(name => path.basename(name, '.svg'));
const alternative = {
"👁️‍🗨️" : "👁‍🗨",
};
const emojis = {};
for (let e of data) {
const emoji = alternative[e.emoji] || e.emoji;
const emojiCode = getEmojiIconCode(emoji);
if (!checkExistanceInTwemoji(emojiCode)) {
console.error('Can not find', emoji, emojiCode);
continue;
}
emojis[emoji] = e.shortcodes;
}
const outputDir = path.dirname(outputPath);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir);
}
fs.writeFileSync(outputPath, JSON.stringify(emojis));
function checkExistanceInTwemoji(code) {
return twemojiFileNames.indexOf(code) != -1;
}
function getEmojiIconCode(emoji) {
const U200D = String.fromCharCode(0x200D);
return twemoji.convert.toCodePoint(emoji.indexOf(U200D) < 0 ?
emoji.replace(/\uFE0F/g, '') :
emoji
);
}

View File

@ -2,7 +2,8 @@ import getCaretCoordinates from 'textarea-caret';
import { extend } from 'flarum/extend'; import { extend } from 'flarum/extend';
import ComposerBody from 'flarum/components/ComposerBody'; import ComposerBody from 'flarum/components/ComposerBody';
import emojiMap from './helpers/emojiMap'; import emojiMap from './generated/emojiMap.json';
import getEmojiIconCode from './helpers/getEmojiIconCode';
import KeyboardNavigatable from 'flarum/utils/KeyboardNavigatable'; import KeyboardNavigatable from 'flarum/utils/KeyboardNavigatable';
import AutocompleteDropdown from './components/AutocompleteDropdown'; import AutocompleteDropdown from './components/AutocompleteDropdown';
@ -75,25 +76,22 @@ export default function addComposerAutocomplete() {
if (emojiStart) { if (emojiStart) {
typed = value.substring(emojiStart, cursor).toLowerCase(); typed = value.substring(emojiStart, cursor).toLowerCase();
const makeSuggestion = function(key) { const makeSuggestion = function({emoji, name, code}) {
const code = ':' + key + ':';
const imageName = emojiMap[key];
return ( return (
<button <button
key={key} key={emoji}
onclick={() => applySuggestion(code)} onclick={() => applySuggestion(emoji)}
onmouseenter={function() { onmouseenter={function() {
dropdown.setIndex($(this).parent().index()); dropdown.setIndex($(this).parent().index());
}}> }}>
<img alt={code} class="emoji" draggable="false" src={'//cdn.jsdelivr.net/emojione/assets/png/' + imageName + '.png'}/> <img alt={emoji} class="emoji" draggable="false" src={'//twemoji.maxcdn.com/2/72x72/' + code + '.png'}/>
{key} {name}
</button> </button>
); );
}; };
const buildSuggestions = () => { const buildSuggestions = () => {
const suggestions = []; const similarEmoji = [];
let similarEmoji = [];
// Build a regular expression to do a fuzzy match of the given input string // Build a regular expression to do a fuzzy match of the given input string
const fuzzyRegexp = function(str) { const fuzzyRegexp = function(str) {
@ -107,9 +105,16 @@ export default function addComposerAutocomplete() {
const findMatchingEmojis = matcher => { const findMatchingEmojis = matcher => {
for (let i = 0; i < emojiKeys.length && maxSuggestions > 0; i++) { for (let i = 0; i < emojiKeys.length && maxSuggestions > 0; i++) {
const curEmoji = emojiKeys[i]; const curEmoji = emojiKeys[i];
if (matcher(curEmoji) && similarEmoji.indexOf(curEmoji) === -1) {
if (similarEmoji.indexOf(curEmoji) === -1) {
const names = emojiMap[curEmoji];
for (let name of names) {
if (matcher(name)) {
--maxSuggestions; --maxSuggestions;
similarEmoji.push(emojiKeys[i]); similarEmoji.push(curEmoji);
break;
}
}
} }
} }
}; };
@ -120,13 +125,13 @@ export default function addComposerAutocomplete() {
// If there are still suggestions left, try for some fuzzy matches // If there are still suggestions left, try for some fuzzy matches
findMatchingEmojis(emoji => regTyped.test(emoji)); findMatchingEmojis(emoji => regTyped.test(emoji));
similarEmoji = similarEmoji.sort((a, b) => { const suggestions = similarEmoji.map(emoji => ({
return a.length - b.length emoji,
}); name: emojiMap[emoji][0],
code: getEmojiIconCode(emoji),
for (let key of similarEmoji) { })).sort((a, b) => {
suggestions.push(makeSuggestion(key)); return a.name.length - b.name.length;
} }).map(makeSuggestion);
if (suggestions.length) { if (suggestions.length) {
dropdown.props.items = suggestions; dropdown.props.items = suggestions;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,26 @@
/*! Copyright Twitter Inc. and other contributors. Licensed under MIT *//*
https://github.com/twitter/twemoji/blob/gh-pages/LICENSE
*/
import twemoji from 'twemoji/2/twemoji.npm';
// avoid using a string literal like '\u200D' here because minifiers expand it inline
const U200D = String.fromCharCode(0x200D);
// avoid runtime RegExp creation for not so smart,
// not JIT based, and old browsers / engines
const UFE0Fg = /\uFE0F/g;
/**
* Used to both remove the possible variant
* and to convert utf16 into code points.
* If there is a zero-width-joiner (U+200D), leave the variants in.
* @param string the raw text of the emoji match
* @return string the code point
*/
export default function getEmojiIconCode(emoji) {
return twemoji.convert.toCodePoint(emoji.indexOf(U200D) < 0 ?
emoji.replace(UFE0Fg, '') :
emoji
);
}

View File

@ -3,9 +3,13 @@ import app from 'flarum/app';
import Post from 'flarum/models/Post'; import Post from 'flarum/models/Post';
import addComposerAutocomplete from './addComposerAutocomplete'; import addComposerAutocomplete from './addComposerAutocomplete';
import renderEmoji from './renderEmoji';
app.initializers.add('flarum-emoji', () => { app.initializers.add('flarum-emoji', () => {
// After typing ':' in the composer, show a dropdown suggesting a bunch of // After typing ':' in the composer, show a dropdown suggesting a bunch of
// emoji that the user could use. // emoji that the user could use.
addComposerAutocomplete(); addComposerAutocomplete();
// render emoji as image in Posts content and title.
renderEmoji();
}); });

View File

@ -0,0 +1,25 @@
/*global s9e*/
import twemoji from 'twemoji/2/twemoji.npm';
import { override } from 'flarum/extend';
import Post from 'flarum/models/Post';
export default function renderEmoji() {
override(Post.prototype, 'contentHtml', function(original) {
const contentHtml = original();
if (this.oldContentHtml !== contentHtml) {
this.emojifiedContentHtml = twemoji.parse(contentHtml);
this.oldContentHtml = contentHtml;
}
return this.emojifiedContentHtml;
});
override(s9e.TextFormatter, 'preview', (original, text, element) => {
original(text, element);
twemoji.parse(element);
});
}