@@ -1,55 +1,61 @@ | |||||
// This code is largely borrowed from: | // 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 data from './emoji_mart_data_light'; | ||||
import { getData, getSanitizedData, intersect } from './emoji_utils'; | import { getData, getSanitizedData, intersect } from './emoji_utils'; | ||||
let originalPool = {}; | |||||
let index = {}; | let index = {}; | ||||
let emojisList = {}; | let emojisList = {}; | ||||
let emoticonsList = {}; | let emoticonsList = {}; | ||||
let previousInclude = []; | |||||
let previousExclude = []; | |||||
for (let emoji in data.emojis) { | 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; | emoticonsList[emoticon] = id; | ||||
} | |||||
}); | |||||
} | } | ||||
emojisList[id] = getSanitizedData(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 = [] } = {}) { | function search(value, { emojisToShowFilter, maxResults, include, exclude, custom = [] } = {}) { | ||||
addCustomToPool(custom, originalPool); | |||||
maxResults = maxResults || 75; | maxResults = maxResults || 75; | ||||
include = include || []; | include = include || []; | ||||
exclude = exclude || []; | 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.length) { | ||||
if (value === '-' || value === '-1') { | if (value === '-' || value === '-1') { | ||||
return [emojisList['-1']]; | return [emojisList['-1']]; | ||||
} | } | ||||
let values = value.toLowerCase().split(/[\s|,|\-|_]+/); | |||||
let values = value.toLowerCase().split(/[\s|,|\-|_]+/), | |||||
allResults = []; | |||||
if (values.length > 2) { | if (values.length > 2) { | ||||
values = [values[0], values[1]]; | values = [values[0], values[1]]; | ||||
@@ -58,33 +64,32 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo | |||||
if (include.length || exclude.length) { | if (include.length || exclude.length) { | ||||
pool = {}; | 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 isIncluded = include && include.length ? include.indexOf(category.name.toLowerCase()) > -1 : true; | ||||
let isExcluded = exclude && exclude.length ? exclude.indexOf(category.name.toLowerCase()) > -1 : false; | let isExcluded = exclude && exclude.length ? exclude.indexOf(category.name.toLowerCase()) > -1 : false; | ||||
if (!isIncluded || isExcluded) { | 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++; | length++; | ||||
aIndex[char] = aIndex[char] || {}; | aIndex[char] = aIndex[char] || {}; | ||||
@@ -104,9 +109,7 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo | |||||
if (subIndex !== -1) { | if (subIndex !== -1) { | ||||
let score = subIndex + 1; | let score = subIndex + 1; | ||||
if (sub === id) { | |||||
score = 0; | |||||
} | |||||
if (sub === id) score = 0; | |||||
aIndex.results.push(emojisList[id]); | aIndex.results.push(emojisList[id]); | ||||
aIndex.pool[id] = emoji; | aIndex.pool[id] = emoji; | ||||
@@ -130,7 +133,7 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo | |||||
}).filter(a => a); | }).filter(a => a); | ||||
if (allResults.length > 1) { | if (allResults.length > 1) { | ||||
results = intersect(...allResults); | |||||
results = intersect.apply(null, allResults); | |||||
} else if (allResults.length) { | } else if (allResults.length) { | ||||
results = allResults[0]; | results = allResults[0]; | ||||
} else { | } 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: | // 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'; | import data from './emoji_mart_data_light'; | ||||
const COLONS_REGEX = /^(?:\:([^\:]+)\:)(?:\:skin-tone-(\d)\:)?$/; | |||||
function buildSearch(thisData) { | |||||
const buildSearch = (data) => { | |||||
const search = []; | const search = []; | ||||
let addToSearch = (strings, split) => { | 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) { | function unifiedToNative(unified) { | ||||
let unicodes = unified.split('-'), | let unicodes = unified.split('-'), | ||||
codePoints = unicodes.map((u) => `0x${u}`); | codePoints = unicodes.map((u) => `0x${u}`); | ||||
return String.fromCodePoint(...codePoints); | |||||
return stringFromCodePoint.apply(null, codePoints); | |||||
} | } | ||||
function sanitize(emoji) { | 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 = {}; | let emojiData = {}; | ||||
if (typeof emoji === 'string') { | if (typeof emoji === 'string') { | ||||
@@ -83,6 +130,9 @@ function getData(emoji) { | |||||
if (matches) { | if (matches) { | ||||
emoji = matches[1]; | emoji = matches[1]; | ||||
if (matches[2]) { | |||||
skin = parseInt(matches[2]); | |||||
} | |||||
} | } | ||||
if (data.short_names.hasOwnProperty(emoji)) { | if (data.short_names.hasOwnProperty(emoji)) { | ||||
@@ -92,17 +142,6 @@ function getData(emoji) { | |||||
if (data.emojis.hasOwnProperty(emoji)) { | if (data.emojis.hasOwnProperty(emoji)) { | ||||
emojiData = data.emojis[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) { | } else if (emoji.id) { | ||||
if (data.short_names.hasOwnProperty(emoji.id)) { | if (data.short_names.hasOwnProperty(emoji.id)) { | ||||
emoji.id = data.short_names[emoji.id]; | emoji.id = data.short_names[emoji.id]; | ||||
@@ -110,31 +149,110 @@ function getData(emoji) { | |||||
if (data.emojis.hasOwnProperty(emoji.id)) { | if (data.emojis.hasOwnProperty(emoji.id)) { | ||||
emojiData = data.emojis[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.emoticons = emojiData.emoticons || []; | ||||
emojiData.variations = emojiData.variations || []; | 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) { | if (emojiData.variations && emojiData.variations.length) { | ||||
emojiData = JSON.parse(JSON.stringify(emojiData)); | |||||
emojiData = JSON.parse(_JSON.stringify(emojiData)); | |||||
emojiData.unified = emojiData.variations.shift(); | emojiData.unified = emojiData.variations.shift(); | ||||
} | } | ||||
return emojiData; | return emojiData; | ||||
} | } | ||||
function uniq(arr) { | |||||
return arr.reduce((acc, item) => { | |||||
if (acc.indexOf(item) === -1) { | |||||
acc.push(item); | |||||
} | |||||
return acc; | |||||
}, []); | |||||
} | |||||
function intersect(a, b) { | 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 () { | export function EmojiPicker () { | ||||
return import(/* webpackChunkName: "emoji_picker" */'emoji-mart'); | |||||
return import(/* webpackChunkName: "emoji_picker" */'../../emoji/emoji_picker'); | |||||
} | } | ||||
export function Compose () { | export function Compose () { | ||||
@@ -45,7 +45,7 @@ | |||||
"css-loader": "^0.28.4", | "css-loader": "^0.28.4", | ||||
"detect-passive-events": "^1.0.2", | "detect-passive-events": "^1.0.2", | ||||
"dotenv": "^4.0.0", | "dotenv": "^4.0.0", | ||||
"emoji-mart": "^2.0.1", | |||||
"emoji-mart": "^2.1.1", | |||||
"es6-symbol": "^3.1.1", | "es6-symbol": "^3.1.1", | ||||
"escape-html": "^1.0.3", | "escape-html": "^1.0.3", | ||||
"express": "^4.15.2", | "express": "^4.15.2", | ||||
@@ -100,7 +100,12 @@ describe('emoji_index', () => { | |||||
it('can search for thinking_face', () => { | it('can search for thinking_face', () => { | ||||
let expected = [ { id: 'thinking_face', unified: '1f914', native: '🤔' } ]; | let expected = [ { id: 'thinking_face', unified: '1f914', native: '🤔' } ]; | ||||
expect(search('thinking_fac').map(trimEmojis)).to.deep.equal(expected); | 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-assert "^1.0.0" | ||||
minimalistic-crypto-utils "^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: | emoji-regex@^6.1.0: | ||||
version "6.4.3" | version "6.4.3" | ||||