Merge pull request #178 from davidcilley/less-destructive-add-data-prop

Add parent components and data property for components that reference data.js Issue#86
nolan/hinaloe-test
Etienne Lemay 2018-04-29 22:18:27 -04:00 committed by GitHub
commit 1edf3eaa96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 2608 additions and 2085 deletions

1
.gitignore vendored
View File

@ -3,4 +3,3 @@ dist/
dist-es/
stats.json
report.html
/src/data/data.js

View File

@ -73,32 +73,36 @@ categories: {
#### Sheet sizes
Sheets are served from [unpkg](https://unpkg.com), a global CDN that serves files published to [npm](https://www.npmjs.com).
| Set | sheetSize | Size |
| --------- | --------- | -------- |
| apple | 16 | 334 KB |
| apple | 20 | 459 KB |
| apple | 32 | 1.08 MB |
| apple | 64 | 2.94 MB |
| emojione | 16 | 315 KB |
| emojione | 20 | 435 KB |
| emojione | 32 | 1020 KB |
| emojione | 64 | 2.33 MB |
| facebook | 16 | 322 KB |
| facebook | 20 | 439 KB |
| facebook | 32 | 1020 KB |
| facebook | 64 | 2.5 MB |
| google | 16 | 301 KB |
| google | 20 | 409 KB |
| google | 32 | 907 KB |
| google | 64 | 2.17 MB |
| messenger | 16 | 325 KB |
| messenger | 20 | 449 MB |
| messenger | 32 | 1.05 MB |
| messenger | 64 | 2.69 MB |
| twitter | 16 | 288 KB |
| twitter | 20 | 389 KB |
| twitter | 32 | 839 KB |
| twitter | 64 | 1.82 MB |
| Set | Size (`sheetSize: 16`) | Size (`sheetSize: 20`) | Size (`sheetSize: 32`) | Size (`sheetSize: 64`) |
| --------- | ---------------------- | ---------------------- | ---------------------- | ---------------------- |
| apple | 334 KB | 459 KB | 1.08 MB | 2.94 MB |
| emojione | 315 KB | 435 KB | 1020 KB | 2.33 MB |
| facebook | 322 KB | 439 KB | 1020 KB | 2.50 MB |
| google | 301 KB | 409 KB | 907 KB | 2.17 MB |
| messenger | 325 KB | 449 MB | 1.05 MB | 2.69 MB |
| twitter | 288 KB | 389 KB | 839 KB | 1.82 MB |
#### Datasets
While all sets are available by default, you may want to include only a single set data to reduce the size of your bundle.
| Set | Size |
| --------- | ------ |
| all | 551 KB |
| apple | 465 KB |
| emojione | 226 KB |
| facebook | 405 KB |
| google | 450 KB |
| messenger | 197 KB |
| twitter | 466 KB |
To use these data files (or any other custom data), use the `NimblePicker` component:
```js
import data from 'emoji-mart/data/messenger.json'
import { NimblePicker } from 'emoji-mart'
<NimblePicker set='messenger' data={data} />
```
#### Examples of `emoji` object:
```js
@ -240,6 +244,15 @@ emojiIndex.search('christmas').map((o) => o.native)
// => [🎄, 🎅🏼, 🔔, 🎁, ⛄️, ❄️]
```
### With custom data
```js
import data from 'emoji-mart/datasets/messenger'
import { NimbleEmojiIndex } from 'emoji-mart'
let emojiIndex = new NimbleEmojiIndex(data)
emojiIndex.search('christmas')
```
## Storage
By default EmojiMart will store user chosen skin and frequently used emojis in `localStorage`. That can however be overwritten should you want to store these in your own storage.

1
data/all.json Normal file

File diff suppressed because one or more lines are too long

1
data/apple.json Normal file

File diff suppressed because one or more lines are too long

1
data/emojione.json Normal file

File diff suppressed because one or more lines are too long

1
data/facebook.json Normal file

File diff suppressed because one or more lines are too long

1
data/google.json Normal file

File diff suppressed because one or more lines are too long

1
data/messenger.json Normal file

File diff suppressed because one or more lines are too long

1
data/twitter.json Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -60,13 +60,13 @@
"webpack": "^3.6.0"
},
"scripts": {
"clean": "rm -rf dist/ dist-es/ src/data/data.js",
"clean": "rm -rf dist/ dist-es/",
"build:data": "node scripts/build-data",
"build:dist": "npm run build:cjs && npm run build:es",
"build:cjs": "BABEL_ENV=cjs babel src --out-dir dist --copy-files",
"build:es": "babel src --out-dir dist-es --copy-files",
"build:docs": "cp css/emoji-mart.css docs && webpack --config ./docs/webpack.config.js",
"build": "npm run clean && npm run build:data && npm run build:dist",
"build": "npm run clean && npm run build:dist",
"watch": "BABEL_ENV=cjs babel src --watch --out-dir dist --copy-files",
"start": "npm run watch",
"stats": "webpack --config ./spec/webpack.config.js --json > spec/stats.json",
@ -77,7 +77,7 @@
"prepublishOnly": "npm run build",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook",
"prettier": "prettier --write \"{src,spec}/**/*.js\""
"prettier": "prettier --write \"{src,scripts,spec}/**/*.js\""
},
"size-limit": [
{

View File

@ -1,107 +1,11 @@
var fs = require('fs'),
emojiData = require('emoji-datasource'),
emojiLib = require('emojilib'),
inflection = require('inflection'),
mkdirp = require('mkdirp')
const build = require('./build')
const sets = ['apple', 'emojione', 'facebook', 'google', 'messenger', 'twitter']
var data = { categories: [], emojis: {}, skins: {}, short_names: {} },
categoriesIndex = {}
build({ output: 'data/all.json' })
var categories = [
['Smileys & People', 'people'],
['Animals & Nature', 'nature'],
['Food & Drink', 'foods'],
['Activities', 'activity'],
['Travel & Places', 'places'],
['Objects', 'objects'],
['Symbols', 'symbols'],
['Flags', 'flags'],
]
categories.forEach((category, i) => {
let [name, id] = category
data.categories[i] = { id: id, name: name, emojis: [] }
categoriesIndex[name] = i
})
emojiData.sort((a, b) => {
var aTest = a.sort_order || a.short_name,
bTest = b.sort_order || b.short_name
return aTest - bTest
})
emojiData.forEach((datum) => {
var category = datum.category,
keywords = [],
categoryIndex
if (!datum.category) {
throw new Error('“' + datum.short_name + '” doesnt have a category')
}
datum.name || (datum.name = datum.short_name.replace(/\-/g, ' '))
datum.name = inflection.titleize(datum.name || '')
if (!datum.name) {
throw new Error('“' + datum.short_name + '” doesnt have a name')
}
datum.emoticons = datum.texts || []
datum.text = datum.text || ''
delete datum.texts
if (emojiLib.lib[datum.short_name]) {
datum.keywords = emojiLib.lib[datum.short_name].keywords
}
if (datum.category == 'Skin Tones') {
data.skins[datum.short_name] = datum
} else {
categoryIndex = categoriesIndex[category]
data.categories[categoryIndex].emojis.push(datum.short_name)
data.emojis[datum.short_name] = datum
}
datum.short_names.forEach((short_name, i) => {
if (i == 0) { return }
data.short_names[short_name] = datum.short_name
sets.forEach((set) => {
build({
output: `data/${set}.json`,
sets: [set],
})
datum.short_names = datum.short_names.filter(i => i !== datum.short_name)
datum.sheet = [datum.sheet_x, datum.sheet_y]
if (datum.text === '') delete datum.text
if (datum.added_in === '6.0') delete datum.added_in
delete datum.docomo
delete datum.au
delete datum.softbank
delete datum.google
delete datum.image
delete datum.short_name
delete datum.category
delete datum.sort_order
delete datum.sheet_x
delete datum.sheet_y
for (let key in datum) {
let value = datum[key]
if (Array.isArray(value) && !value.length) {
delete datum[key]
}
}
})
var flags = data.categories[categoriesIndex['Flags']]
flags.emojis = flags.emojis.filter((flag) => {
// Until browsers support Flag UN
if (flag == 'flag-un') return
return true
}).sort()
const stringified = JSON.stringify(data).replace(/\"([A-Za-z_]+)\":/g, '$1:')
fs.writeFile('src/data/data.js', `export default ${stringified}`, (err) => {
if (err) throw err
})

124
scripts/build.js Normal file
View File

@ -0,0 +1,124 @@
var fs = require('fs'),
emojiLib = require('emojilib'),
inflection = require('inflection'),
mkdirp = require('mkdirp')
var { compress } = require('../src/utils/data')
var categories = [
['Smileys & People', 'people'],
['Animals & Nature', 'nature'],
['Food & Drink', 'foods'],
['Activities', 'activity'],
['Travel & Places', 'places'],
['Objects', 'objects'],
['Symbols', 'symbols'],
['Flags', 'flags'],
]
var sets = ['apple', 'emojione', 'facebook', 'google', 'messenger', 'twitter']
module.exports = (options) => {
delete require.cache[require.resolve('emoji-datasource')]
var emojiData = require('emoji-datasource')
var data = { compressed: true, categories: [], emojis: {}, aliases: {} },
categoriesIndex = {}
categories.forEach((category, i) => {
let [name, id] = category
data.categories[i] = { id: id, name: name, emojis: [] }
categoriesIndex[name] = i
})
emojiData.sort((a, b) => {
var aTest = a.sort_order || a.short_name,
bTest = b.sort_order || b.short_name
return aTest - bTest
})
emojiData.forEach((datum) => {
var category = datum.category,
keywords = [],
categoryIndex
if (!datum.category) {
throw new Error('“' + datum.short_name + '” doesnt have a category')
}
if (options.sets) {
var keepEmoji = false
options.sets.forEach((set) => {
if (keepEmoji) return
if (datum[`has_img_${set}`]) {
keepEmoji = true
}
})
if (!keepEmoji) {
return
}
sets.forEach((set) => {
if (options.sets.length == 1 || options.sets.indexOf(set) == -1) {
var key = `has_img_${set}`
delete datum[key]
}
})
}
datum.name || (datum.name = datum.short_name.replace(/\-/g, ' '))
datum.name = inflection.titleize(datum.name || '')
if (!datum.name) {
throw new Error('“' + datum.short_name + '” doesnt have a name')
}
datum.emoticons = datum.texts || []
datum.text = datum.text || ''
delete datum.texts
if (emojiLib.lib[datum.short_name]) {
datum.keywords = emojiLib.lib[datum.short_name].keywords
}
if (datum.category != 'Skin Tones') {
categoryIndex = categoriesIndex[category]
data.categories[categoryIndex].emojis.push(datum.short_name)
data.emojis[datum.short_name] = datum
}
datum.short_names.forEach((short_name, i) => {
if (i == 0) {
return
}
data.aliases[short_name] = datum.short_name
})
delete datum.docomo
delete datum.au
delete datum.softbank
delete datum.google
delete datum.image
delete datum.category
delete datum.sort_order
compress(datum)
})
var flags = data.categories[categoriesIndex['Flags']]
flags.emojis = flags.emojis
.filter((flag) => {
// Until browsers support Flag UN
if (flag == 'flag-un') return
return true
})
.sort()
fs.writeFile(options.output, JSON.stringify(data), (err) => {
if (err) throw err
})
}

View File

@ -2,5 +2,5 @@ var pack = require('../package.json')
module.exports = {
'process.env.NODE_ENV': 'production',
EMOJI_DATASOURCE_VERSION: pack.devDependencies['emoji-datasource']
EMOJI_DATASOURCE_VERSION: pack.devDependencies['emoji-datasource'],
}

View File

@ -1,4 +1,4 @@
import emojiIndex from '../src/utils/emoji-index'
import emojiIndex from '../src/utils/emoji-index/emoji-index'
describe('#emojiIndex', () => {
describe('search', function() {

View File

@ -1,6 +1,8 @@
import React from 'react'
import TestUtils from 'react-dom/test-utils'
import Picker from '../src/components/picker'
import data from '../data/all.json'
import { NimblePicker } from '../src/components'
const { click } = TestUtils.Simulate
@ -11,11 +13,11 @@ const {
} = TestUtils
const render = (props = {}) => {
const defaultProps = {}
return renderIntoDocument(<Picker {...defaultProps} {...props} />)
const defaultProps = { data }
return renderIntoDocument(<NimblePicker {...defaultProps} {...props} />)
}
describe('Picker', () => {
describe('NimblePicker', () => {
let subject
it('works', () => {

View File

@ -3,12 +3,13 @@ import PropTypes from 'prop-types'
import frequently from '../utils/frequently'
import { getData } from '../utils'
import { Emoji } from '.'
import { NimbleEmoji } from '.'
export default class Category extends React.Component {
constructor(props) {
super(props)
this.data = props.data
this.setContainerRef = this.setContainerRef.bind(this)
this.setLabelRef = this.setLabelRef.bind(this)
}
@ -109,7 +110,7 @@ export default class Category extends React.Component {
return id
})
.filter((id) => !!getData(id))
.filter((id) => !!getData(id, null, null, this.data))
}
if (emojis.length === 0 && frequentlyUsed.length > 0) {
@ -184,13 +185,16 @@ export default class Category extends React.Component {
</div>
{emojis &&
emojis.map((emoji) => Emoji({ emoji: emoji, ...emojiProps }))}
emojis.map((emoji) =>
NimbleEmoji({ emoji: emoji, data: this.data, ...emojiProps }),
)}
{emojis &&
!emojis.length && (
<div>
<div>
{Emoji({
{NimbleEmoji({
data: this.data,
...emojiProps,
size: 38,
emoji: 'sleuth_or_spy',

View File

@ -0,0 +1,21 @@
import React from 'react'
import data from '../../../data/all.json'
import NimbleEmoji from './nimble-emoji'
import { EmojiPropTypes, EmojiDefaultProps } from '../../utils/shared-props'
const Emoji = (props) => {
for (let k in Emoji.defaultProps) {
if (props[k] == undefined && Emoji.defaultProps[k] != undefined) {
props[k] = Emoji.defaultProps[k]
}
}
return NimbleEmoji({ ...props })
}
Emoji.propTypes = EmojiPropTypes
Emoji.defaultProps = { ...EmojiDefaultProps, data }
export default Emoji

View File

@ -1,11 +1,17 @@
import React from 'react'
import PropTypes from 'prop-types'
import data from '../data'
import { getData, getSanitizedData, unifiedToNative } from '../utils'
import { getData, getSanitizedData, unifiedToNative } from '../../utils'
import { uncompress } from '../../utils/data'
import { EmojiPropTypes, EmojiDefaultProps } from '../../utils/shared-props'
const SHEET_COLUMNS = 52
const _getData = (props) => {
var { emoji, skin, set, data } = props
return getData(emoji, skin, set, data)
}
const _getPosition = (props) => {
var { sheet_x, sheet_y } = _getData(props),
multiply = 100 / (SHEET_COLUMNS - 1)
@ -13,14 +19,9 @@ const _getPosition = (props) => {
return `${multiply * sheet_x}% ${multiply * sheet_y}%`
}
const _getData = (props) => {
var { emoji, skin, set } = props
return getData(emoji, skin, set)
}
const _getSanitizedData = (props) => {
var { emoji, skin, set } = props
return getSanitizedData(emoji, skin, set)
var { emoji, skin, set, data } = props
return getSanitizedData(emoji, skin, set, data)
}
const _handleClick = (e, props) => {
@ -73,10 +74,14 @@ const _convertStyleToCSS = (style) => {
return div.getAttribute('style')
}
const Emoji = (props) => {
for (let k in Emoji.defaultProps) {
if (props[k] == undefined && Emoji.defaultProps[k] != undefined) {
props[k] = Emoji.defaultProps[k]
const NimbleEmoji = (props) => {
if (props.data.compressed) {
uncompress(props.data)
}
for (let k in NimbleEmoji.defaultProps) {
if (props[k] == undefined && NimbleEmoji.defaultProps[k] != undefined) {
props[k] = NimbleEmoji.defaultProps[k]
}
}
@ -119,7 +124,8 @@ const Emoji = (props) => {
backgroundSize: 'contain',
}
} else {
let setHasEmoji = _getData(props)[`has_img_${props.set}`]
let setHasEmoji =
data[`has_img_${props.set}`] == undefined || data[`has_img_${props.set}`]
if (!setHasEmoji) {
if (props.fallback) {
@ -163,41 +169,7 @@ const Emoji = (props) => {
}
}
Emoji.propTypes = {
onOver: PropTypes.func,
onLeave: PropTypes.func,
onClick: PropTypes.func,
fallback: PropTypes.func,
backgroundImageFn: PropTypes.func,
native: PropTypes.bool,
forceSize: PropTypes.bool,
tooltip: PropTypes.bool,
skin: PropTypes.oneOf([1, 2, 3, 4, 5, 6]),
sheetSize: PropTypes.oneOf([16, 20, 32, 64]),
set: PropTypes.oneOf([
'apple',
'google',
'twitter',
'emojione',
'messenger',
'facebook',
]),
size: PropTypes.number.isRequired,
emoji: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
}
NimbleEmoji.propTypes = { ...EmojiPropTypes, data: PropTypes.object.isRequired }
NimbleEmoji.defaultProps = EmojiDefaultProps
Emoji.defaultProps = {
skin: 1,
set: 'apple',
sheetSize: 64,
native: false,
forceSize: false,
tooltip: false,
backgroundImageFn: (set, sheetSize) =>
`https://unpkg.com/emoji-datasource-${set}@${EMOJI_DATASOURCE_VERSION}/img/${set}/sheets-256/${sheetSize}.png`,
onOver: () => {},
onLeave: () => {},
onClick: () => {},
}
export default Emoji
export default NimbleEmoji

View File

@ -1,7 +1,11 @@
export { default as Anchors } from './anchors'
export { default as Category } from './category'
export { default as Emoji } from './emoji'
export { default as Picker } from './picker'
export { default as Preview } from './preview'
export { default as Search } from './search'
export { default as Skins } from './skins'
export { default as Emoji } from './emoji/emoji'
export { default as NimbleEmoji } from './emoji/nimble-emoji'
export { default as Picker } from './picker/picker'
export { default as NimblePicker } from './picker/nimble-picker'

View File

@ -1,14 +1,15 @@
import '../vendor/raf-polyfill'
import '../../vendor/raf-polyfill'
import React from 'react'
import PropTypes from 'prop-types'
import data from '../data'
import store from '../utils/store'
import frequently from '../utils/frequently'
import { deepMerge, measureScrollbar } from '../utils'
import store from '../../utils/store'
import frequently from '../../utils/frequently'
import { deepMerge, measureScrollbar } from '../../utils'
import { uncompress } from '../../utils/data'
import { PickerPropTypes, PickerDefaultProps } from '../../utils/shared-props'
import { Anchors, Category, Emoji, Preview, Search } from '.'
import { Anchors, Category, Preview, Search } from '..'
const I18N = {
search: 'Search',
@ -28,7 +29,7 @@ const I18N = {
},
}
export default class Picker extends React.PureComponent {
export default class NimblePicker extends React.PureComponent {
constructor(props) {
super(props)
@ -41,6 +42,11 @@ export default class Picker extends React.PureComponent {
anchor: false,
}
if (props.data.compressed) {
uncompress(props.data)
}
this.data = props.data
this.i18n = deepMerge(I18N, props.i18n)
this.state = {
skin: props.skin || store.get('skin') || props.defaultSkin,
@ -48,7 +54,7 @@ export default class Picker extends React.PureComponent {
}
this.categories = []
let allCategories = [].concat(data.categories)
let allCategories = [].concat(this.data.categories)
if (props.custom.length > 0) {
this.CUSTOM_CATEGORY.emojis = props.custom.map((emoji) => {
@ -99,7 +105,7 @@ export default class Picker extends React.PureComponent {
const { emojis } = category
for (let emojiIndex = 0; emojiIndex < emojis.length; emojiIndex++) {
const emoji = emojis[emojiIndex]
if (props.emojisToShowFilter(data.emojis[emoji] || emoji)) {
if (props.emojisToShowFilter(this.data.emojis[emoji] || emoji)) {
newEmojis.push(emoji)
}
}
@ -474,6 +480,7 @@ export default class Picker extends React.PureComponent {
<div className="emoji-mart-bar">
<Anchors
ref={this.setAnchorsRef}
data={this.data}
i18n={this.i18n}
color={color}
categories={this.categories}
@ -484,6 +491,7 @@ export default class Picker extends React.PureComponent {
<Search
ref={this.setSearchRef}
onSearch={this.handleSearch}
data={this.data}
i18n={this.i18n}
emojisToShowFilter={emojisToShowFilter}
include={include}
@ -508,6 +516,7 @@ export default class Picker extends React.PureComponent {
perLine={perLine}
native={native}
hasStickyPosition={this.hasStickyPosition}
data={this.data}
i18n={this.i18n}
recent={
category.id == this.RECENT_CATEGORY.id ? recent : undefined
@ -539,6 +548,7 @@ export default class Picker extends React.PureComponent {
<div className="emoji-mart-bar">
<Preview
ref={this.setPreviewRef}
data={this.data}
title={title}
emoji={emoji}
showSkinTones={showSkinTones}
@ -562,62 +572,8 @@ export default class Picker extends React.PureComponent {
}
}
Picker.propTypes = {
onClick: PropTypes.func,
onSelect: PropTypes.func,
onSkinChange: PropTypes.func,
perLine: PropTypes.number,
emojiSize: PropTypes.number,
i18n: PropTypes.object,
style: PropTypes.object,
title: PropTypes.string,
emoji: PropTypes.string,
color: PropTypes.string,
set: Emoji.propTypes.set,
skin: Emoji.propTypes.skin,
native: PropTypes.bool,
backgroundImageFn: Emoji.propTypes.backgroundImageFn,
sheetSize: Emoji.propTypes.sheetSize,
emojisToShowFilter: PropTypes.func,
showPreview: PropTypes.bool,
showSkinTones: PropTypes.bool,
emojiTooltip: Emoji.propTypes.tooltip,
include: PropTypes.arrayOf(PropTypes.string),
exclude: PropTypes.arrayOf(PropTypes.string),
recent: PropTypes.arrayOf(PropTypes.string),
autoFocus: PropTypes.bool,
custom: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string.isRequired,
short_names: PropTypes.arrayOf(PropTypes.string).isRequired,
emoticons: PropTypes.arrayOf(PropTypes.string),
keywords: PropTypes.arrayOf(PropTypes.string),
imageUrl: PropTypes.string.isRequired,
}),
),
}
Picker.defaultProps = {
onClick: () => {},
onSelect: () => {},
onSkinChange: () => {},
emojiSize: 24,
perLine: 9,
i18n: {},
style: {},
title: 'Emoji Mart™',
emoji: 'department_store',
color: '#ae65c5',
set: Emoji.defaultProps.set,
skin: null,
defaultSkin: Emoji.defaultProps.skin,
native: Emoji.defaultProps.native,
sheetSize: Emoji.defaultProps.sheetSize,
backgroundImageFn: Emoji.defaultProps.backgroundImageFn,
emojisToShowFilter: null,
showPreview: true,
showSkinTones: true,
emojiTooltip: Emoji.defaultProps.tooltip,
autoFocus: false,
custom: [],
NimblePicker.propTypes = {
...PickerPropTypes,
data: PropTypes.object.isRequired,
}
NimblePicker.defaultProps = { ...PickerDefaultProps }

View File

@ -0,0 +1,15 @@
import React from 'react'
import data from '../../../data/all.json'
import NimblePicker from './nimble-picker'
import { PickerPropTypes, PickerDefaultProps } from '../../utils/shared-props'
export default class Picker extends React.PureComponent {
render() {
return <NimblePicker {...this.props} {...this.state} />
}
}
Picker.propTypes = PickerPropTypes
Picker.defaultProps = { ...PickerDefaultProps, data }

View File

@ -1,12 +1,14 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Emoji, Skins } from '.'
import { getData } from '../utils'
import { NimbleEmoji, Skins } from '.'
export default class Preview extends React.PureComponent {
constructor(props) {
super(props)
this.data = props.data
this.state = { emoji: null }
}
@ -21,7 +23,7 @@ export default class Preview extends React.PureComponent {
} = this.props
if (emoji) {
var emojiData = getData(emoji),
var emojiData = getData(emoji, null, null, this.data),
{ emoticons = [] } = emojiData,
knownEmoticons = [],
listedEmoticons = []
@ -38,7 +40,12 @@ export default class Preview extends React.PureComponent {
return (
<div className="emoji-mart-preview">
<div className="emoji-mart-preview-emoji">
{Emoji({ key: emoji.id, emoji: emoji, ...emojiProps })}
{NimbleEmoji({
key: emoji.id,
emoji: emoji,
data: this.data,
...emojiProps,
})}
</div>
<div className="emoji-mart-preview-data">
@ -66,7 +73,7 @@ export default class Preview extends React.PureComponent {
<div className="emoji-mart-preview-emoji">
{idleEmoji &&
idleEmoji.length &&
Emoji({ emoji: idleEmoji, ...emojiProps })}
NimbleEmoji({ emoji: idleEmoji, data: this.data, ...emojiProps })}
</div>
<div className="emoji-mart-preview-data">

View File

@ -1,11 +1,14 @@
import React from 'react'
import PropTypes from 'prop-types'
import emojiIndex from '../utils/emoji-index'
import NimbleEmojiIndex from '../utils/emoji-index/nimble-emoji-index'
export default class Search extends React.PureComponent {
constructor(props) {
super(props)
this.data = props.data
this.emojiIndex = new NimbleEmojiIndex(this.data)
this.setRef = this.setRef.bind(this)
this.handleChange = this.handleChange.bind(this)
}
@ -14,7 +17,7 @@ export default class Search extends React.PureComponent {
var value = this.input.value
this.props.onSearch(
emojiIndex.search(value, {
this.emojiIndex.search(value, {
emojisToShowFilter: this.props.emojisToShowFilter,
maxResults: this.props.maxResults,
include: this.props.include,

View File

@ -1,30 +0,0 @@
import buildSearch from '../utils/build-search'
import data from './data'
function uncompress(list) {
for (var short_name in list) {
var datum = list[short_name]
if (!datum.short_names) datum.short_names = []
datum.short_names.unshift(short_name)
datum.sheet_x = datum.sheet[0]
datum.sheet_y = datum.sheet[1]
delete datum.sheet
if (!datum.text) datum.text = ''
if (datum.added_in !== null && !datum.added_in) datum.added_in = '6.0'
datum.search = buildSearch({
short_names: datum.short_names,
name: datum.name,
keywords: datum.keywords,
emoticons: datum.emoticons,
})
}
}
uncompress(data.emojis)
uncompress(data.skins)
export default data

View File

@ -1,6 +1,14 @@
import emojiIndex from './utils/emoji-index'
import emojiIndex from './utils/emoji-index/emoji-index'
import store from './utils/store'
import frequently from './utils/frequently'
export { Picker, Emoji, Category } from './components'
export {
Picker,
NimblePicker,
Emoji,
NimbleEmoji,
Category,
} from './components'
export { NimbleEmojiIndex } from './utils/emoji-index/nimble-emoji-index'
export { emojiIndex, store, frequently }

View File

@ -1,26 +0,0 @@
export default (data) => {
const search = []
var addToSearch = (strings, split) => {
if (!strings) {
return
}
;(Array.isArray(strings) ? strings : [strings]).forEach((string) => {
;(split ? string.split(/[-|_|\s]+/) : [string]).forEach((s) => {
s = s.toLowerCase()
if (search.indexOf(s) == -1) {
search.push(s)
}
})
})
}
addToSearch(data.short_names, true)
addToSearch(data.name, true)
addToSearch(data.keywords, false)
addToSearch(data.emoticons, false)
return search.join(',')
}

106
src/utils/data.js Normal file
View File

@ -0,0 +1,106 @@
const mapping = {
name: 'a',
unified: 'b',
non_qualified: 'c',
has_img_apple: 'd',
has_img_google: 'e',
has_img_twitter: 'f',
has_img_emojione: 'g',
has_img_facebook: 'h',
has_img_messenger: 'i',
keywords: 'j',
sheet: 'k',
emoticons: 'l',
text: 'm',
short_names: 'n',
added_in: 'o',
}
const buildSearch = (emoji) => {
const search = []
var addToSearch = (strings, split) => {
if (!strings) {
return
}
;(Array.isArray(strings) ? strings : [strings]).forEach((string) => {
;(split ? string.split(/[-|_|\s]+/) : [string]).forEach((s) => {
s = s.toLowerCase()
if (search.indexOf(s) == -1) {
search.push(s)
}
})
})
}
addToSearch(emoji.short_names, true)
addToSearch(emoji.name, true)
addToSearch(emoji.keywords, false)
addToSearch(emoji.emoticons, false)
return search.join(',')
}
const compress = (emoji) => {
emoji.short_names = emoji.short_names.filter((short_name) => {
return short_name !== emoji.short_name
})
delete emoji.short_name
emoji.sheet = [emoji.sheet_x, emoji.sheet_y]
delete emoji.sheet_x
delete emoji.sheet_y
emoji.added_in = parseInt(emoji.added_in)
if (emoji.added_in === 6) {
delete emoji.added_in
}
for (let key in mapping) {
emoji[mapping[key]] = emoji[key]
delete emoji[key]
}
for (let key in emoji) {
let value = emoji[key]
if (Array.isArray(value) && !value.length) {
delete emoji[key]
} else if (typeof value === 'string' && !value.length) {
delete emoji[key]
} else if (value === null) {
delete emoji[key]
}
}
}
const uncompress = (data) => {
data.compressed = false
for (let id in data.emojis) {
let emoji = data.emojis[id]
for (let key in mapping) {
emoji[key] = emoji[mapping[key]]
delete emoji[mapping[key]]
}
if (!emoji.short_names) emoji.short_names = []
emoji.short_names.unshift(id)
emoji.sheet_x = emoji.sheet[0]
emoji.sheet_y = emoji.sheet[1]
delete emoji.sheet
if (!emoji.text) emoji.text = ''
if (!emoji.added_in) emoji.added_in = 6
emoji.added_in = emoji.added_in.toFixed(1)
emoji.search = buildSearch(emoji)
}
}
module.exports = { buildSearch, compress, uncompress }

View File

@ -1,180 +0,0 @@
import data from '../data'
import { getData, getSanitizedData, intersect } from '.'
var originalPool = {}
var index = {}
var emojisList = {}
var emoticonsList = {}
var customEmojisList = []
for (let emoji in data.emojis) {
let emojiData = data.emojis[emoji],
{ short_names, emoticons } = emojiData,
id = short_names[0]
if (emoticons) {
emoticons.forEach((emoticon) => {
if (emoticonsList[emoticon]) {
return
}
emoticonsList[emoticon] = id
})
}
emojisList[id] = getSanitizedData(id)
originalPool[id] = emojiData
}
function clearCustomEmojis(pool) {
customEmojisList.forEach((emoji) => {
let emojiId = emoji.id || emoji.short_names[0]
delete pool[emojiId]
delete emojisList[emojiId]
})
}
function addCustomToPool(custom, pool) {
if (customEmojisList.length) clearCustomEmojis(pool)
custom.forEach((emoji) => {
let emojiId = emoji.id || emoji.short_names[0]
if (emojiId && !pool[emojiId]) {
pool[emojiId] = getData(emoji)
emojisList[emojiId] = getSanitizedData(emoji)
}
})
customEmojisList = custom
index = {}
}
function search(
value,
{ emojisToShowFilter, maxResults, include, exclude, custom = [] } = {},
) {
if (customEmojisList != custom) addCustomToPool(custom, originalPool)
maxResults || (maxResults = 75)
include || (include = [])
exclude || (exclude = [])
var results = null,
pool = originalPool
if (value.length) {
if (value == '-' || value == '-1') {
return [emojisList['-1']]
}
var values = value.toLowerCase().split(/[\s|,|\-|_]+/),
allResults = []
if (values.length > 2) {
values = [values[0], values[1]]
}
if (include.length || exclude.length) {
pool = {}
data.categories.forEach((category) => {
let isIncluded =
include && include.length ? include.indexOf(category.id) > -1 : true
let isExcluded =
exclude && exclude.length ? exclude.indexOf(category.id) > -1 : false
if (!isIncluded || isExcluded) {
return
}
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)
}
}
}
allResults = values
.map((value) => {
var aPool = pool,
aIndex = index,
length = 0
for (let charIndex = 0; charIndex < value.length; charIndex++) {
const char = value[charIndex]
length++
aIndex[char] || (aIndex[char] = {})
aIndex = aIndex[char]
if (!aIndex.results) {
let scores = {}
aIndex.results = []
aIndex.pool = {}
for (let id in aPool) {
let emoji = aPool[id],
{ search } = emoji,
sub = value.substr(0, length),
subIndex = search.indexOf(sub)
if (subIndex != -1) {
let score = subIndex + 1
if (sub == id) score = 0
aIndex.results.push(emojisList[id])
aIndex.pool[id] = emoji
scores[id] = score
}
}
aIndex.results.sort((a, b) => {
var aScore = scores[a.id],
bScore = scores[b.id]
return aScore - bScore
})
}
aPool = aIndex.pool
}
return aIndex.results
})
.filter((a) => a)
if (allResults.length > 1) {
results = intersect.apply(null, allResults)
} else if (allResults.length) {
results = allResults[0]
} else {
results = []
}
}
if (results) {
if (emojisToShowFilter) {
results = results.filter((result) => emojisToShowFilter(pool[result.id]))
}
if (results && results.length > maxResults) {
results = results.slice(0, maxResults)
}
}
return results
}
export default { search, emojis: emojisList, emoticons: emoticonsList }

View File

@ -0,0 +1,11 @@
import data from '../../../data/all.json'
import NimbleEmojiIndex from './nimble-emoji-index'
const emojiIndex = new NimbleEmojiIndex(data)
const { emojis, emoticons } = emojiIndex
function search() {
return emojiIndex.search(...arguments)
}
export default { search, emojis, emoticons }

View File

@ -0,0 +1,196 @@
import { getData, getSanitizedData, intersect } from '..'
import { uncompress } from '../data'
export default class NimbleEmojiIndex {
constructor(data) {
if (data.compressed) {
uncompress(data)
}
this.data = data || {}
this.originalPool = {}
this.index = {}
this.emojis = {}
this.emoticons = {}
this.customEmojisList = []
this.buildIndex()
}
buildIndex() {
for (let emoji in this.data.emojis) {
let emojiData = this.data.emojis[emoji],
{ short_names, emoticons } = emojiData,
id = short_names[0]
if (emoticons) {
emoticons.forEach((emoticon) => {
if (this.emoticons[emoticon]) {
return
}
this.emoticons[emoticon] = id
})
}
this.emojis[id] = getSanitizedData(id, null, null, this.data)
this.originalPool[id] = emojiData
}
}
clearCustomEmojis(pool) {
this.customEmojisList.forEach((emoji) => {
let emojiId = emoji.id || emoji.short_names[0]
delete pool[emojiId]
delete emojisList[emojiId]
})
}
addCustomToPool(custom, pool) {
if (this.customEmojisList.length) this.clearCustomEmojis(pool)
custom.forEach((emoji) => {
let emojiId = emoji.id || emoji.short_names[0]
if (emojiId && !pool[emojiId]) {
pool[emojiId] = getData(emoji, null, null, this.data)
this.emojis[emojiId] = getSanitizedData(emoji, null, null, this.data)
}
})
this.customEmojisList = custom
this.index = {}
}
search(
value,
{ emojisToShowFilter, maxResults, include, exclude, custom = [] } = {},
) {
if (this.customEmojisList != custom)
this.addCustomToPool(custom, this.originalPool)
maxResults || (maxResults = 75)
include || (include = [])
exclude || (exclude = [])
var results = null,
pool = this.originalPool
if (value.length) {
if (value == '-' || value == '-1') {
return [this.emojis['-1']]
}
var values = value.toLowerCase().split(/[\s|,|\-|_]+/),
allResults = []
if (values.length > 2) {
values = [values[0], values[1]]
}
if (include.length || exclude.length) {
pool = {}
this.data.categories.forEach((category) => {
let isIncluded =
include && include.length ? include.indexOf(category.id) > -1 : true
let isExcluded =
exclude && exclude.length
? exclude.indexOf(category.id) > -1
: false
if (!isIncluded || isExcluded) {
return
}
category.emojis.forEach(
(emojiId) => (pool[emojiId] = this.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) {
this.addCustomToPool(custom, pool)
}
}
}
allResults = values
.map((value) => {
var aPool = pool,
aIndex = this.index,
length = 0
for (let charIndex = 0; charIndex < value.length; charIndex++) {
const char = value[charIndex]
length++
aIndex[char] || (aIndex[char] = {})
aIndex = aIndex[char]
if (!aIndex.results) {
let scores = {}
aIndex.results = []
aIndex.pool = {}
for (let id in aPool) {
let emoji = aPool[id],
{ search } = emoji,
sub = value.substr(0, length),
subIndex = search.indexOf(sub)
if (subIndex != -1) {
let score = subIndex + 1
if (sub == id) score = 0
aIndex.results.push(this.emojis[id])
aIndex.pool[id] = emoji
scores[id] = score
}
}
aIndex.results.sort((a, b) => {
var aScore = scores[a.id],
bScore = scores[b.id]
return aScore - bScore
})
}
aPool = aIndex.pool
}
return aIndex.results
})
.filter((a) => a)
if (allResults.length > 1) {
results = intersect.apply(null, allResults)
} else if (allResults.length) {
results = allResults[0]
} else {
results = []
}
}
if (results) {
if (emojisToShowFilter) {
results = results.filter((result) =>
emojisToShowFilter(pool[result.id]),
)
}
if (results && results.length > maxResults) {
results = results.slice(0, maxResults)
}
}
return results
}
}

View File

@ -1,5 +1,4 @@
import buildSearch from './build-search'
import data from '../data'
import { buildSearch } from './data'
import stringFromCodePoint from '../polyfills/stringFromCodePoint'
const _JSON = JSON
@ -58,7 +57,7 @@ function getSanitizedData() {
return sanitize(getData(...arguments))
}
function getData(emoji, skin, set) {
function getData(emoji, skin, set, data) {
var emojiData = {}
if (typeof emoji == 'string') {
@ -68,12 +67,12 @@ function getData(emoji, skin, set) {
emoji = matches[1]
if (matches[2]) {
skin = parseInt(matches[2])
skin = parseInt(matches[2], 10)
}
}
if (data.short_names.hasOwnProperty(emoji)) {
emoji = data.short_names[emoji]
if (data.aliases.hasOwnProperty(emoji)) {
emoji = data.aliases[emoji]
}
if (data.emojis.hasOwnProperty(emoji)) {
@ -82,8 +81,8 @@ function getData(emoji, skin, set) {
return null
}
} else if (emoji.id) {
if (data.short_names.hasOwnProperty(emoji.id)) {
emoji.id = data.short_names[emoji.id]
if (data.aliases.hasOwnProperty(emoji.id)) {
emoji.id = data.aliases[emoji.id]
}
if (data.emojis.hasOwnProperty(emoji.id)) {
@ -114,7 +113,10 @@ function getData(emoji, skin, set) {
delete emojiData.variations
}
if (variationData[`has_img_${set}`]) {
if (
variationData[`has_img_${set}`] == undefined ||
variationData[`has_img_${set}`]
) {
emojiData.skin_tone = skin
for (let k in variationData) {

104
src/utils/shared-props.js Normal file
View File

@ -0,0 +1,104 @@
import PropTypes from 'prop-types'
const EmojiPropTypes = {
data: PropTypes.object.isRequired,
onOver: PropTypes.func,
onLeave: PropTypes.func,
onClick: PropTypes.func,
fallback: PropTypes.func,
backgroundImageFn: PropTypes.func,
native: PropTypes.bool,
forceSize: PropTypes.bool,
tooltip: PropTypes.bool,
skin: PropTypes.oneOf([1, 2, 3, 4, 5, 6]),
sheetSize: PropTypes.oneOf([16, 20, 32, 64]),
set: PropTypes.oneOf([
'apple',
'google',
'twitter',
'emojione',
'messenger',
'facebook',
]),
size: PropTypes.number.isRequired,
emoji: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
}
const EmojiDefaultProps = {
skin: 1,
set: 'apple',
sheetSize: 64,
native: false,
forceSize: false,
tooltip: false,
backgroundImageFn: (set, sheetSize) =>
`https://unpkg.com/emoji-datasource-${set}@${EMOJI_DATASOURCE_VERSION}/img/${set}/sheets-256/${sheetSize}.png`,
onOver: () => {},
onLeave: () => {},
onClick: () => {},
}
const PickerPropTypes = {
onClick: PropTypes.func,
onSkinChange: PropTypes.func,
perLine: PropTypes.number,
emojiSize: PropTypes.number,
i18n: PropTypes.object,
style: PropTypes.object,
title: PropTypes.string,
emoji: PropTypes.string,
color: PropTypes.string,
set: EmojiPropTypes.set,
skin: EmojiPropTypes.skin,
native: PropTypes.bool,
backgroundImageFn: EmojiPropTypes.backgroundImageFn,
sheetSize: EmojiPropTypes.sheetSize,
emojisToShowFilter: PropTypes.func,
showPreview: PropTypes.bool,
showSkinTones: PropTypes.bool,
emojiTooltip: EmojiPropTypes.tooltip,
include: PropTypes.arrayOf(PropTypes.string),
exclude: PropTypes.arrayOf(PropTypes.string),
recent: PropTypes.arrayOf(PropTypes.string),
autoFocus: PropTypes.bool,
custom: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string.isRequired,
short_names: PropTypes.arrayOf(PropTypes.string).isRequired,
emoticons: PropTypes.arrayOf(PropTypes.string),
keywords: PropTypes.arrayOf(PropTypes.string),
imageUrl: PropTypes.string.isRequired,
}),
),
}
const PickerDefaultProps = {
onClick: () => {},
onSkinChange: () => {},
emojiSize: 24,
perLine: 9,
i18n: {},
style: {},
title: 'Emoji Mart™',
emoji: 'department_store',
color: '#ae65c5',
set: EmojiDefaultProps.set,
skin: null,
defaultSkin: EmojiDefaultProps.skin,
native: EmojiDefaultProps.native,
sheetSize: EmojiDefaultProps.sheetSize,
backgroundImageFn: EmojiDefaultProps.backgroundImageFn,
emojisToShowFilter: null,
showPreview: true,
showSkinTones: true,
emojiTooltip: EmojiDefaultProps.tooltip,
autoFocus: false,
custom: [],
}
export {
EmojiPropTypes,
EmojiDefaultProps,
PickerPropTypes,
PickerDefaultProps,
}