@@ -1,55 +1,61 @@ | |||
// This code is largely borrowed from: | |||
// https://github.com/missive/emoji-mart/blob/bbd4fbe/src/utils/emoji-index.js | |||
// https://github.com/missive/emoji-mart/blob/5f2ffcc/src/utils/emoji-index.js | |||
import data from './emoji_mart_data_light'; | |||
import { getData, getSanitizedData, intersect } from './emoji_utils'; | |||
let originalPool = {}; | |||
let index = {}; | |||
let emojisList = {}; | |||
let emoticonsList = {}; | |||
let previousInclude = []; | |||
let previousExclude = []; | |||
for (let emoji in data.emojis) { | |||
let emojiData = data.emojis[emoji], | |||
{ short_names, emoticons } = emojiData, | |||
id = short_names[0]; | |||
let emojiData = data.emojis[emoji]; | |||
let { short_names, emoticons } = emojiData; | |||
let id = short_names[0]; | |||
if (emoticons) { | |||
emoticons.forEach(emoticon => { | |||
if (emoticonsList[emoticon]) { | |||
return; | |||
} | |||
for (let emoticon of (emoticons || [])) { | |||
if (!emoticonsList[emoticon]) { | |||
emoticonsList[emoticon] = id; | |||
} | |||
}); | |||
} | |||
emojisList[id] = getSanitizedData(id); | |||
originalPool[id] = emojiData; | |||
} | |||
function addCustomToPool(custom, pool) { | |||
custom.forEach((emoji) => { | |||
let emojiId = emoji.id || emoji.short_names[0]; | |||
if (emojiId && !pool[emojiId]) { | |||
pool[emojiId] = getData(emoji); | |||
emojisList[emojiId] = getSanitizedData(emoji); | |||
} | |||
}); | |||
} | |||
function search(value, { emojisToShowFilter, maxResults, include, exclude, custom = [] } = {}) { | |||
addCustomToPool(custom, originalPool); | |||
maxResults = maxResults || 75; | |||
include = include || []; | |||
exclude = exclude || []; | |||
if (custom.length) { | |||
for (const emoji of custom) { | |||
data.emojis[emoji.id] = getData(emoji); | |||
emojisList[emoji.id] = getSanitizedData(emoji); | |||
} | |||
data.categories.push({ | |||
name: 'Custom', | |||
emojis: custom.map(emoji => emoji.id), | |||
}); | |||
} | |||
let results = null; | |||
let pool = data.emojis; | |||
let results = null, | |||
pool = originalPool; | |||
if (value.length) { | |||
if (value === '-' || value === '-1') { | |||
return [emojisList['-1']]; | |||
} | |||
let values = value.toLowerCase().split(/[\s|,|\-|_]+/); | |||
let values = value.toLowerCase().split(/[\s|,|\-|_]+/), | |||
allResults = []; | |||
if (values.length > 2) { | |||
values = [values[0], values[1]]; | |||
@@ -58,33 +64,32 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo | |||
if (include.length || exclude.length) { | |||
pool = {}; | |||
if (previousInclude !== include.sort().join(',') || previousExclude !== exclude.sort().join(',')) { | |||
previousInclude = include.sort().join(','); | |||
previousExclude = exclude.sort().join(','); | |||
index = {}; | |||
} | |||
for (let category of data.categories) { | |||
data.categories.forEach(category => { | |||
let isIncluded = include && include.length ? include.indexOf(category.name.toLowerCase()) > -1 : true; | |||
let isExcluded = exclude && exclude.length ? exclude.indexOf(category.name.toLowerCase()) > -1 : false; | |||
if (!isIncluded || isExcluded) { | |||
continue; | |||
return; | |||
} | |||
for (let emojiId of category.emojis) { | |||
pool[emojiId] = data.emojis[emojiId]; | |||
category.emojis.forEach(emojiId => pool[emojiId] = data.emojis[emojiId]); | |||
}); | |||
if (custom.length) { | |||
let customIsIncluded = include && include.length ? include.indexOf('custom') > -1 : true; | |||
let customIsExcluded = exclude && exclude.length ? exclude.indexOf('custom') > -1 : false; | |||
if (customIsIncluded && !customIsExcluded) { | |||
addCustomToPool(custom, pool); | |||
} | |||
} | |||
} else if (previousInclude.length || previousExclude.length) { | |||
index = {}; | |||
} | |||
let allResults = values.map((value) => { | |||
let aPool = pool; | |||
let aIndex = index; | |||
let length = 0; | |||
allResults = values.map((value) => { | |||
let aPool = pool, | |||
aIndex = index, | |||
length = 0; | |||
for (let char of value.split('')) { | |||
for (let charIndex = 0; charIndex < value.length; charIndex++) { | |||
const char = value[charIndex]; | |||
length++; | |||
aIndex[char] = aIndex[char] || {}; | |||
@@ -104,9 +109,7 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo | |||
if (subIndex !== -1) { | |||
let score = subIndex + 1; | |||
if (sub === id) { | |||
score = 0; | |||
} | |||
if (sub === id) score = 0; | |||
aIndex.results.push(emojisList[id]); | |||
aIndex.pool[id] = emoji; | |||
@@ -130,7 +133,7 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo | |||
}).filter(a => a); | |||
if (allResults.length > 1) { | |||
results = intersect(...allResults); | |||
results = intersect.apply(null, allResults); | |||
} else if (allResults.length) { | |||
results = allResults[0]; | |||
} else { | |||
@@ -0,0 +1,7 @@ | |||
import Picker from 'emoji-mart/dist-es/components/picker'; | |||
import Emoji from 'emoji-mart/dist-es/components/emoji'; | |||
export { | |||
Picker, | |||
Emoji, | |||
}; |
@@ -1,11 +1,9 @@ | |||
// This code is largely borrowed from: | |||
// https://github.com/missive/emoji-mart/blob/bbd4fbe/src/utils/index.js | |||
// https://github.com/missive/emoji-mart/blob/5f2ffcc/src/utils/index.js | |||
import data from './emoji_mart_data_light'; | |||
const COLONS_REGEX = /^(?:\:([^\:]+)\:)(?:\:skin-tone-(\d)\:)?$/; | |||
function buildSearch(thisData) { | |||
const buildSearch = (data) => { | |||
const search = []; | |||
let addToSearch = (strings, split) => { | |||
@@ -24,19 +22,68 @@ function buildSearch(thisData) { | |||
}); | |||
}; | |||
addToSearch(thisData.short_names, true); | |||
addToSearch(thisData.name, true); | |||
addToSearch(thisData.keywords, false); | |||
addToSearch(thisData.emoticons, false); | |||
addToSearch(data.short_names, true); | |||
addToSearch(data.name, true); | |||
addToSearch(data.keywords, false); | |||
addToSearch(data.emoticons, false); | |||
return search; | |||
} | |||
return search.join(','); | |||
}; | |||
const _String = String; | |||
const stringFromCodePoint = _String.fromCodePoint || function () { | |||
let MAX_SIZE = 0x4000; | |||
let codeUnits = []; | |||
let highSurrogate; | |||
let lowSurrogate; | |||
let index = -1; | |||
let length = arguments.length; | |||
if (!length) { | |||
return ''; | |||
} | |||
let result = ''; | |||
while (++index < length) { | |||
let codePoint = Number(arguments[index]); | |||
if ( | |||
!isFinite(codePoint) || // `NaN`, `+Infinity`, or `-Infinity` | |||
codePoint < 0 || // not a valid Unicode code point | |||
codePoint > 0x10FFFF || // not a valid Unicode code point | |||
Math.floor(codePoint) !== codePoint // not an integer | |||
) { | |||
throw RangeError('Invalid code point: ' + codePoint); | |||
} | |||
if (codePoint <= 0xFFFF) { // BMP code point | |||
codeUnits.push(codePoint); | |||
} else { // Astral code point; split in surrogate halves | |||
// http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae | |||
codePoint -= 0x10000; | |||
highSurrogate = (codePoint >> 10) + 0xD800; | |||
lowSurrogate = (codePoint % 0x400) + 0xDC00; | |||
codeUnits.push(highSurrogate, lowSurrogate); | |||
} | |||
if (index + 1 === length || codeUnits.length > MAX_SIZE) { | |||
result += String.fromCharCode.apply(null, codeUnits); | |||
codeUnits.length = 0; | |||
} | |||
} | |||
return result; | |||
}; | |||
const _JSON = JSON; | |||
const COLONS_REGEX = /^(?:\:([^\:]+)\:)(?:\:skin-tone-(\d)\:)?$/; | |||
const SKINS = [ | |||
'1F3FA', '1F3FB', '1F3FC', | |||
'1F3FD', '1F3FE', '1F3FF', | |||
]; | |||
function unifiedToNative(unified) { | |||
let unicodes = unified.split('-'), | |||
codePoints = unicodes.map((u) => `0x${u}`); | |||
return String.fromCodePoint(...codePoints); | |||
return stringFromCodePoint.apply(null, codePoints); | |||
} | |||
function sanitize(emoji) { | |||
@@ -70,11 +117,11 @@ function sanitize(emoji) { | |||
}; | |||
} | |||
function getSanitizedData(emoji) { | |||
return sanitize(getData(emoji)); | |||
function getSanitizedData() { | |||
return sanitize(getData(...arguments)); | |||
} | |||
function getData(emoji) { | |||
function getData(emoji, skin, set) { | |||
let emojiData = {}; | |||
if (typeof emoji === 'string') { | |||
@@ -83,6 +130,9 @@ function getData(emoji) { | |||
if (matches) { | |||
emoji = matches[1]; | |||
if (matches[2]) { | |||
skin = parseInt(matches[2]); | |||
} | |||
} | |||
if (data.short_names.hasOwnProperty(emoji)) { | |||
@@ -92,17 +142,6 @@ function getData(emoji) { | |||
if (data.emojis.hasOwnProperty(emoji)) { | |||
emojiData = data.emojis[emoji]; | |||
} | |||
} else if (emoji.custom) { | |||
emojiData = emoji; | |||
emojiData.search = buildSearch({ | |||
short_names: emoji.short_names, | |||
name: emoji.name, | |||
keywords: emoji.keywords, | |||
emoticons: emoji.emoticons, | |||
}); | |||
emojiData.search = emojiData.search.join(','); | |||
} else if (emoji.id) { | |||
if (data.short_names.hasOwnProperty(emoji.id)) { | |||
emoji.id = data.short_names[emoji.id]; | |||
@@ -110,31 +149,110 @@ function getData(emoji) { | |||
if (data.emojis.hasOwnProperty(emoji.id)) { | |||
emojiData = data.emojis[emoji.id]; | |||
skin = skin || emoji.skin; | |||
} | |||
} | |||
if (!Object.keys(emojiData).length) { | |||
emojiData = emoji; | |||
emojiData.custom = true; | |||
if (!emojiData.search) { | |||
emojiData.search = buildSearch(emoji); | |||
} | |||
} | |||
emojiData.emoticons = emojiData.emoticons || []; | |||
emojiData.variations = emojiData.variations || []; | |||
if (emojiData.skin_variations && skin > 1 && set) { | |||
emojiData = JSON.parse(_JSON.stringify(emojiData)); | |||
let skinKey = SKINS[skin - 1], | |||
variationData = emojiData.skin_variations[skinKey]; | |||
if (!variationData.variations && emojiData.variations) { | |||
delete emojiData.variations; | |||
} | |||
if (variationData[`has_img_${set}`]) { | |||
emojiData.skin_tone = skin; | |||
for (let k in variationData) { | |||
let v = variationData[k]; | |||
emojiData[k] = v; | |||
} | |||
} | |||
} | |||
if (emojiData.variations && emojiData.variations.length) { | |||
emojiData = JSON.parse(JSON.stringify(emojiData)); | |||
emojiData = JSON.parse(_JSON.stringify(emojiData)); | |||
emojiData.unified = emojiData.variations.shift(); | |||
} | |||
return emojiData; | |||
} | |||
function uniq(arr) { | |||
return arr.reduce((acc, item) => { | |||
if (acc.indexOf(item) === -1) { | |||
acc.push(item); | |||
} | |||
return acc; | |||
}, []); | |||
} | |||
function intersect(a, b) { | |||
let set; | |||
let list; | |||
if (a.length < b.length) { | |||
set = new Set(a); | |||
list = b; | |||
} else { | |||
set = new Set(b); | |||
list = a; | |||
const uniqA = uniq(a); | |||
const uniqB = uniq(b); | |||
return uniqA.filter(item => uniqB.indexOf(item) >= 0); | |||
} | |||
function deepMerge(a, b) { | |||
let o = {}; | |||
for (let key in a) { | |||
let originalValue = a[key], | |||
value = originalValue; | |||
if (b.hasOwnProperty(key)) { | |||
value = b[key]; | |||
} | |||
if (typeof value === 'object') { | |||
value = deepMerge(originalValue, value); | |||
} | |||
o[key] = value; | |||
} | |||
return Array.from(new Set(list.filter(x => set.has(x)))); | |||
return o; | |||
} | |||
// https://github.com/sonicdoe/measure-scrollbar | |||
function measureScrollbar() { | |||
const div = document.createElement('div'); | |||
div.style.width = '100px'; | |||
div.style.height = '100px'; | |||
div.style.overflow = 'scroll'; | |||
div.style.position = 'absolute'; | |||
div.style.top = '-9999px'; | |||
document.body.appendChild(div); | |||
const scrollbarWidth = div.offsetWidth - div.clientWidth; | |||
document.body.removeChild(div); | |||
return scrollbarWidth; | |||
} | |||
export { getData, getSanitizedData, intersect }; | |||
export { | |||
getData, | |||
getSanitizedData, | |||
uniq, | |||
intersect, | |||
deepMerge, | |||
unifiedToNative, | |||
measureScrollbar, | |||
}; |
@@ -1,5 +1,5 @@ | |||
export function EmojiPicker () { | |||
return import(/* webpackChunkName: "emoji_picker" */'emoji-mart'); | |||
return import(/* webpackChunkName: "emoji_picker" */'../../emoji/emoji_picker'); | |||
} | |||
export function Compose () { | |||
@@ -45,7 +45,7 @@ | |||
"css-loader": "^0.28.4", | |||
"detect-passive-events": "^1.0.2", | |||
"dotenv": "^4.0.0", | |||
"emoji-mart": "^2.0.1", | |||
"emoji-mart": "^2.1.1", | |||
"es6-symbol": "^3.1.1", | |||
"escape-html": "^1.0.3", | |||
"express": "^4.15.2", | |||
@@ -100,7 +100,12 @@ describe('emoji_index', () => { | |||
it('can search for thinking_face', () => { | |||
let expected = [ { id: 'thinking_face', unified: '1f914', native: '🤔' } ]; | |||
expect(search('thinking_fac').map(trimEmojis)).to.deep.equal(expected); | |||
// this is currently broken in emoji-mart | |||
// expect(emojiIndex.search('thinking_fac').map(trimEmojis)).to.deep.equal(expected); | |||
expect(emojiIndex.search('thinking_fac').map(trimEmojis)).to.deep.equal(expected); | |||
}); | |||
it('can search for woman-facepalming', () => { | |||
let expected = [ { id: 'woman-facepalming', unified: '1f926-200d-2640-fe0f', native: '🤦♀️' } ]; | |||
expect(search('woman-facep').map(trimEmojis)).to.deep.equal(expected); | |||
expect(emojiIndex.search('woman-facep').map(trimEmojis)).deep.equal(expected); | |||
}); | |||
}); |
@@ -2191,9 +2191,9 @@ elliptic@^6.0.0: | |||
minimalistic-assert "^1.0.0" | |||
minimalistic-crypto-utils "^1.0.0" | |||
emoji-mart@^2.0.1: | |||
version "2.0.1" | |||
resolved "https://registry.yarnpkg.com/emoji-mart/-/emoji-mart-2.0.1.tgz#b76ea33f2dabc82d8c1d4b6463c8a07fbce23682" | |||
emoji-mart@^2.1.1: | |||
version "2.1.1" | |||
resolved "https://registry.yarnpkg.com/emoji-mart/-/emoji-mart-2.1.1.tgz#4bce8ec9d9fd0d8adfd2517e7e296871c40762ac" | |||
emoji-regex@^6.1.0: | |||
version "6.4.3" | |||