summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilipa Lacerda <filipa@gitlab.com>2017-03-08 12:45:42 +0000
committerFilipa Lacerda <filipa@gitlab.com>2017-03-08 12:45:42 +0000
commitbb11a062eb19f08dd692da7e54ca9ae13fb23f36 (patch)
treea3c313258a95270d429c5c8a89b6c760e682aa66
parent4cbc39bfab32f7c9ac038ba9dcecf14efebb2b9d (diff)
parent685e261f0a9f2495b29e552489e174987101f1f9 (diff)
downloadgitlab-ce-bb11a062eb19f08dd692da7e54ca9ae13fb23f36.tar.gz
Merge branch 'master' into 20450-fix-ujs-actions
* master: (184 commits) fixed user_access_request_spec Fixed changelog and a haml condition project.html.haml Fixed some typos inside the _project.html.haml partial Added MR number to changelog Removed the settings gear button inside the Project to a tab Add changelog for filtered-search-visual-tokens Fix edit last visual token Fix filtered search visual token editing dropdown Code improvements Add filtered search visual tokens Fix transient failure in TodoService spec Returns correct header data for commits endpoint Fix pagination headers for repository commits api endpoint Manually set total_count when paginating commits Fix go-get support for projects in nested groups Remove unecessary endpoint from repository, add compatibility endpoints for v3 and several improvements Add configuration instructions for Container Registry Notifications.[ci skip] Update API endpoints for raw files Clear AR connections before starting Sidekiq API V4 is no longer in Beta ...
-rw-r--r--CHANGELOG.md9
-rw-r--r--Gemfile105
-rw-r--r--Gemfile.lock17
-rw-r--r--app/assets/images/emoji.pngbin1087659 -> 1218558 bytes
-rw-r--r--app/assets/images/emoji/100.pngbin0 -> 793 bytes
-rw-r--r--app/assets/images/emoji/1234.pngbin0 -> 676 bytes
-rw-r--r--app/assets/images/emoji/1F627.pngbin0 -> 821 bytes
-rw-r--r--app/assets/images/emoji/8ball.pngbin0 -> 810 bytes
-rw-r--r--app/assets/images/emoji/a.pngbin0 -> 469 bytes
-rw-r--r--app/assets/images/emoji/ab.pngbin0 -> 505 bytes
-rw-r--r--app/assets/images/emoji/abc.pngbin0 -> 646 bytes
-rw-r--r--app/assets/images/emoji/abcd.pngbin0 -> 670 bytes
-rw-r--r--app/assets/images/emoji/accept.pngbin0 -> 491 bytes
-rw-r--r--app/assets/images/emoji/aerial_tramway.pngbin0 -> 759 bytes
-rw-r--r--app/assets/images/emoji/airplane.pngbin0 -> 1152 bytes
-rw-r--r--app/assets/images/emoji/airplane_arriving.pngbin0 -> 1101 bytes
-rw-r--r--app/assets/images/emoji/airplane_departure.pngbin0 -> 1111 bytes
-rw-r--r--app/assets/images/emoji/airplane_small.pngbin0 -> 1229 bytes
-rw-r--r--app/assets/images/emoji/alarm_clock.pngbin0 -> 1044 bytes
-rw-r--r--app/assets/images/emoji/alembic.pngbin0 -> 953 bytes
-rw-r--r--app/assets/images/emoji/alien.pngbin0 -> 839 bytes
-rw-r--r--app/assets/images/emoji/ambulance.pngbin0 -> 1238 bytes
-rw-r--r--app/assets/images/emoji/amphora.pngbin0 -> 1044 bytes
-rw-r--r--app/assets/images/emoji/anchor.pngbin0 -> 779 bytes
-rw-r--r--app/assets/images/emoji/angel.pngbin0 -> 2077 bytes
-rw-r--r--app/assets/images/emoji/angel_tone1.pngbin0 -> 2088 bytes
-rw-r--r--app/assets/images/emoji/angel_tone2.pngbin0 -> 2075 bytes
-rw-r--r--app/assets/images/emoji/angel_tone3.pngbin0 -> 2078 bytes
-rw-r--r--app/assets/images/emoji/angel_tone4.pngbin0 -> 2076 bytes
-rw-r--r--app/assets/images/emoji/angel_tone5.pngbin0 -> 2078 bytes
-rw-r--r--app/assets/images/emoji/anger.pngbin0 -> 594 bytes
-rw-r--r--app/assets/images/emoji/anger_right.pngbin0 -> 551 bytes
-rw-r--r--app/assets/images/emoji/angry.pngbin0 -> 845 bytes
-rw-r--r--app/assets/images/emoji/ant.pngbin0 -> 1412 bytes
-rw-r--r--app/assets/images/emoji/apple.pngbin0 -> 655 bytes
-rw-r--r--app/assets/images/emoji/aquarius.pngbin0 -> 648 bytes
-rw-r--r--app/assets/images/emoji/aries.pngbin0 -> 711 bytes
-rw-r--r--app/assets/images/emoji/arrow_backward.pngbin0 -> 429 bytes
-rw-r--r--app/assets/images/emoji/arrow_double_down.pngbin0 -> 543 bytes
-rw-r--r--app/assets/images/emoji/arrow_double_up.pngbin0 -> 535 bytes
-rw-r--r--app/assets/images/emoji/arrow_down.pngbin0 -> 512 bytes
-rw-r--r--app/assets/images/emoji/arrow_down_small.pngbin0 -> 455 bytes
-rw-r--r--app/assets/images/emoji/arrow_forward.pngbin0 -> 429 bytes
-rw-r--r--app/assets/images/emoji/arrow_heading_down.pngbin0 -> 563 bytes
-rw-r--r--app/assets/images/emoji/arrow_heading_up.pngbin0 -> 559 bytes
-rw-r--r--app/assets/images/emoji/arrow_left.pngbin0 -> 471 bytes
-rw-r--r--app/assets/images/emoji/arrow_lower_left.pngbin0 -> 520 bytes
-rw-r--r--app/assets/images/emoji/arrow_lower_right.pngbin0 -> 526 bytes
-rw-r--r--app/assets/images/emoji/arrow_right.pngbin0 -> 468 bytes
-rw-r--r--app/assets/images/emoji/arrow_right_hook.pngbin0 -> 644 bytes
-rw-r--r--app/assets/images/emoji/arrow_up.pngbin0 -> 507 bytes
-rw-r--r--app/assets/images/emoji/arrow_up_down.pngbin0 -> 474 bytes
-rw-r--r--app/assets/images/emoji/arrow_up_small.pngbin0 -> 454 bytes
-rw-r--r--app/assets/images/emoji/arrow_upper_left.pngbin0 -> 521 bytes
-rw-r--r--app/assets/images/emoji/arrow_upper_right.pngbin0 -> 524 bytes
-rw-r--r--app/assets/images/emoji/arrows_clockwise.pngbin0 -> 519 bytes
-rw-r--r--app/assets/images/emoji/arrows_counterclockwise.pngbin0 -> 693 bytes
-rw-r--r--app/assets/images/emoji/art.pngbin0 -> 1455 bytes
-rw-r--r--app/assets/images/emoji/articulated_lorry.pngbin0 -> 1710 bytes
-rw-r--r--app/assets/images/emoji/asterisk.pngbin0 -> 627 bytes
-rw-r--r--app/assets/images/emoji/astonished.pngbin0 -> 862 bytes
-rw-r--r--app/assets/images/emoji/athletic_shoe.pngbin0 -> 1595 bytes
-rw-r--r--app/assets/images/emoji/atm.pngbin0 -> 1397 bytes
-rw-r--r--app/assets/images/emoji/atom.pngbin0 -> 912 bytes
-rw-r--r--app/assets/images/emoji/avocado.pngbin0 -> 1520 bytes
-rw-r--r--app/assets/images/emoji/b.pngbin0 -> 391 bytes
-rw-r--r--app/assets/images/emoji/baby.pngbin0 -> 1380 bytes
-rw-r--r--app/assets/images/emoji/baby_bottle.pngbin0 -> 818 bytes
-rw-r--r--app/assets/images/emoji/baby_chick.pngbin0 -> 1181 bytes
-rw-r--r--app/assets/images/emoji/baby_symbol.pngbin0 -> 665 bytes
-rw-r--r--app/assets/images/emoji/baby_tone1.pngbin0 -> 1392 bytes
-rw-r--r--app/assets/images/emoji/baby_tone2.pngbin0 -> 1392 bytes
-rw-r--r--app/assets/images/emoji/baby_tone3.pngbin0 -> 1403 bytes
-rw-r--r--app/assets/images/emoji/baby_tone4.pngbin0 -> 1413 bytes
-rw-r--r--app/assets/images/emoji/baby_tone5.pngbin0 -> 1405 bytes
-rw-r--r--app/assets/images/emoji/back.pngbin0 -> 562 bytes
-rw-r--r--app/assets/images/emoji/bacon.pngbin0 -> 2148 bytes
-rw-r--r--app/assets/images/emoji/badminton.pngbin0 -> 1253 bytes
-rw-r--r--app/assets/images/emoji/baggage_claim.pngbin0 -> 490 bytes
-rw-r--r--app/assets/images/emoji/balloon.pngbin0 -> 501 bytes
-rw-r--r--app/assets/images/emoji/ballot_box.pngbin0 -> 1355 bytes
-rw-r--r--app/assets/images/emoji/ballot_box_with_check.pngbin0 -> 639 bytes
-rw-r--r--app/assets/images/emoji/bamboo.pngbin0 -> 1946 bytes
-rw-r--r--app/assets/images/emoji/banana.pngbin0 -> 1157 bytes
-rw-r--r--app/assets/images/emoji/bangbang.pngbin0 -> 390 bytes
-rw-r--r--app/assets/images/emoji/bank.pngbin0 -> 1358 bytes
-rw-r--r--app/assets/images/emoji/bar_chart.pngbin0 -> 408 bytes
-rw-r--r--app/assets/images/emoji/barber.pngbin0 -> 820 bytes
-rw-r--r--app/assets/images/emoji/baseball.pngbin0 -> 1185 bytes
-rw-r--r--app/assets/images/emoji/basketball.pngbin0 -> 1546 bytes
-rw-r--r--app/assets/images/emoji/basketball_player.pngbin0 -> 1491 bytes
-rw-r--r--app/assets/images/emoji/basketball_player_tone1.pngbin0 -> 1492 bytes
-rw-r--r--app/assets/images/emoji/basketball_player_tone2.pngbin0 -> 1493 bytes
-rw-r--r--app/assets/images/emoji/basketball_player_tone3.pngbin0 -> 1492 bytes
-rw-r--r--app/assets/images/emoji/basketball_player_tone4.pngbin0 -> 1491 bytes
-rw-r--r--app/assets/images/emoji/basketball_player_tone5.pngbin0 -> 1474 bytes
-rw-r--r--app/assets/images/emoji/bat.pngbin0 -> 1190 bytes
-rw-r--r--app/assets/images/emoji/bath.pngbin0 -> 1238 bytes
-rw-r--r--app/assets/images/emoji/bath_tone1.pngbin0 -> 1235 bytes
-rw-r--r--app/assets/images/emoji/bath_tone2.pngbin0 -> 1231 bytes
-rw-r--r--app/assets/images/emoji/bath_tone3.pngbin0 -> 1236 bytes
-rw-r--r--app/assets/images/emoji/bath_tone4.pngbin0 -> 1252 bytes
-rw-r--r--app/assets/images/emoji/bath_tone5.pngbin0 -> 1239 bytes
-rw-r--r--app/assets/images/emoji/bathtub.pngbin0 -> 767 bytes
-rw-r--r--app/assets/images/emoji/battery.pngbin0 -> 228 bytes
-rw-r--r--app/assets/images/emoji/beach.pngbin0 -> 942 bytes
-rw-r--r--app/assets/images/emoji/beach_umbrella.pngbin0 -> 1486 bytes
-rw-r--r--app/assets/images/emoji/bear.pngbin0 -> 1023 bytes
-rw-r--r--app/assets/images/emoji/bed.pngbin0 -> 1572 bytes
-rw-r--r--app/assets/images/emoji/bee.pngbin0 -> 1378 bytes
-rw-r--r--app/assets/images/emoji/beer.pngbin0 -> 1338 bytes
-rw-r--r--app/assets/images/emoji/beers.pngbin0 -> 2100 bytes
-rw-r--r--app/assets/images/emoji/beetle.pngbin0 -> 1288 bytes
-rw-r--r--app/assets/images/emoji/beginner.pngbin0 -> 545 bytes
-rw-r--r--app/assets/images/emoji/bell.pngbin0 -> 1496 bytes
-rw-r--r--app/assets/images/emoji/bellhop.pngbin0 -> 891 bytes
-rw-r--r--app/assets/images/emoji/bento.pngbin0 -> 1127 bytes
-rw-r--r--app/assets/images/emoji/bicyclist.pngbin0 -> 1911 bytes
-rw-r--r--app/assets/images/emoji/bicyclist_tone1.pngbin0 -> 1860 bytes
-rw-r--r--app/assets/images/emoji/bicyclist_tone2.pngbin0 -> 1866 bytes
-rw-r--r--app/assets/images/emoji/bicyclist_tone3.pngbin0 -> 1851 bytes
-rw-r--r--app/assets/images/emoji/bicyclist_tone4.pngbin0 -> 1852 bytes
-rw-r--r--app/assets/images/emoji/bicyclist_tone5.pngbin0 -> 1840 bytes
-rw-r--r--app/assets/images/emoji/bike.pngbin0 -> 1505 bytes
-rw-r--r--app/assets/images/emoji/bikini.pngbin0 -> 613 bytes
-rw-r--r--app/assets/images/emoji/biohazard.pngbin0 -> 794 bytes
-rw-r--r--app/assets/images/emoji/bird.pngbin0 -> 1068 bytes
-rw-r--r--app/assets/images/emoji/birthday.pngbin0 -> 2219 bytes
-rw-r--r--app/assets/images/emoji/black_circle.pngbin0 -> 374 bytes
-rw-r--r--app/assets/images/emoji/black_heart.pngbin0 -> 435 bytes
-rw-r--r--app/assets/images/emoji/black_joker.pngbin0 -> 1091 bytes
-rw-r--r--app/assets/images/emoji/black_large_square.pngbin0 -> 110 bytes
-rw-r--r--app/assets/images/emoji/black_medium_small_square.pngbin0 -> 110 bytes
-rw-r--r--app/assets/images/emoji/black_medium_square.pngbin0 -> 108 bytes
-rw-r--r--app/assets/images/emoji/black_nib.pngbin0 -> 620 bytes
-rw-r--r--app/assets/images/emoji/black_small_square.pngbin0 -> 108 bytes
-rw-r--r--app/assets/images/emoji/black_square_button.pngbin0 -> 122 bytes
-rw-r--r--app/assets/images/emoji/blossom.pngbin0 -> 867 bytes
-rw-r--r--app/assets/images/emoji/blowfish.pngbin0 -> 1620 bytes
-rw-r--r--app/assets/images/emoji/blue_book.pngbin0 -> 1347 bytes
-rw-r--r--app/assets/images/emoji/blue_car.pngbin0 -> 1275 bytes
-rw-r--r--app/assets/images/emoji/blue_heart.pngbin0 -> 435 bytes
-rw-r--r--app/assets/images/emoji/blush.pngbin0 -> 812 bytes
-rw-r--r--app/assets/images/emoji/boar.pngbin0 -> 1366 bytes
-rw-r--r--app/assets/images/emoji/bomb.pngbin0 -> 702 bytes
-rw-r--r--app/assets/images/emoji/book.pngbin0 -> 1716 bytes
-rw-r--r--app/assets/images/emoji/bookmark.pngbin0 -> 747 bytes
-rw-r--r--app/assets/images/emoji/bookmark_tabs.pngbin0 -> 1395 bytes
-rw-r--r--app/assets/images/emoji/books.pngbin0 -> 2474 bytes
-rw-r--r--app/assets/images/emoji/boom.pngbin0 -> 1110 bytes
-rw-r--r--app/assets/images/emoji/boot.pngbin0 -> 662 bytes
-rw-r--r--app/assets/images/emoji/bouquet.pngbin0 -> 1662 bytes
-rw-r--r--app/assets/images/emoji/bow.pngbin0 -> 1394 bytes
-rw-r--r--app/assets/images/emoji/bow_and_arrow.pngbin0 -> 1402 bytes
-rw-r--r--app/assets/images/emoji/bow_tone1.pngbin0 -> 1394 bytes
-rw-r--r--app/assets/images/emoji/bow_tone2.pngbin0 -> 1394 bytes
-rw-r--r--app/assets/images/emoji/bow_tone3.pngbin0 -> 1394 bytes
-rw-r--r--app/assets/images/emoji/bow_tone4.pngbin0 -> 1394 bytes
-rw-r--r--app/assets/images/emoji/bow_tone5.pngbin0 -> 1394 bytes
-rw-r--r--app/assets/images/emoji/bowling.pngbin0 -> 1426 bytes
-rw-r--r--app/assets/images/emoji/boxing_glove.pngbin0 -> 1575 bytes
-rw-r--r--app/assets/images/emoji/boy.pngbin0 -> 881 bytes
-rw-r--r--app/assets/images/emoji/boy_tone1.pngbin0 -> 876 bytes
-rw-r--r--app/assets/images/emoji/boy_tone2.pngbin0 -> 876 bytes
-rw-r--r--app/assets/images/emoji/boy_tone3.pngbin0 -> 876 bytes
-rw-r--r--app/assets/images/emoji/boy_tone4.pngbin0 -> 870 bytes
-rw-r--r--app/assets/images/emoji/boy_tone5.pngbin0 -> 873 bytes
-rw-r--r--app/assets/images/emoji/bread.pngbin0 -> 1419 bytes
-rw-r--r--app/assets/images/emoji/bride_with_veil.pngbin0 -> 2452 bytes
-rw-r--r--app/assets/images/emoji/bride_with_veil_tone1.pngbin0 -> 2464 bytes
-rw-r--r--app/assets/images/emoji/bride_with_veil_tone2.pngbin0 -> 2457 bytes
-rw-r--r--app/assets/images/emoji/bride_with_veil_tone3.pngbin0 -> 2463 bytes
-rw-r--r--app/assets/images/emoji/bride_with_veil_tone4.pngbin0 -> 2463 bytes
-rw-r--r--app/assets/images/emoji/bride_with_veil_tone5.pngbin0 -> 2462 bytes
-rw-r--r--app/assets/images/emoji/bridge_at_night.pngbin0 -> 637 bytes
-rw-r--r--app/assets/images/emoji/briefcase.pngbin0 -> 1275 bytes
-rw-r--r--app/assets/images/emoji/broken_heart.pngbin0 -> 556 bytes
-rw-r--r--app/assets/images/emoji/bug.pngbin0 -> 1599 bytes
-rw-r--r--app/assets/images/emoji/bulb.pngbin0 -> 805 bytes
-rw-r--r--app/assets/images/emoji/bullettrain_front.pngbin0 -> 1450 bytes
-rw-r--r--app/assets/images/emoji/bullettrain_side.pngbin0 -> 1538 bytes
-rw-r--r--app/assets/images/emoji/burrito.pngbin0 -> 2938 bytes
-rw-r--r--app/assets/images/emoji/bus.pngbin0 -> 1086 bytes
-rw-r--r--app/assets/images/emoji/busstop.pngbin0 -> 626 bytes
-rw-r--r--app/assets/images/emoji/bust_in_silhouette.pngbin0 -> 426 bytes
-rw-r--r--app/assets/images/emoji/busts_in_silhouette.pngbin0 -> 526 bytes
-rw-r--r--app/assets/images/emoji/butterfly.pngbin0 -> 1981 bytes
-rw-r--r--app/assets/images/emoji/cactus.pngbin0 -> 628 bytes
-rw-r--r--app/assets/images/emoji/cake.pngbin0 -> 2266 bytes
-rw-r--r--app/assets/images/emoji/calendar.pngbin0 -> 2077 bytes
-rw-r--r--app/assets/images/emoji/calendar_spiral.pngbin0 -> 1491 bytes
-rw-r--r--app/assets/images/emoji/call_me.pngbin0 -> 894 bytes
-rw-r--r--app/assets/images/emoji/call_me_tone1.pngbin0 -> 893 bytes
-rw-r--r--app/assets/images/emoji/call_me_tone2.pngbin0 -> 891 bytes
-rw-r--r--app/assets/images/emoji/call_me_tone3.pngbin0 -> 891 bytes
-rw-r--r--app/assets/images/emoji/call_me_tone4.pngbin0 -> 891 bytes
-rw-r--r--app/assets/images/emoji/call_me_tone5.pngbin0 -> 893 bytes
-rw-r--r--app/assets/images/emoji/calling.pngbin0 -> 815 bytes
-rw-r--r--app/assets/images/emoji/camel.pngbin0 -> 1190 bytes
-rw-r--r--app/assets/images/emoji/camera.pngbin0 -> 1783 bytes
-rw-r--r--app/assets/images/emoji/camera_with_flash.pngbin0 -> 2097 bytes
-rw-r--r--app/assets/images/emoji/camping.pngbin0 -> 1513 bytes
-rw-r--r--app/assets/images/emoji/cancer.pngbin0 -> 729 bytes
-rw-r--r--app/assets/images/emoji/candle.pngbin0 -> 1250 bytes
-rw-r--r--app/assets/images/emoji/candy.pngbin0 -> 1054 bytes
-rw-r--r--app/assets/images/emoji/canoe.pngbin0 -> 1244 bytes
-rw-r--r--app/assets/images/emoji/capital_abcd.pngbin0 -> 805 bytes
-rw-r--r--app/assets/images/emoji/capricorn.pngbin0 -> 688 bytes
-rw-r--r--app/assets/images/emoji/card_box.pngbin0 -> 1523 bytes
-rw-r--r--app/assets/images/emoji/card_index.pngbin0 -> 1929 bytes
-rw-r--r--app/assets/images/emoji/carousel_horse.pngbin0 -> 1739 bytes
-rw-r--r--app/assets/images/emoji/carrot.pngbin0 -> 1236 bytes
-rw-r--r--app/assets/images/emoji/cartwheel.pngbin0 -> 1233 bytes
-rw-r--r--app/assets/images/emoji/cartwheel_tone1.pngbin0 -> 1234 bytes
-rw-r--r--app/assets/images/emoji/cartwheel_tone2.pngbin0 -> 1235 bytes
-rw-r--r--app/assets/images/emoji/cartwheel_tone3.pngbin0 -> 1229 bytes
-rw-r--r--app/assets/images/emoji/cartwheel_tone4.pngbin0 -> 1227 bytes
-rw-r--r--app/assets/images/emoji/cartwheel_tone5.pngbin0 -> 1214 bytes
-rw-r--r--app/assets/images/emoji/cat.pngbin0 -> 1354 bytes
-rw-r--r--app/assets/images/emoji/cat2.pngbin0 -> 1781 bytes
-rw-r--r--app/assets/images/emoji/cd.pngbin0 -> 908 bytes
-rw-r--r--app/assets/images/emoji/chains.pngbin0 -> 708 bytes
-rw-r--r--app/assets/images/emoji/champagne.pngbin0 -> 1205 bytes
-rw-r--r--app/assets/images/emoji/champagne_glass.pngbin0 -> 1984 bytes
-rw-r--r--app/assets/images/emoji/chart.pngbin0 -> 724 bytes
-rw-r--r--app/assets/images/emoji/chart_with_downwards_trend.pngbin0 -> 709 bytes
-rw-r--r--app/assets/images/emoji/chart_with_upwards_trend.pngbin0 -> 688 bytes
-rw-r--r--app/assets/images/emoji/checkered_flag.pngbin0 -> 787 bytes
-rw-r--r--app/assets/images/emoji/cheese.pngbin0 -> 1697 bytes
-rw-r--r--app/assets/images/emoji/cherries.pngbin0 -> 1211 bytes
-rw-r--r--app/assets/images/emoji/cherry_blossom.pngbin0 -> 1129 bytes
-rw-r--r--app/assets/images/emoji/chestnut.pngbin0 -> 1337 bytes
-rw-r--r--app/assets/images/emoji/chicken.pngbin0 -> 1267 bytes
-rw-r--r--app/assets/images/emoji/children_crossing.pngbin0 -> 778 bytes
-rw-r--r--app/assets/images/emoji/chipmunk.pngbin0 -> 1454 bytes
-rw-r--r--app/assets/images/emoji/chocolate_bar.pngbin0 -> 771 bytes
-rw-r--r--app/assets/images/emoji/christmas_tree.pngbin0 -> 1542 bytes
-rw-r--r--app/assets/images/emoji/church.pngbin0 -> 1298 bytes
-rw-r--r--app/assets/images/emoji/cinema.pngbin0 -> 585 bytes
-rw-r--r--app/assets/images/emoji/circus_tent.pngbin0 -> 1369 bytes
-rw-r--r--app/assets/images/emoji/city_dusk.pngbin0 -> 431 bytes
-rw-r--r--app/assets/images/emoji/city_sunset.pngbin0 -> 997 bytes
-rw-r--r--app/assets/images/emoji/cityscape.pngbin0 -> 599 bytes
-rw-r--r--app/assets/images/emoji/cl.pngbin0 -> 393 bytes
-rw-r--r--app/assets/images/emoji/clap.pngbin0 -> 1456 bytes
-rw-r--r--app/assets/images/emoji/clap_tone1.pngbin0 -> 1458 bytes
-rw-r--r--app/assets/images/emoji/clap_tone2.pngbin0 -> 1458 bytes
-rw-r--r--app/assets/images/emoji/clap_tone3.pngbin0 -> 1458 bytes
-rw-r--r--app/assets/images/emoji/clap_tone4.pngbin0 -> 1458 bytes
-rw-r--r--app/assets/images/emoji/clap_tone5.pngbin0 -> 1444 bytes
-rw-r--r--app/assets/images/emoji/clapper.pngbin0 -> 1535 bytes
-rw-r--r--app/assets/images/emoji/classical_building.pngbin0 -> 1006 bytes
-rw-r--r--app/assets/images/emoji/clipboard.pngbin0 -> 1345 bytes
-rw-r--r--app/assets/images/emoji/clock.pngbin0 -> 592 bytes
-rw-r--r--app/assets/images/emoji/clock1.pngbin0 -> 586 bytes
-rw-r--r--app/assets/images/emoji/clock10.pngbin0 -> 593 bytes
-rw-r--r--app/assets/images/emoji/clock1030.pngbin0 -> 530 bytes
-rw-r--r--app/assets/images/emoji/clock11.pngbin0 -> 590 bytes
-rw-r--r--app/assets/images/emoji/clock1130.pngbin0 -> 583 bytes
-rw-r--r--app/assets/images/emoji/clock12.pngbin0 -> 480 bytes
-rw-r--r--app/assets/images/emoji/clock1230.pngbin0 -> 579 bytes
-rw-r--r--app/assets/images/emoji/clock130.pngbin0 -> 526 bytes
-rw-r--r--app/assets/images/emoji/clock2.pngbin0 -> 591 bytes
-rw-r--r--app/assets/images/emoji/clock230.pngbin0 -> 576 bytes
-rw-r--r--app/assets/images/emoji/clock3.pngbin0 -> 482 bytes
-rw-r--r--app/assets/images/emoji/clock330.pngbin0 -> 568 bytes
-rw-r--r--app/assets/images/emoji/clock4.pngbin0 -> 592 bytes
-rw-r--r--app/assets/images/emoji/clock430.pngbin0 -> 531 bytes
-rw-r--r--app/assets/images/emoji/clock5.pngbin0 -> 585 bytes
-rw-r--r--app/assets/images/emoji/clock530.pngbin0 -> 552 bytes
-rw-r--r--app/assets/images/emoji/clock6.pngbin0 -> 466 bytes
-rw-r--r--app/assets/images/emoji/clock630.pngbin0 -> 536 bytes
-rw-r--r--app/assets/images/emoji/clock7.pngbin0 -> 581 bytes
-rw-r--r--app/assets/images/emoji/clock730.pngbin0 -> 531 bytes
-rw-r--r--app/assets/images/emoji/clock8.pngbin0 -> 590 bytes
-rw-r--r--app/assets/images/emoji/clock830.pngbin0 -> 570 bytes
-rw-r--r--app/assets/images/emoji/clock9.pngbin0 -> 484 bytes
-rw-r--r--app/assets/images/emoji/clock930.pngbin0 -> 576 bytes
-rw-r--r--app/assets/images/emoji/closed_book.pngbin0 -> 1359 bytes
-rw-r--r--app/assets/images/emoji/closed_lock_with_key.pngbin0 -> 1250 bytes
-rw-r--r--app/assets/images/emoji/closed_umbrella.pngbin0 -> 1002 bytes
-rw-r--r--app/assets/images/emoji/cloud.pngbin0 -> 626 bytes
-rw-r--r--app/assets/images/emoji/cloud_lightning.pngbin0 -> 767 bytes
-rw-r--r--app/assets/images/emoji/cloud_rain.pngbin0 -> 876 bytes
-rw-r--r--app/assets/images/emoji/cloud_snow.pngbin0 -> 823 bytes
-rw-r--r--app/assets/images/emoji/cloud_tornado.pngbin0 -> 1519 bytes
-rw-r--r--app/assets/images/emoji/clown.pngbin0 -> 1818 bytes
-rw-r--r--app/assets/images/emoji/clubs.pngbin0 -> 458 bytes
-rw-r--r--app/assets/images/emoji/cocktail.pngbin0 -> 1027 bytes
-rw-r--r--app/assets/images/emoji/coffee.pngbin0 -> 1679 bytes
-rw-r--r--app/assets/images/emoji/coffin.pngbin0 -> 2195 bytes
-rw-r--r--app/assets/images/emoji/cold_sweat.pngbin0 -> 971 bytes
-rw-r--r--app/assets/images/emoji/comet.pngbin0 -> 1819 bytes
-rw-r--r--app/assets/images/emoji/compression.pngbin0 -> 1612 bytes
-rw-r--r--app/assets/images/emoji/computer.pngbin0 -> 369 bytes
-rw-r--r--app/assets/images/emoji/confetti_ball.pngbin0 -> 1703 bytes
-rw-r--r--app/assets/images/emoji/confounded.pngbin0 -> 844 bytes
-rw-r--r--app/assets/images/emoji/confused.pngbin0 -> 647 bytes
-rw-r--r--app/assets/images/emoji/congratulations.pngbin0 -> 729 bytes
-rw-r--r--app/assets/images/emoji/construction.pngbin0 -> 1083 bytes
-rw-r--r--app/assets/images/emoji/construction_site.pngbin0 -> 668 bytes
-rw-r--r--app/assets/images/emoji/construction_worker.pngbin0 -> 1126 bytes
-rw-r--r--app/assets/images/emoji/construction_worker_tone1.pngbin0 -> 1102 bytes
-rw-r--r--app/assets/images/emoji/construction_worker_tone2.pngbin0 -> 1102 bytes
-rw-r--r--app/assets/images/emoji/construction_worker_tone3.pngbin0 -> 1102 bytes
-rw-r--r--app/assets/images/emoji/construction_worker_tone4.pngbin0 -> 1095 bytes
-rw-r--r--app/assets/images/emoji/construction_worker_tone5.pngbin0 -> 1119 bytes
-rw-r--r--app/assets/images/emoji/control_knobs.pngbin0 -> 1104 bytes
-rw-r--r--app/assets/images/emoji/convenience_store.pngbin0 -> 528 bytes
-rw-r--r--app/assets/images/emoji/cookie.pngbin0 -> 1351 bytes
-rw-r--r--app/assets/images/emoji/cooking.pngbin0 -> 764 bytes
-rw-r--r--app/assets/images/emoji/cool.pngbin0 -> 396 bytes
-rw-r--r--app/assets/images/emoji/cop.pngbin0 -> 1440 bytes
-rw-r--r--app/assets/images/emoji/cop_tone1.pngbin0 -> 1421 bytes
-rw-r--r--app/assets/images/emoji/cop_tone2.pngbin0 -> 1424 bytes
-rw-r--r--app/assets/images/emoji/cop_tone3.pngbin0 -> 1419 bytes
-rw-r--r--app/assets/images/emoji/cop_tone4.pngbin0 -> 1417 bytes
-rw-r--r--app/assets/images/emoji/cop_tone5.pngbin0 -> 1433 bytes
-rw-r--r--app/assets/images/emoji/copyright.pngbin0 -> 530 bytes
-rw-r--r--app/assets/images/emoji/corn.pngbin0 -> 1547 bytes
-rw-r--r--app/assets/images/emoji/couch.pngbin0 -> 1362 bytes
-rw-r--r--app/assets/images/emoji/couple.pngbin0 -> 1537 bytes
-rw-r--r--app/assets/images/emoji/couple_mm.pngbin0 -> 1091 bytes
-rw-r--r--app/assets/images/emoji/couple_with_heart.pngbin0 -> 1285 bytes
-rw-r--r--app/assets/images/emoji/couple_ww.pngbin0 -> 1034 bytes
-rw-r--r--app/assets/images/emoji/couplekiss.pngbin0 -> 1380 bytes
-rw-r--r--app/assets/images/emoji/cow.pngbin0 -> 1640 bytes
-rw-r--r--app/assets/images/emoji/cow2.pngbin0 -> 1810 bytes
-rw-r--r--app/assets/images/emoji/cowboy.pngbin0 -> 1353 bytes
-rw-r--r--app/assets/images/emoji/crab.pngbin0 -> 1475 bytes
-rw-r--r--app/assets/images/emoji/crayon.pngbin0 -> 633 bytes
-rw-r--r--app/assets/images/emoji/credit_card.pngbin0 -> 1012 bytes
-rw-r--r--app/assets/images/emoji/crescent_moon.pngbin0 -> 446 bytes
-rw-r--r--app/assets/images/emoji/cricket.pngbin0 -> 1060 bytes
-rw-r--r--app/assets/images/emoji/crocodile.pngbin0 -> 2408 bytes
-rw-r--r--app/assets/images/emoji/croissant.pngbin0 -> 1313 bytes
-rw-r--r--app/assets/images/emoji/cross.pngbin0 -> 408 bytes
-rw-r--r--app/assets/images/emoji/crossed_flags.pngbin0 -> 1239 bytes
-rw-r--r--app/assets/images/emoji/crossed_swords.pngbin0 -> 1591 bytes
-rw-r--r--app/assets/images/emoji/crown.pngbin0 -> 1534 bytes
-rw-r--r--app/assets/images/emoji/cruise_ship.pngbin0 -> 2272 bytes
-rw-r--r--app/assets/images/emoji/cry.pngbin0 -> 1123 bytes
-rw-r--r--app/assets/images/emoji/crying_cat_face.pngbin0 -> 1875 bytes
-rw-r--r--app/assets/images/emoji/crystal_ball.pngbin0 -> 1913 bytes
-rw-r--r--app/assets/images/emoji/cucumber.pngbin0 -> 1357 bytes
-rw-r--r--app/assets/images/emoji/cupid.pngbin0 -> 846 bytes
-rw-r--r--app/assets/images/emoji/curly_loop.pngbin0 -> 545 bytes
-rw-r--r--app/assets/images/emoji/currency_exchange.pngbin0 -> 576 bytes
-rw-r--r--app/assets/images/emoji/curry.pngbin0 -> 1754 bytes
-rw-r--r--app/assets/images/emoji/custard.pngbin0 -> 1273 bytes
-rw-r--r--app/assets/images/emoji/customs.pngbin0 -> 648 bytes
-rw-r--r--app/assets/images/emoji/cyclone.pngbin0 -> 797 bytes
-rw-r--r--app/assets/images/emoji/dagger.pngbin0 -> 916 bytes
-rw-r--r--app/assets/images/emoji/dancer.pngbin0 -> 1405 bytes
-rw-r--r--app/assets/images/emoji/dancer_tone1.pngbin0 -> 1420 bytes
-rw-r--r--app/assets/images/emoji/dancer_tone2.pngbin0 -> 1423 bytes
-rw-r--r--app/assets/images/emoji/dancer_tone3.pngbin0 -> 1429 bytes
-rw-r--r--app/assets/images/emoji/dancer_tone4.pngbin0 -> 1428 bytes
-rw-r--r--app/assets/images/emoji/dancer_tone5.pngbin0 -> 1418 bytes
-rw-r--r--app/assets/images/emoji/dancers.pngbin0 -> 1872 bytes
-rw-r--r--app/assets/images/emoji/dango.pngbin0 -> 802 bytes
-rw-r--r--app/assets/images/emoji/dark_sunglasses.pngbin0 -> 829 bytes
-rw-r--r--app/assets/images/emoji/dart.pngbin0 -> 1374 bytes
-rw-r--r--app/assets/images/emoji/dash.pngbin0 -> 840 bytes
-rw-r--r--app/assets/images/emoji/date.pngbin0 -> 788 bytes
-rw-r--r--app/assets/images/emoji/deciduous_tree.pngbin0 -> 1267 bytes
-rw-r--r--app/assets/images/emoji/deer.pngbin0 -> 1606 bytes
-rw-r--r--app/assets/images/emoji/department_store.pngbin0 -> 673 bytes
-rw-r--r--app/assets/images/emoji/desert.pngbin0 -> 1443 bytes
-rw-r--r--app/assets/images/emoji/desktop.pngbin0 -> 311 bytes
-rw-r--r--app/assets/images/emoji/diamond_shape_with_a_dot_inside.pngbin0 -> 693 bytes
-rw-r--r--app/assets/images/emoji/diamonds.pngbin0 -> 247 bytes
-rw-r--r--app/assets/images/emoji/disappointed.pngbin0 -> 757 bytes
-rw-r--r--app/assets/images/emoji/disappointed_relieved.pngbin0 -> 835 bytes
-rw-r--r--app/assets/images/emoji/dividers.pngbin0 -> 810 bytes
-rw-r--r--app/assets/images/emoji/dizzy.pngbin0 -> 795 bytes
-rw-r--r--app/assets/images/emoji/dizzy_face.pngbin0 -> 710 bytes
-rw-r--r--app/assets/images/emoji/do_not_litter.pngbin0 -> 1010 bytes
-rw-r--r--app/assets/images/emoji/dog.pngbin0 -> 1674 bytes
-rw-r--r--app/assets/images/emoji/dog2.pngbin0 -> 2085 bytes
-rw-r--r--app/assets/images/emoji/dollar.pngbin0 -> 405 bytes
-rw-r--r--app/assets/images/emoji/dolls.pngbin0 -> 2249 bytes
-rw-r--r--app/assets/images/emoji/dolphin.pngbin0 -> 1697 bytes
-rw-r--r--app/assets/images/emoji/door.pngbin0 -> 1105 bytes
-rw-r--r--app/assets/images/emoji/doughnut.pngbin0 -> 1322 bytes
-rw-r--r--app/assets/images/emoji/dove.pngbin0 -> 967 bytes
-rw-r--r--app/assets/images/emoji/dragon.pngbin0 -> 1574 bytes
-rw-r--r--app/assets/images/emoji/dragon_face.pngbin0 -> 1769 bytes
-rw-r--r--app/assets/images/emoji/dress.pngbin0 -> 1001 bytes
-rw-r--r--app/assets/images/emoji/dromedary_camel.pngbin0 -> 1515 bytes
-rw-r--r--app/assets/images/emoji/drooling_face.pngbin0 -> 1049 bytes
-rw-r--r--app/assets/images/emoji/droplet.pngbin0 -> 411 bytes
-rw-r--r--app/assets/images/emoji/drum.pngbin0 -> 1870 bytes
-rw-r--r--app/assets/images/emoji/duck.pngbin0 -> 1729 bytes
-rw-r--r--app/assets/images/emoji/dvd.pngbin0 -> 933 bytes
-rw-r--r--app/assets/images/emoji/e-mail.pngbin0 -> 1196 bytes
-rw-r--r--app/assets/images/emoji/eagle.pngbin0 -> 2222 bytes
-rw-r--r--app/assets/images/emoji/ear.pngbin0 -> 860 bytes
-rw-r--r--app/assets/images/emoji/ear_of_rice.pngbin0 -> 1422 bytes
-rw-r--r--app/assets/images/emoji/ear_tone1.pngbin0 -> 860 bytes
-rw-r--r--app/assets/images/emoji/ear_tone2.pngbin0 -> 860 bytes
-rw-r--r--app/assets/images/emoji/ear_tone3.pngbin0 -> 860 bytes
-rw-r--r--app/assets/images/emoji/ear_tone4.pngbin0 -> 860 bytes
-rw-r--r--app/assets/images/emoji/ear_tone5.pngbin0 -> 860 bytes
-rw-r--r--app/assets/images/emoji/earth_africa.pngbin0 -> 978 bytes
-rw-r--r--app/assets/images/emoji/earth_americas.pngbin0 -> 1031 bytes
-rw-r--r--app/assets/images/emoji/earth_asia.pngbin0 -> 966 bytes
-rw-r--r--app/assets/images/emoji/egg.pngbin0 -> 710 bytes
-rw-r--r--app/assets/images/emoji/eggplant.pngbin0 -> 773 bytes
-rw-r--r--app/assets/images/emoji/eight.pngbin0 -> 608 bytes
-rw-r--r--app/assets/images/emoji/eight_pointed_black_star.pngbin0 -> 493 bytes
-rw-r--r--app/assets/images/emoji/eight_spoked_asterisk.pngbin0 -> 493 bytes
-rw-r--r--app/assets/images/emoji/eject.pngbin0 -> 548 bytes
-rw-r--r--app/assets/images/emoji/electric_plug.pngbin0 -> 548 bytes
-rw-r--r--app/assets/images/emoji/elephant.pngbin0 -> 1293 bytes
-rw-r--r--app/assets/images/emoji/end.pngbin0 -> 393 bytes
-rw-r--r--app/assets/images/emoji/envelope.pngbin0 -> 916 bytes
-rw-r--r--app/assets/images/emoji/envelope_with_arrow.pngbin0 -> 1062 bytes
-rw-r--r--app/assets/images/emoji/euro.pngbin0 -> 460 bytes
-rw-r--r--app/assets/images/emoji/european_castle.pngbin0 -> 965 bytes
-rw-r--r--app/assets/images/emoji/european_post_office.pngbin0 -> 551 bytes
-rw-r--r--app/assets/images/emoji/evergreen_tree.pngbin0 -> 719 bytes
-rw-r--r--app/assets/images/emoji/exclamation.pngbin0 -> 354 bytes
-rw-r--r--app/assets/images/emoji/expressionless.pngbin0 -> 438 bytes
-rw-r--r--app/assets/images/emoji/eye.pngbin0 -> 664 bytes
-rw-r--r--app/assets/images/emoji/eye_in_speech_bubble.pngbin0 -> 698 bytes
-rw-r--r--app/assets/images/emoji/eyeglasses.pngbin0 -> 577 bytes
-rw-r--r--app/assets/images/emoji/eyes.pngbin0 -> 791 bytes
-rw-r--r--app/assets/images/emoji/face_palm.pngbin0 -> 1523 bytes
-rw-r--r--app/assets/images/emoji/face_palm_tone1.pngbin0 -> 1563 bytes
-rw-r--r--app/assets/images/emoji/face_palm_tone2.pngbin0 -> 1547 bytes
-rw-r--r--app/assets/images/emoji/face_palm_tone3.pngbin0 -> 1550 bytes
-rw-r--r--app/assets/images/emoji/face_palm_tone4.pngbin0 -> 1553 bytes
-rw-r--r--app/assets/images/emoji/face_palm_tone5.pngbin0 -> 1532 bytes
-rw-r--r--app/assets/images/emoji/factory.pngbin0 -> 936 bytes
-rw-r--r--app/assets/images/emoji/fallen_leaf.pngbin0 -> 951 bytes
-rw-r--r--app/assets/images/emoji/family.pngbin0 -> 1433 bytes
-rw-r--r--app/assets/images/emoji/family_mmb.pngbin0 -> 1206 bytes
-rw-r--r--app/assets/images/emoji/family_mmbb.pngbin0 -> 1349 bytes
-rw-r--r--app/assets/images/emoji/family_mmg.pngbin0 -> 1361 bytes
-rw-r--r--app/assets/images/emoji/family_mmgb.pngbin0 -> 1626 bytes
-rw-r--r--app/assets/images/emoji/family_mmgg.pngbin0 -> 1448 bytes
-rw-r--r--app/assets/images/emoji/family_mwbb.pngbin0 -> 1638 bytes
-rw-r--r--app/assets/images/emoji/family_mwg.pngbin0 -> 1554 bytes
-rw-r--r--app/assets/images/emoji/family_mwgb.pngbin0 -> 1837 bytes
-rw-r--r--app/assets/images/emoji/family_mwgg.pngbin0 -> 1738 bytes
-rw-r--r--app/assets/images/emoji/family_wwb.pngbin0 -> 1155 bytes
-rw-r--r--app/assets/images/emoji/family_wwbb.pngbin0 -> 1289 bytes
-rw-r--r--app/assets/images/emoji/family_wwg.pngbin0 -> 1286 bytes
-rw-r--r--app/assets/images/emoji/family_wwgb.pngbin0 -> 1550 bytes
-rw-r--r--app/assets/images/emoji/family_wwgg.pngbin0 -> 1374 bytes
-rw-r--r--app/assets/images/emoji/fast_forward.pngbin0 -> 523 bytes
-rw-r--r--app/assets/images/emoji/fax.pngbin0 -> 1188 bytes
-rw-r--r--app/assets/images/emoji/fearful.pngbin0 -> 1002 bytes
-rw-r--r--app/assets/images/emoji/feet.pngbin0 -> 603 bytes
-rw-r--r--app/assets/images/emoji/fencer.pngbin0 -> 1342 bytes
-rw-r--r--app/assets/images/emoji/ferris_wheel.pngbin0 -> 2185 bytes
-rw-r--r--app/assets/images/emoji/ferry.pngbin0 -> 528 bytes
-rw-r--r--app/assets/images/emoji/field_hockey.pngbin0 -> 947 bytes
-rw-r--r--app/assets/images/emoji/file_cabinet.pngbin0 -> 1420 bytes
-rw-r--r--app/assets/images/emoji/file_folder.pngbin0 -> 1445 bytes
-rw-r--r--app/assets/images/emoji/film_frames.pngbin0 -> 560 bytes
-rw-r--r--app/assets/images/emoji/fingers_crossed.pngbin0 -> 1050 bytes
-rw-r--r--app/assets/images/emoji/fingers_crossed_tone1.pngbin0 -> 1047 bytes
-rw-r--r--app/assets/images/emoji/fingers_crossed_tone2.pngbin0 -> 1050 bytes
-rw-r--r--app/assets/images/emoji/fingers_crossed_tone3.pngbin0 -> 1050 bytes
-rw-r--r--app/assets/images/emoji/fingers_crossed_tone4.pngbin0 -> 1046 bytes
-rw-r--r--app/assets/images/emoji/fingers_crossed_tone5.pngbin0 -> 1050 bytes
-rw-r--r--app/assets/images/emoji/fire.pngbin0 -> 1020 bytes
-rw-r--r--app/assets/images/emoji/fire_engine.pngbin0 -> 1656 bytes
-rw-r--r--app/assets/images/emoji/fireworks.pngbin0 -> 1364 bytes
-rw-r--r--app/assets/images/emoji/first_place.pngbin0 -> 1419 bytes
-rw-r--r--app/assets/images/emoji/first_quarter_moon.pngbin0 -> 1152 bytes
-rw-r--r--app/assets/images/emoji/first_quarter_moon_with_face.pngbin0 -> 1068 bytes
-rw-r--r--app/assets/images/emoji/fish.pngbin0 -> 1080 bytes
-rw-r--r--app/assets/images/emoji/fish_cake.pngbin0 -> 1245 bytes
-rw-r--r--app/assets/images/emoji/fishing_pole_and_fish.pngbin0 -> 1442 bytes
-rw-r--r--app/assets/images/emoji/fist.pngbin0 -> 1014 bytes
-rw-r--r--app/assets/images/emoji/fist_tone1.pngbin0 -> 1014 bytes
-rw-r--r--app/assets/images/emoji/fist_tone2.pngbin0 -> 1014 bytes
-rw-r--r--app/assets/images/emoji/fist_tone3.pngbin0 -> 1014 bytes
-rw-r--r--app/assets/images/emoji/fist_tone4.pngbin0 -> 1014 bytes
-rw-r--r--app/assets/images/emoji/fist_tone5.pngbin0 -> 1014 bytes
-rw-r--r--app/assets/images/emoji/five.pngbin0 -> 577 bytes
-rw-r--r--app/assets/images/emoji/flag_ac.pngbin0 -> 1934 bytes
-rw-r--r--app/assets/images/emoji/flag_ad.pngbin0 -> 1285 bytes
-rw-r--r--app/assets/images/emoji/flag_ae.pngbin0 -> 544 bytes
-rw-r--r--app/assets/images/emoji/flag_af.pngbin0 -> 942 bytes
-rw-r--r--app/assets/images/emoji/flag_ag.pngbin0 -> 913 bytes
-rw-r--r--app/assets/images/emoji/flag_ai.pngbin0 -> 1056 bytes
-rw-r--r--app/assets/images/emoji/flag_al.pngbin0 -> 905 bytes
-rw-r--r--app/assets/images/emoji/flag_am.pngbin0 -> 514 bytes
-rw-r--r--app/assets/images/emoji/flag_ao.pngbin0 -> 997 bytes
-rw-r--r--app/assets/images/emoji/flag_aq.pngbin0 -> 657 bytes
-rw-r--r--app/assets/images/emoji/flag_ar.pngbin0 -> 975 bytes
-rw-r--r--app/assets/images/emoji/flag_as.pngbin0 -> 1489 bytes
-rw-r--r--app/assets/images/emoji/flag_at.pngbin0 -> 430 bytes
-rw-r--r--app/assets/images/emoji/flag_au.pngbin0 -> 962 bytes
-rw-r--r--app/assets/images/emoji/flag_aw.pngbin0 -> 709 bytes
-rw-r--r--app/assets/images/emoji/flag_ax.pngbin0 -> 496 bytes
-rw-r--r--app/assets/images/emoji/flag_az.pngbin0 -> 709 bytes
-rw-r--r--app/assets/images/emoji/flag_ba.pngbin0 -> 848 bytes
-rw-r--r--app/assets/images/emoji/flag_bb.pngbin0 -> 789 bytes
-rw-r--r--app/assets/images/emoji/flag_bd.pngbin0 -> 490 bytes
-rw-r--r--app/assets/images/emoji/flag_be.pngbin0 -> 444 bytes
-rw-r--r--app/assets/images/emoji/flag_bf.pngbin0 -> 717 bytes
-rw-r--r--app/assets/images/emoji/flag_bg.pngbin0 -> 513 bytes
-rw-r--r--app/assets/images/emoji/flag_bh.pngbin0 -> 593 bytes
-rw-r--r--app/assets/images/emoji/flag_bi.pngbin0 -> 795 bytes
-rw-r--r--app/assets/images/emoji/flag_bj.pngbin0 -> 554 bytes
-rw-r--r--app/assets/images/emoji/flag_bl.pngbin0 -> 1691 bytes
-rw-r--r--app/assets/images/emoji/flag_black.pngbin0 -> 702 bytes
-rw-r--r--app/assets/images/emoji/flag_bm.pngbin0 -> 1374 bytes
-rw-r--r--app/assets/images/emoji/flag_bn.pngbin0 -> 1355 bytes
-rw-r--r--app/assets/images/emoji/flag_bo.pngbin0 -> 1132 bytes
-rw-r--r--app/assets/images/emoji/flag_bq.pngbin0 -> 1144 bytes
-rw-r--r--app/assets/images/emoji/flag_br.pngbin0 -> 819 bytes
-rw-r--r--app/assets/images/emoji/flag_bs.pngbin0 -> 448 bytes
-rw-r--r--app/assets/images/emoji/flag_bt.pngbin0 -> 1213 bytes
-rw-r--r--app/assets/images/emoji/flag_bv.pngbin0 -> 495 bytes
-rw-r--r--app/assets/images/emoji/flag_bw.pngbin0 -> 391 bytes
-rw-r--r--app/assets/images/emoji/flag_by.pngbin0 -> 1120 bytes
-rw-r--r--app/assets/images/emoji/flag_bz.pngbin0 -> 1595 bytes
-rw-r--r--app/assets/images/emoji/flag_ca.pngbin0 -> 755 bytes
-rw-r--r--app/assets/images/emoji/flag_cc.pngbin0 -> 851 bytes
-rw-r--r--app/assets/images/emoji/flag_cd.pngbin0 -> 707 bytes
-rw-r--r--app/assets/images/emoji/flag_cf.pngbin0 -> 673 bytes
-rw-r--r--app/assets/images/emoji/flag_cg.pngbin0 -> 586 bytes
-rw-r--r--app/assets/images/emoji/flag_ch.pngbin0 -> 390 bytes
-rw-r--r--app/assets/images/emoji/flag_ci.pngbin0 -> 440 bytes
-rw-r--r--app/assets/images/emoji/flag_ck.pngbin0 -> 1083 bytes
-rw-r--r--app/assets/images/emoji/flag_cl.pngbin0 -> 748 bytes
-rw-r--r--app/assets/images/emoji/flag_cm.pngbin0 -> 627 bytes
-rw-r--r--app/assets/images/emoji/flag_cn.pngbin0 -> 676 bytes
-rw-r--r--app/assets/images/emoji/flag_co.pngbin0 -> 524 bytes
-rw-r--r--app/assets/images/emoji/flag_cp.pngbin0 -> 443 bytes
-rw-r--r--app/assets/images/emoji/flag_cr.pngbin0 -> 419 bytes
-rw-r--r--app/assets/images/emoji/flag_cu.pngbin0 -> 586 bytes
-rw-r--r--app/assets/images/emoji/flag_cv.pngbin0 -> 642 bytes
-rw-r--r--app/assets/images/emoji/flag_cw.pngbin0 -> 665 bytes
-rw-r--r--app/assets/images/emoji/flag_cx.pngbin0 -> 1142 bytes
-rw-r--r--app/assets/images/emoji/flag_cy.pngbin0 -> 830 bytes
-rw-r--r--app/assets/images/emoji/flag_cz.pngbin0 -> 600 bytes
-rw-r--r--app/assets/images/emoji/flag_de.pngbin0 -> 502 bytes
-rw-r--r--app/assets/images/emoji/flag_dg.pngbin0 -> 1911 bytes
-rw-r--r--app/assets/images/emoji/flag_dj.pngbin0 -> 753 bytes
-rw-r--r--app/assets/images/emoji/flag_dk.pngbin0 -> 450 bytes
-rw-r--r--app/assets/images/emoji/flag_dm.pngbin0 -> 1075 bytes
-rw-r--r--app/assets/images/emoji/flag_do.pngbin0 -> 1135 bytes
-rw-r--r--app/assets/images/emoji/flag_dz.pngbin0 -> 734 bytes
-rw-r--r--app/assets/images/emoji/flag_ea.pngbin0 -> 1337 bytes
-rw-r--r--app/assets/images/emoji/flag_ec.pngbin0 -> 1431 bytes
-rw-r--r--app/assets/images/emoji/flag_ee.pngbin0 -> 512 bytes
-rw-r--r--app/assets/images/emoji/flag_eg.pngbin0 -> 818 bytes
-rw-r--r--app/assets/images/emoji/flag_eh.pngbin0 -> 742 bytes
-rw-r--r--app/assets/images/emoji/flag_er.pngbin0 -> 1218 bytes
-rw-r--r--app/assets/images/emoji/flag_es.pngbin0 -> 1337 bytes
-rw-r--r--app/assets/images/emoji/flag_et.pngbin0 -> 947 bytes
-rw-r--r--app/assets/images/emoji/flag_eu.pngbin0 -> 760 bytes
-rw-r--r--app/assets/images/emoji/flag_fi.pngbin0 -> 487 bytes
-rw-r--r--app/assets/images/emoji/flag_fj.pngbin0 -> 1381 bytes
-rw-r--r--app/assets/images/emoji/flag_fk.pngbin0 -> 1558 bytes
-rw-r--r--app/assets/images/emoji/flag_fm.pngbin0 -> 554 bytes
-rw-r--r--app/assets/images/emoji/flag_fo.pngbin0 -> 495 bytes
-rw-r--r--app/assets/images/emoji/flag_fr.pngbin0 -> 443 bytes
-rw-r--r--app/assets/images/emoji/flag_ga.pngbin0 -> 512 bytes
-rw-r--r--app/assets/images/emoji/flag_gb.pngbin0 -> 919 bytes
-rw-r--r--app/assets/images/emoji/flag_gd.pngbin0 -> 1017 bytes
-rw-r--r--app/assets/images/emoji/flag_ge.pngbin0 -> 583 bytes
-rw-r--r--app/assets/images/emoji/flag_gf.pngbin0 -> 865 bytes
-rw-r--r--app/assets/images/emoji/flag_gg.pngbin0 -> 521 bytes
-rw-r--r--app/assets/images/emoji/flag_gh.pngbin0 -> 723 bytes
-rw-r--r--app/assets/images/emoji/flag_gi.pngbin0 -> 1053 bytes
-rw-r--r--app/assets/images/emoji/flag_gl.pngbin0 -> 700 bytes
-rw-r--r--app/assets/images/emoji/flag_gm.pngbin0 -> 501 bytes
-rw-r--r--app/assets/images/emoji/flag_gn.pngbin0 -> 434 bytes
-rw-r--r--app/assets/images/emoji/flag_gp.pngbin0 -> 1587 bytes
-rw-r--r--app/assets/images/emoji/flag_gq.pngbin0 -> 1132 bytes
-rw-r--r--app/assets/images/emoji/flag_gr.pngbin0 -> 549 bytes
-rw-r--r--app/assets/images/emoji/flag_gs.pngbin0 -> 2115 bytes
-rw-r--r--app/assets/images/emoji/flag_gt.pngbin0 -> 1087 bytes
-rw-r--r--app/assets/images/emoji/flag_gu.pngbin0 -> 1045 bytes
-rw-r--r--app/assets/images/emoji/flag_gw.pngbin0 -> 705 bytes
-rw-r--r--app/assets/images/emoji/flag_gy.pngbin0 -> 690 bytes
-rw-r--r--app/assets/images/emoji/flag_hk.pngbin0 -> 759 bytes
-rw-r--r--app/assets/images/emoji/flag_hm.pngbin0 -> 1036 bytes
-rw-r--r--app/assets/images/emoji/flag_hn.pngbin0 -> 513 bytes
-rw-r--r--app/assets/images/emoji/flag_hr.pngbin0 -> 1411 bytes
-rw-r--r--app/assets/images/emoji/flag_ht.pngbin0 -> 1205 bytes
-rw-r--r--app/assets/images/emoji/flag_hu.pngbin0 -> 513 bytes
-rw-r--r--app/assets/images/emoji/flag_ic.pngbin0 -> 1330 bytes
-rw-r--r--app/assets/images/emoji/flag_id.pngbin0 -> 498 bytes
-rw-r--r--app/assets/images/emoji/flag_ie.pngbin0 -> 478 bytes
-rw-r--r--app/assets/images/emoji/flag_il.pngbin0 -> 658 bytes
-rw-r--r--app/assets/images/emoji/flag_im.pngbin0 -> 976 bytes
-rw-r--r--app/assets/images/emoji/flag_in.pngbin0 -> 773 bytes
-rw-r--r--app/assets/images/emoji/flag_io.pngbin0 -> 1911 bytes
-rw-r--r--app/assets/images/emoji/flag_iq.pngbin0 -> 811 bytes
-rw-r--r--app/assets/images/emoji/flag_ir.pngbin0 -> 1036 bytes
-rw-r--r--app/assets/images/emoji/flag_is.pngbin0 -> 491 bytes
-rw-r--r--app/assets/images/emoji/flag_it.pngbin0 -> 472 bytes
-rw-r--r--app/assets/images/emoji/flag_je.pngbin0 -> 956 bytes
-rw-r--r--app/assets/images/emoji/flag_jm.pngbin0 -> 837 bytes
-rw-r--r--app/assets/images/emoji/flag_jo.pngbin0 -> 740 bytes
-rw-r--r--app/assets/images/emoji/flag_jp.pngbin0 -> 455 bytes
-rw-r--r--app/assets/images/emoji/flag_ke.pngbin0 -> 1160 bytes
-rw-r--r--app/assets/images/emoji/flag_kg.pngbin0 -> 1080 bytes
-rw-r--r--app/assets/images/emoji/flag_kh.pngbin0 -> 872 bytes
-rw-r--r--app/assets/images/emoji/flag_ki.pngbin0 -> 1369 bytes
-rw-r--r--app/assets/images/emoji/flag_km.pngbin0 -> 783 bytes
-rw-r--r--app/assets/images/emoji/flag_kn.pngbin0 -> 1316 bytes
-rw-r--r--app/assets/images/emoji/flag_kp.pngbin0 -> 696 bytes
-rw-r--r--app/assets/images/emoji/flag_kr.pngbin0 -> 967 bytes
-rw-r--r--app/assets/images/emoji/flag_kw.pngbin0 -> 560 bytes
-rw-r--r--app/assets/images/emoji/flag_ky.pngbin0 -> 1671 bytes
-rw-r--r--app/assets/images/emoji/flag_kz.pngbin0 -> 1136 bytes
-rw-r--r--app/assets/images/emoji/flag_la.pngbin0 -> 479 bytes
-rw-r--r--app/assets/images/emoji/flag_lb.pngbin0 -> 740 bytes
-rw-r--r--app/assets/images/emoji/flag_lc.pngbin0 -> 561 bytes
-rw-r--r--app/assets/images/emoji/flag_li.pngbin0 -> 946 bytes
-rw-r--r--app/assets/images/emoji/flag_lk.pngbin0 -> 974 bytes
-rw-r--r--app/assets/images/emoji/flag_lr.pngbin0 -> 772 bytes
-rw-r--r--app/assets/images/emoji/flag_ls.pngbin0 -> 775 bytes
-rw-r--r--app/assets/images/emoji/flag_lt.pngbin0 -> 510 bytes
-rw-r--r--app/assets/images/emoji/flag_lu.pngbin0 -> 512 bytes
-rw-r--r--app/assets/images/emoji/flag_lv.pngbin0 -> 388 bytes
-rw-r--r--app/assets/images/emoji/flag_ly.pngbin0 -> 685 bytes
-rw-r--r--app/assets/images/emoji/flag_ma.pngbin0 -> 626 bytes
-rw-r--r--app/assets/images/emoji/flag_mc.pngbin0 -> 528 bytes
-rw-r--r--app/assets/images/emoji/flag_md.pngbin0 -> 1170 bytes
-rw-r--r--app/assets/images/emoji/flag_me.pngbin0 -> 1074 bytes
-rw-r--r--app/assets/images/emoji/flag_mf.pngbin0 -> 443 bytes
-rw-r--r--app/assets/images/emoji/flag_mg.pngbin0 -> 556 bytes
-rw-r--r--app/assets/images/emoji/flag_mh.pngbin0 -> 1138 bytes
-rw-r--r--app/assets/images/emoji/flag_mk.pngbin0 -> 1023 bytes
-rw-r--r--app/assets/images/emoji/flag_ml.pngbin0 -> 440 bytes
-rw-r--r--app/assets/images/emoji/flag_mm.pngbin0 -> 937 bytes
-rw-r--r--app/assets/images/emoji/flag_mn.pngbin0 -> 698 bytes
-rw-r--r--app/assets/images/emoji/flag_mo.pngbin0 -> 792 bytes
-rw-r--r--app/assets/images/emoji/flag_mp.pngbin0 -> 1797 bytes
-rw-r--r--app/assets/images/emoji/flag_mq.pngbin0 -> 780 bytes
-rw-r--r--app/assets/images/emoji/flag_mr.pngbin0 -> 657 bytes
-rw-r--r--app/assets/images/emoji/flag_ms.pngbin0 -> 1477 bytes
-rw-r--r--app/assets/images/emoji/flag_mt.pngbin0 -> 799 bytes
-rw-r--r--app/assets/images/emoji/flag_mu.pngbin0 -> 544 bytes
-rw-r--r--app/assets/images/emoji/flag_mv.pngbin0 -> 598 bytes
-rw-r--r--app/assets/images/emoji/flag_mw.pngbin0 -> 825 bytes
-rw-r--r--app/assets/images/emoji/flag_mx.pngbin0 -> 951 bytes
-rw-r--r--app/assets/images/emoji/flag_my.pngbin0 -> 775 bytes
-rw-r--r--app/assets/images/emoji/flag_mz.pngbin0 -> 1159 bytes
-rw-r--r--app/assets/images/emoji/flag_na.pngbin0 -> 1249 bytes
-rw-r--r--app/assets/images/emoji/flag_nc.pngbin0 -> 1148 bytes
-rw-r--r--app/assets/images/emoji/flag_ne.pngbin0 -> 593 bytes
-rw-r--r--app/assets/images/emoji/flag_nf.pngbin0 -> 877 bytes
-rw-r--r--app/assets/images/emoji/flag_ng.pngbin0 -> 438 bytes
-rw-r--r--app/assets/images/emoji/flag_ni.pngbin0 -> 823 bytes
-rw-r--r--app/assets/images/emoji/flag_nl.pngbin0 -> 499 bytes
-rw-r--r--app/assets/images/emoji/flag_no.pngbin0 -> 484 bytes
-rw-r--r--app/assets/images/emoji/flag_np.pngbin0 -> 802 bytes
-rw-r--r--app/assets/images/emoji/flag_nr.pngbin0 -> 529 bytes
-rw-r--r--app/assets/images/emoji/flag_nu.pngbin0 -> 1128 bytes
-rw-r--r--app/assets/images/emoji/flag_nz.pngbin0 -> 1099 bytes
-rw-r--r--app/assets/images/emoji/flag_om.pngbin0 -> 754 bytes
-rw-r--r--app/assets/images/emoji/flag_pa.pngbin0 -> 830 bytes
-rw-r--r--app/assets/images/emoji/flag_pe.pngbin0 -> 439 bytes
-rw-r--r--app/assets/images/emoji/flag_pf.pngbin0 -> 1091 bytes
-rw-r--r--app/assets/images/emoji/flag_pg.pngbin0 -> 1076 bytes
-rw-r--r--app/assets/images/emoji/flag_ph.pngbin0 -> 867 bytes
-rw-r--r--app/assets/images/emoji/flag_pk.pngbin0 -> 753 bytes
-rw-r--r--app/assets/images/emoji/flag_pl.pngbin0 -> 522 bytes
-rw-r--r--app/assets/images/emoji/flag_pm.pngbin0 -> 2314 bytes
-rw-r--r--app/assets/images/emoji/flag_pn.pngbin0 -> 1895 bytes
-rw-r--r--app/assets/images/emoji/flag_pr.pngbin0 -> 605 bytes
-rw-r--r--app/assets/images/emoji/flag_ps.pngbin0 -> 574 bytes
-rw-r--r--app/assets/images/emoji/flag_pt.pngbin0 -> 1055 bytes
-rw-r--r--app/assets/images/emoji/flag_pw.pngbin0 -> 475 bytes
-rw-r--r--app/assets/images/emoji/flag_py.pngbin0 -> 1085 bytes
-rw-r--r--app/assets/images/emoji/flag_qa.pngbin0 -> 657 bytes
-rw-r--r--app/assets/images/emoji/flag_re.pngbin0 -> 837 bytes
-rw-r--r--app/assets/images/emoji/flag_ro.pngbin0 -> 441 bytes
-rw-r--r--app/assets/images/emoji/flag_rs.pngbin0 -> 1237 bytes
-rw-r--r--app/assets/images/emoji/flag_ru.pngbin0 -> 496 bytes
-rw-r--r--app/assets/images/emoji/flag_rw.pngbin0 -> 940 bytes
-rw-r--r--app/assets/images/emoji/flag_sa.pngbin0 -> 781 bytes
-rw-r--r--app/assets/images/emoji/flag_sb.pngbin0 -> 1102 bytes
-rw-r--r--app/assets/images/emoji/flag_sc.pngbin0 -> 1073 bytes
-rw-r--r--app/assets/images/emoji/flag_sd.pngbin0 -> 578 bytes
-rw-r--r--app/assets/images/emoji/flag_se.pngbin0 -> 455 bytes
-rw-r--r--app/assets/images/emoji/flag_sg.pngbin0 -> 730 bytes
-rw-r--r--app/assets/images/emoji/flag_sh.pngbin0 -> 1369 bytes
-rw-r--r--app/assets/images/emoji/flag_si.pngbin0 -> 1030 bytes
-rw-r--r--app/assets/images/emoji/flag_sj.pngbin0 -> 495 bytes
-rw-r--r--app/assets/images/emoji/flag_sk.pngbin0 -> 780 bytes
-rw-r--r--app/assets/images/emoji/flag_sl.pngbin0 -> 510 bytes
-rw-r--r--app/assets/images/emoji/flag_sm.pngbin0 -> 2000 bytes
-rw-r--r--app/assets/images/emoji/flag_sn.pngbin0 -> 621 bytes
-rw-r--r--app/assets/images/emoji/flag_so.pngbin0 -> 609 bytes
-rw-r--r--app/assets/images/emoji/flag_sr.pngbin0 -> 650 bytes
-rw-r--r--app/assets/images/emoji/flag_ss.pngbin0 -> 722 bytes
-rw-r--r--app/assets/images/emoji/flag_st.pngbin0 -> 562 bytes
-rw-r--r--app/assets/images/emoji/flag_sv.pngbin0 -> 1125 bytes
-rw-r--r--app/assets/images/emoji/flag_sx.pngbin0 -> 1195 bytes
-rw-r--r--app/assets/images/emoji/flag_sy.pngbin0 -> 696 bytes
-rw-r--r--app/assets/images/emoji/flag_sz.pngbin0 -> 1102 bytes
-rw-r--r--app/assets/images/emoji/flag_ta.pngbin0 -> 1907 bytes
-rw-r--r--app/assets/images/emoji/flag_tc.pngbin0 -> 1538 bytes
-rw-r--r--app/assets/images/emoji/flag_td.pngbin0 -> 443 bytes
-rw-r--r--app/assets/images/emoji/flag_tf.pngbin0 -> 857 bytes
-rw-r--r--app/assets/images/emoji/flag_tg.pngbin0 -> 790 bytes
-rw-r--r--app/assets/images/emoji/flag_th.pngbin0 -> 421 bytes
-rw-r--r--app/assets/images/emoji/flag_tj.pngbin0 -> 906 bytes
-rw-r--r--app/assets/images/emoji/flag_tk.pngbin0 -> 835 bytes
-rw-r--r--app/assets/images/emoji/flag_tl.pngbin0 -> 849 bytes
-rw-r--r--app/assets/images/emoji/flag_tm.pngbin0 -> 1178 bytes
-rw-r--r--app/assets/images/emoji/flag_tn.pngbin0 -> 625 bytes
-rw-r--r--app/assets/images/emoji/flag_to.pngbin0 -> 553 bytes
-rw-r--r--app/assets/images/emoji/flag_tr.pngbin0 -> 576 bytes
-rw-r--r--app/assets/images/emoji/flag_tt.pngbin0 -> 604 bytes
-rw-r--r--app/assets/images/emoji/flag_tv.pngbin0 -> 1120 bytes
-rw-r--r--app/assets/images/emoji/flag_tw.pngbin0 -> 761 bytes
-rw-r--r--app/assets/images/emoji/flag_tz.pngbin0 -> 1061 bytes
-rw-r--r--app/assets/images/emoji/flag_ua.pngbin0 -> 528 bytes
-rw-r--r--app/assets/images/emoji/flag_ug.pngbin0 -> 887 bytes
-rw-r--r--app/assets/images/emoji/flag_um.pngbin0 -> 776 bytes
-rw-r--r--app/assets/images/emoji/flag_us.pngbin0 -> 776 bytes
-rw-r--r--app/assets/images/emoji/flag_uy.pngbin0 -> 966 bytes
-rw-r--r--app/assets/images/emoji/flag_uz.pngbin0 -> 750 bytes
-rw-r--r--app/assets/images/emoji/flag_va.pngbin0 -> 1331 bytes
-rw-r--r--app/assets/images/emoji/flag_vc.pngbin0 -> 897 bytes
-rw-r--r--app/assets/images/emoji/flag_ve.pngbin0 -> 748 bytes
-rw-r--r--app/assets/images/emoji/flag_vg.pngbin0 -> 1789 bytes
-rw-r--r--app/assets/images/emoji/flag_vi.pngbin0 -> 1378 bytes
-rw-r--r--app/assets/images/emoji/flag_vn.pngbin0 -> 583 bytes
-rw-r--r--app/assets/images/emoji/flag_vu.pngbin0 -> 844 bytes
-rw-r--r--app/assets/images/emoji/flag_wf.pngbin0 -> 443 bytes
-rw-r--r--app/assets/images/emoji/flag_white.pngbin0 -> 699 bytes
-rw-r--r--app/assets/images/emoji/flag_ws.pngbin0 -> 634 bytes
-rw-r--r--app/assets/images/emoji/flag_xk.pngbin0 -> 722 bytes
-rw-r--r--app/assets/images/emoji/flag_ye.pngbin0 -> 507 bytes
-rw-r--r--app/assets/images/emoji/flag_yt.pngbin0 -> 1623 bytes
-rw-r--r--app/assets/images/emoji/flag_za.pngbin0 -> 676 bytes
-rw-r--r--app/assets/images/emoji/flag_zm.pngbin0 -> 881 bytes
-rw-r--r--app/assets/images/emoji/flag_zw.pngbin0 -> 993 bytes
-rw-r--r--app/assets/images/emoji/flags.pngbin0 -> 1722 bytes
-rw-r--r--app/assets/images/emoji/flashlight.pngbin0 -> 964 bytes
-rw-r--r--app/assets/images/emoji/fleur-de-lis.pngbin0 -> 632 bytes
-rw-r--r--app/assets/images/emoji/floppy_disk.pngbin0 -> 258 bytes
-rw-r--r--app/assets/images/emoji/flower_playing_cards.pngbin0 -> 449 bytes
-rw-r--r--app/assets/images/emoji/flushed.pngbin0 -> 1127 bytes
-rw-r--r--app/assets/images/emoji/fog.pngbin0 -> 713 bytes
-rw-r--r--app/assets/images/emoji/foggy.pngbin0 -> 1069 bytes
-rw-r--r--app/assets/images/emoji/football.pngbin0 -> 956 bytes
-rw-r--r--app/assets/images/emoji/footprints.pngbin0 -> 621 bytes
-rw-r--r--app/assets/images/emoji/fork_and_knife.pngbin0 -> 668 bytes
-rw-r--r--app/assets/images/emoji/fork_knife_plate.pngbin0 -> 976 bytes
-rw-r--r--app/assets/images/emoji/fountain.pngbin0 -> 1768 bytes
-rw-r--r--app/assets/images/emoji/four.pngbin0 -> 497 bytes
-rw-r--r--app/assets/images/emoji/four_leaf_clover.pngbin0 -> 1156 bytes
-rw-r--r--app/assets/images/emoji/fox.pngbin0 -> 1556 bytes
-rw-r--r--app/assets/images/emoji/frame_photo.pngbin0 -> 514 bytes
-rw-r--r--app/assets/images/emoji/free.pngbin0 -> 370 bytes
-rw-r--r--app/assets/images/emoji/french_bread.pngbin0 -> 1551 bytes
-rw-r--r--app/assets/images/emoji/fried_shrimp.pngbin0 -> 1241 bytes
-rw-r--r--app/assets/images/emoji/fries.pngbin0 -> 1873 bytes
-rw-r--r--app/assets/images/emoji/frog.pngbin0 -> 897 bytes
-rw-r--r--app/assets/images/emoji/frowning.pngbin0 -> 633 bytes
-rw-r--r--app/assets/images/emoji/frowning2.pngbin0 -> 589 bytes
-rw-r--r--app/assets/images/emoji/fuelpump.pngbin0 -> 864 bytes
-rw-r--r--app/assets/images/emoji/full_moon.pngbin0 -> 841 bytes
-rw-r--r--app/assets/images/emoji/full_moon_with_face.pngbin0 -> 1186 bytes
-rw-r--r--app/assets/images/emoji/game_die.pngbin0 -> 1136 bytes
-rw-r--r--app/assets/images/emoji/gear.pngbin0 -> 747 bytes
-rw-r--r--app/assets/images/emoji/gem.pngbin0 -> 715 bytes
-rw-r--r--app/assets/images/emoji/gemini.pngbin0 -> 547 bytes
-rw-r--r--app/assets/images/emoji/ghost.pngbin0 -> 1465 bytes
-rw-r--r--app/assets/images/emoji/gift.pngbin0 -> 1966 bytes
-rw-r--r--app/assets/images/emoji/gift_heart.pngbin0 -> 1141 bytes
-rw-r--r--app/assets/images/emoji/girl.pngbin0 -> 1261 bytes
-rw-r--r--app/assets/images/emoji/girl_tone1.pngbin0 -> 1259 bytes
-rw-r--r--app/assets/images/emoji/girl_tone2.pngbin0 -> 1255 bytes
-rw-r--r--app/assets/images/emoji/girl_tone3.pngbin0 -> 1255 bytes
-rw-r--r--app/assets/images/emoji/girl_tone4.pngbin0 -> 1241 bytes
-rw-r--r--app/assets/images/emoji/girl_tone5.pngbin0 -> 1245 bytes
-rw-r--r--app/assets/images/emoji/globe_with_meridians.pngbin0 -> 796 bytes
-rw-r--r--app/assets/images/emoji/goal.pngbin0 -> 1242 bytes
-rw-r--r--app/assets/images/emoji/goat.pngbin0 -> 981 bytes
-rw-r--r--app/assets/images/emoji/golf.pngbin0 -> 823 bytes
-rw-r--r--app/assets/images/emoji/golfer.pngbin0 -> 1189 bytes
-rw-r--r--app/assets/images/emoji/gorilla.pngbin0 -> 1090 bytes
-rw-r--r--app/assets/images/emoji/grapes.pngbin0 -> 1552 bytes
-rw-r--r--app/assets/images/emoji/green_apple.pngbin0 -> 656 bytes
-rw-r--r--app/assets/images/emoji/green_book.pngbin0 -> 1366 bytes
-rw-r--r--app/assets/images/emoji/green_heart.pngbin0 -> 435 bytes
-rw-r--r--app/assets/images/emoji/grey_exclamation.pngbin0 -> 354 bytes
-rw-r--r--app/assets/images/emoji/grey_question.pngbin0 -> 449 bytes
-rw-r--r--app/assets/images/emoji/grimacing.pngbin0 -> 694 bytes
-rw-r--r--app/assets/images/emoji/grin.pngbin0 -> 767 bytes
-rw-r--r--app/assets/images/emoji/grinning.pngbin0 -> 810 bytes
-rw-r--r--app/assets/images/emoji/guardsman.pngbin0 -> 1140 bytes
-rw-r--r--app/assets/images/emoji/guardsman_tone1.pngbin0 -> 1122 bytes
-rw-r--r--app/assets/images/emoji/guardsman_tone2.pngbin0 -> 1160 bytes
-rw-r--r--app/assets/images/emoji/guardsman_tone3.pngbin0 -> 1160 bytes
-rw-r--r--app/assets/images/emoji/guardsman_tone4.pngbin0 -> 1157 bytes
-rw-r--r--app/assets/images/emoji/guardsman_tone5.pngbin0 -> 1165 bytes
-rw-r--r--app/assets/images/emoji/guitar.pngbin0 -> 1056 bytes
-rw-r--r--app/assets/images/emoji/gun.pngbin0 -> 1859 bytes
-rw-r--r--app/assets/images/emoji/haircut.pngbin0 -> 1935 bytes
-rw-r--r--app/assets/images/emoji/haircut_tone1.pngbin0 -> 1945 bytes
-rw-r--r--app/assets/images/emoji/haircut_tone2.pngbin0 -> 1935 bytes
-rw-r--r--app/assets/images/emoji/haircut_tone3.pngbin0 -> 1923 bytes
-rw-r--r--app/assets/images/emoji/haircut_tone4.pngbin0 -> 1904 bytes
-rw-r--r--app/assets/images/emoji/haircut_tone5.pngbin0 -> 1920 bytes
-rw-r--r--app/assets/images/emoji/hamburger.pngbin0 -> 1973 bytes
-rw-r--r--app/assets/images/emoji/hammer.pngbin0 -> 834 bytes
-rw-r--r--app/assets/images/emoji/hammer_pick.pngbin0 -> 1068 bytes
-rw-r--r--app/assets/images/emoji/hamster.pngbin0 -> 1279 bytes
-rw-r--r--app/assets/images/emoji/hand_splayed.pngbin0 -> 1081 bytes
-rw-r--r--app/assets/images/emoji/hand_splayed_tone1.pngbin0 -> 1081 bytes
-rw-r--r--app/assets/images/emoji/hand_splayed_tone2.pngbin0 -> 1081 bytes
-rw-r--r--app/assets/images/emoji/hand_splayed_tone3.pngbin0 -> 1081 bytes
-rw-r--r--app/assets/images/emoji/hand_splayed_tone4.pngbin0 -> 1081 bytes
-rw-r--r--app/assets/images/emoji/hand_splayed_tone5.pngbin0 -> 1081 bytes
-rw-r--r--app/assets/images/emoji/handbag.pngbin0 -> 1285 bytes
-rw-r--r--app/assets/images/emoji/handball.pngbin0 -> 1634 bytes
-rw-r--r--app/assets/images/emoji/handball_tone1.pngbin0 -> 1645 bytes
-rw-r--r--app/assets/images/emoji/handball_tone2.pngbin0 -> 1628 bytes
-rw-r--r--app/assets/images/emoji/handball_tone3.pngbin0 -> 1639 bytes
-rw-r--r--app/assets/images/emoji/handball_tone4.pngbin0 -> 1634 bytes
-rw-r--r--app/assets/images/emoji/handball_tone5.pngbin0 -> 1606 bytes
-rw-r--r--app/assets/images/emoji/handshake.pngbin0 -> 1366 bytes
-rw-r--r--app/assets/images/emoji/handshake_tone1.pngbin0 -> 1381 bytes
-rw-r--r--app/assets/images/emoji/handshake_tone2.pngbin0 -> 1381 bytes
-rw-r--r--app/assets/images/emoji/handshake_tone3.pngbin0 -> 1381 bytes
-rw-r--r--app/assets/images/emoji/handshake_tone4.pngbin0 -> 1381 bytes
-rw-r--r--app/assets/images/emoji/handshake_tone5.pngbin0 -> 1381 bytes
-rw-r--r--app/assets/images/emoji/hash.pngbin0 -> 604 bytes
-rw-r--r--app/assets/images/emoji/hatched_chick.pngbin0 -> 1174 bytes
-rw-r--r--app/assets/images/emoji/hatching_chick.pngbin0 -> 1598 bytes
-rw-r--r--app/assets/images/emoji/head_bandage.pngbin0 -> 1199 bytes
-rw-r--r--app/assets/images/emoji/headphones.pngbin0 -> 1202 bytes
-rw-r--r--app/assets/images/emoji/hear_no_evil.pngbin0 -> 1210 bytes
-rw-r--r--app/assets/images/emoji/heart.pngbin0 -> 435 bytes
-rw-r--r--app/assets/images/emoji/heart_decoration.pngbin0 -> 557 bytes
-rw-r--r--app/assets/images/emoji/heart_exclamation.pngbin0 -> 471 bytes
-rw-r--r--app/assets/images/emoji/heart_eyes.pngbin0 -> 1069 bytes
-rw-r--r--app/assets/images/emoji/heart_eyes_cat.pngbin0 -> 1512 bytes
-rw-r--r--app/assets/images/emoji/heartbeat.pngbin0 -> 699 bytes
-rw-r--r--app/assets/images/emoji/heartpulse.pngbin0 -> 675 bytes
-rw-r--r--app/assets/images/emoji/hearts.pngbin0 -> 449 bytes
-rw-r--r--app/assets/images/emoji/heavy_check_mark.pngbin0 -> 438 bytes
-rw-r--r--app/assets/images/emoji/heavy_division_sign.pngbin0 -> 204 bytes
-rw-r--r--app/assets/images/emoji/heavy_dollar_sign.pngbin0 -> 429 bytes
-rw-r--r--app/assets/images/emoji/heavy_minus_sign.pngbin0 -> 108 bytes
-rw-r--r--app/assets/images/emoji/heavy_multiplication_x.pngbin0 -> 298 bytes
-rw-r--r--app/assets/images/emoji/heavy_plus_sign.pngbin0 -> 115 bytes
-rw-r--r--app/assets/images/emoji/helicopter.pngbin0 -> 1098 bytes
-rw-r--r--app/assets/images/emoji/helmet_with_cross.pngbin0 -> 1014 bytes
-rw-r--r--app/assets/images/emoji/herb.pngbin0 -> 886 bytes
-rw-r--r--app/assets/images/emoji/hibiscus.pngbin0 -> 1815 bytes
-rw-r--r--app/assets/images/emoji/high_brightness.pngbin0 -> 474 bytes
-rw-r--r--app/assets/images/emoji/high_heel.pngbin0 -> 1008 bytes
-rw-r--r--app/assets/images/emoji/hockey.pngbin0 -> 1010 bytes
-rw-r--r--app/assets/images/emoji/hole.pngbin0 -> 1390 bytes
-rw-r--r--app/assets/images/emoji/homes.pngbin0 -> 981 bytes
-rw-r--r--app/assets/images/emoji/honey_pot.pngbin0 -> 1217 bytes
-rw-r--r--app/assets/images/emoji/horse.pngbin0 -> 1694 bytes
-rw-r--r--app/assets/images/emoji/horse_racing.pngbin0 -> 2096 bytes
-rw-r--r--app/assets/images/emoji/horse_racing_tone1.pngbin0 -> 2099 bytes
-rw-r--r--app/assets/images/emoji/horse_racing_tone2.pngbin0 -> 2103 bytes
-rw-r--r--app/assets/images/emoji/horse_racing_tone3.pngbin0 -> 2090 bytes
-rw-r--r--app/assets/images/emoji/horse_racing_tone4.pngbin0 -> 2090 bytes
-rw-r--r--app/assets/images/emoji/horse_racing_tone5.pngbin0 -> 2085 bytes
-rw-r--r--app/assets/images/emoji/hospital.pngbin0 -> 530 bytes
-rw-r--r--app/assets/images/emoji/hot_pepper.pngbin0 -> 677 bytes
-rw-r--r--app/assets/images/emoji/hotdog.pngbin0 -> 1770 bytes
-rw-r--r--app/assets/images/emoji/hotel.pngbin0 -> 1322 bytes
-rw-r--r--app/assets/images/emoji/hotsprings.pngbin0 -> 733 bytes
-rw-r--r--app/assets/images/emoji/hourglass.pngbin0 -> 800 bytes
-rw-r--r--app/assets/images/emoji/hourglass_flowing_sand.pngbin0 -> 847 bytes
-rw-r--r--app/assets/images/emoji/house.pngbin0 -> 863 bytes
-rw-r--r--app/assets/images/emoji/house_abandoned.pngbin0 -> 1606 bytes
-rw-r--r--app/assets/images/emoji/house_with_garden.pngbin0 -> 1613 bytes
-rw-r--r--app/assets/images/emoji/hugging.pngbin0 -> 1425 bytes
-rw-r--r--app/assets/images/emoji/hushed.pngbin0 -> 634 bytes
-rw-r--r--app/assets/images/emoji/ice_cream.pngbin0 -> 1779 bytes
-rw-r--r--app/assets/images/emoji/ice_skate.pngbin0 -> 1574 bytes
-rw-r--r--app/assets/images/emoji/icecream.pngbin0 -> 1496 bytes
-rw-r--r--app/assets/images/emoji/id.pngbin0 -> 348 bytes
-rw-r--r--app/assets/images/emoji/ideograph_advantage.pngbin0 -> 716 bytes
-rw-r--r--app/assets/images/emoji/imp.pngbin0 -> 1988 bytes
-rw-r--r--app/assets/images/emoji/inbox_tray.pngbin0 -> 1029 bytes
-rw-r--r--app/assets/images/emoji/incoming_envelope.pngbin0 -> 1129 bytes
-rw-r--r--app/assets/images/emoji/information_desk_person.pngbin0 -> 1580 bytes
-rw-r--r--app/assets/images/emoji/information_desk_person_tone1.pngbin0 -> 1597 bytes
-rw-r--r--app/assets/images/emoji/information_desk_person_tone2.pngbin0 -> 1590 bytes
-rw-r--r--app/assets/images/emoji/information_desk_person_tone3.pngbin0 -> 1580 bytes
-rw-r--r--app/assets/images/emoji/information_desk_person_tone4.pngbin0 -> 1572 bytes
-rw-r--r--app/assets/images/emoji/information_desk_person_tone5.pngbin0 -> 1588 bytes
-rw-r--r--app/assets/images/emoji/information_source.pngbin0 -> 506 bytes
-rw-r--r--app/assets/images/emoji/innocent.pngbin0 -> 935 bytes
-rw-r--r--app/assets/images/emoji/interrobang.pngbin0 -> 601 bytes
-rw-r--r--app/assets/images/emoji/iphone.pngbin0 -> 695 bytes
-rw-r--r--app/assets/images/emoji/island.pngbin0 -> 1273 bytes
-rw-r--r--app/assets/images/emoji/izakaya_lantern.pngbin0 -> 1227 bytes
-rw-r--r--app/assets/images/emoji/jack_o_lantern.pngbin0 -> 2289 bytes
-rw-r--r--app/assets/images/emoji/japan.pngbin0 -> 539 bytes
-rw-r--r--app/assets/images/emoji/japanese_castle.pngbin0 -> 1404 bytes
-rw-r--r--app/assets/images/emoji/japanese_goblin.pngbin0 -> 1561 bytes
-rw-r--r--app/assets/images/emoji/japanese_ogre.pngbin0 -> 1864 bytes
-rw-r--r--app/assets/images/emoji/jeans.pngbin0 -> 1158 bytes
-rw-r--r--app/assets/images/emoji/joy.pngbin0 -> 1136 bytes
-rw-r--r--app/assets/images/emoji/joy_cat.pngbin0 -> 1633 bytes
-rw-r--r--app/assets/images/emoji/joystick.pngbin0 -> 1039 bytes
-rw-r--r--app/assets/images/emoji/juggling.pngbin0 -> 1165 bytes
-rw-r--r--app/assets/images/emoji/juggling_tone1.pngbin0 -> 1171 bytes
-rw-r--r--app/assets/images/emoji/juggling_tone2.pngbin0 -> 1160 bytes
-rw-r--r--app/assets/images/emoji/juggling_tone3.pngbin0 -> 1170 bytes
-rw-r--r--app/assets/images/emoji/juggling_tone4.pngbin0 -> 1167 bytes
-rw-r--r--app/assets/images/emoji/juggling_tone5.pngbin0 -> 1161 bytes
-rw-r--r--app/assets/images/emoji/kaaba.pngbin0 -> 1251 bytes
-rw-r--r--app/assets/images/emoji/key.pngbin0 -> 770 bytes
-rw-r--r--app/assets/images/emoji/key2.pngbin0 -> 593 bytes
-rw-r--r--app/assets/images/emoji/keyboard.pngbin0 -> 429 bytes
-rw-r--r--app/assets/images/emoji/kimono.pngbin0 -> 1527 bytes
-rw-r--r--app/assets/images/emoji/kiss.pngbin0 -> 842 bytes
-rw-r--r--app/assets/images/emoji/kiss_mm.pngbin0 -> 1269 bytes
-rw-r--r--app/assets/images/emoji/kiss_ww.pngbin0 -> 1149 bytes
-rw-r--r--app/assets/images/emoji/kissing.pngbin0 -> 738 bytes
-rw-r--r--app/assets/images/emoji/kissing_cat.pngbin0 -> 1468 bytes
-rw-r--r--app/assets/images/emoji/kissing_closed_eyes.pngbin0 -> 888 bytes
-rw-r--r--app/assets/images/emoji/kissing_heart.pngbin0 -> 843 bytes
-rw-r--r--app/assets/images/emoji/kissing_smiling_eyes.pngbin0 -> 648 bytes
-rw-r--r--app/assets/images/emoji/kiwi.pngbin0 -> 1892 bytes
-rw-r--r--app/assets/images/emoji/knife.pngbin0 -> 616 bytes
-rw-r--r--app/assets/images/emoji/koala.pngbin0 -> 1428 bytes
-rw-r--r--app/assets/images/emoji/koko.pngbin0 -> 266 bytes
-rw-r--r--app/assets/images/emoji/label.pngbin0 -> 669 bytes
-rw-r--r--app/assets/images/emoji/large_blue_circle.pngbin0 -> 371 bytes
-rw-r--r--app/assets/images/emoji/large_blue_diamond.pngbin0 -> 245 bytes
-rw-r--r--app/assets/images/emoji/large_orange_diamond.pngbin0 -> 248 bytes
-rw-r--r--app/assets/images/emoji/last_quarter_moon.pngbin0 -> 1180 bytes
-rw-r--r--app/assets/images/emoji/last_quarter_moon_with_face.pngbin0 -> 1030 bytes
-rw-r--r--app/assets/images/emoji/laughing.pngbin0 -> 901 bytes
-rw-r--r--app/assets/images/emoji/leaves.pngbin0 -> 993 bytes
-rw-r--r--app/assets/images/emoji/ledger.pngbin0 -> 1528 bytes
-rw-r--r--app/assets/images/emoji/left_facing_fist.pngbin0 -> 972 bytes
-rw-r--r--app/assets/images/emoji/left_facing_fist_tone1.pngbin0 -> 960 bytes
-rw-r--r--app/assets/images/emoji/left_facing_fist_tone2.pngbin0 -> 972 bytes
-rw-r--r--app/assets/images/emoji/left_facing_fist_tone3.pngbin0 -> 960 bytes
-rw-r--r--app/assets/images/emoji/left_facing_fist_tone4.pngbin0 -> 960 bytes
-rw-r--r--app/assets/images/emoji/left_facing_fist_tone5.pngbin0 -> 976 bytes
-rw-r--r--app/assets/images/emoji/left_luggage.pngbin0 -> 576 bytes
-rw-r--r--app/assets/images/emoji/left_right_arrow.pngbin0 -> 495 bytes
-rw-r--r--app/assets/images/emoji/leftwards_arrow_with_hook.pngbin0 -> 643 bytes
-rw-r--r--app/assets/images/emoji/lemon.pngbin0 -> 1033 bytes
-rw-r--r--app/assets/images/emoji/leo.pngbin0 -> 745 bytes
-rw-r--r--app/assets/images/emoji/leopard.pngbin0 -> 2222 bytes
-rw-r--r--app/assets/images/emoji/level_slider.pngbin0 -> 454 bytes
-rw-r--r--app/assets/images/emoji/levitate.pngbin0 -> 914 bytes
-rw-r--r--app/assets/images/emoji/libra.pngbin0 -> 657 bytes
-rw-r--r--app/assets/images/emoji/lifter.pngbin0 -> 1356 bytes
-rw-r--r--app/assets/images/emoji/lifter_tone1.pngbin0 -> 1346 bytes
-rw-r--r--app/assets/images/emoji/lifter_tone2.pngbin0 -> 1347 bytes
-rw-r--r--app/assets/images/emoji/lifter_tone3.pngbin0 -> 1339 bytes
-rw-r--r--app/assets/images/emoji/lifter_tone4.pngbin0 -> 1343 bytes
-rw-r--r--app/assets/images/emoji/lifter_tone5.pngbin0 -> 1337 bytes
-rw-r--r--app/assets/images/emoji/light_rail.pngbin0 -> 902 bytes
-rw-r--r--app/assets/images/emoji/link.pngbin0 -> 477 bytes
-rw-r--r--app/assets/images/emoji/lion_face.pngbin0 -> 1728 bytes
-rw-r--r--app/assets/images/emoji/lips.pngbin0 -> 599 bytes
-rw-r--r--app/assets/images/emoji/lipstick.pngbin0 -> 549 bytes
-rw-r--r--app/assets/images/emoji/lizard.pngbin0 -> 1709 bytes
-rw-r--r--app/assets/images/emoji/lock.pngbin0 -> 986 bytes
-rw-r--r--app/assets/images/emoji/lock_with_ink_pen.pngbin0 -> 1123 bytes
-rw-r--r--app/assets/images/emoji/lollipop.pngbin0 -> 2164 bytes
-rw-r--r--app/assets/images/emoji/loop.pngbin0 -> 550 bytes
-rw-r--r--app/assets/images/emoji/loud_sound.pngbin0 -> 977 bytes
-rw-r--r--app/assets/images/emoji/loudspeaker.pngbin0 -> 1316 bytes
-rw-r--r--app/assets/images/emoji/love_hotel.pngbin0 -> 372 bytes
-rw-r--r--app/assets/images/emoji/love_letter.pngbin0 -> 923 bytes
-rw-r--r--app/assets/images/emoji/low_brightness.pngbin0 -> 431 bytes
-rw-r--r--app/assets/images/emoji/lying_face.pngbin0 -> 1103 bytes
-rw-r--r--app/assets/images/emoji/m.pngbin0 -> 500 bytes
-rw-r--r--app/assets/images/emoji/mag.pngbin0 -> 1240 bytes
-rw-r--r--app/assets/images/emoji/mag_right.pngbin0 -> 1251 bytes
-rw-r--r--app/assets/images/emoji/mahjong.pngbin0 -> 951 bytes
-rw-r--r--app/assets/images/emoji/mailbox.pngbin0 -> 1166 bytes
-rw-r--r--app/assets/images/emoji/mailbox_closed.pngbin0 -> 1192 bytes
-rw-r--r--app/assets/images/emoji/mailbox_with_mail.pngbin0 -> 1307 bytes
-rw-r--r--app/assets/images/emoji/mailbox_with_no_mail.pngbin0 -> 960 bytes
-rw-r--r--app/assets/images/emoji/man.pngbin0 -> 1092 bytes
-rw-r--r--app/assets/images/emoji/man_dancing.pngbin0 -> 1400 bytes
-rw-r--r--app/assets/images/emoji/man_dancing_tone1.pngbin0 -> 1404 bytes
-rw-r--r--app/assets/images/emoji/man_dancing_tone2.pngbin0 -> 1402 bytes
-rw-r--r--app/assets/images/emoji/man_dancing_tone3.pngbin0 -> 1409 bytes
-rw-r--r--app/assets/images/emoji/man_dancing_tone4.pngbin0 -> 1421 bytes
-rw-r--r--app/assets/images/emoji/man_dancing_tone5.pngbin0 -> 1418 bytes
-rw-r--r--app/assets/images/emoji/man_in_tuxedo.pngbin0 -> 1307 bytes
-rw-r--r--app/assets/images/emoji/man_in_tuxedo_tone1.pngbin0 -> 1307 bytes
-rw-r--r--app/assets/images/emoji/man_in_tuxedo_tone2.pngbin0 -> 1307 bytes
-rw-r--r--app/assets/images/emoji/man_in_tuxedo_tone3.pngbin0 -> 1307 bytes
-rw-r--r--app/assets/images/emoji/man_in_tuxedo_tone4.pngbin0 -> 1307 bytes
-rw-r--r--app/assets/images/emoji/man_in_tuxedo_tone5.pngbin0 -> 1302 bytes
-rw-r--r--app/assets/images/emoji/man_tone1.pngbin0 -> 1069 bytes
-rw-r--r--app/assets/images/emoji/man_tone2.pngbin0 -> 1069 bytes
-rw-r--r--app/assets/images/emoji/man_tone3.pngbin0 -> 1069 bytes
-rw-r--r--app/assets/images/emoji/man_tone4.pngbin0 -> 1069 bytes
-rw-r--r--app/assets/images/emoji/man_tone5.pngbin0 -> 1087 bytes
-rw-r--r--app/assets/images/emoji/man_with_gua_pi_mao.pngbin0 -> 1339 bytes
-rw-r--r--app/assets/images/emoji/man_with_gua_pi_mao_tone1.pngbin0 -> 1328 bytes
-rw-r--r--app/assets/images/emoji/man_with_gua_pi_mao_tone2.pngbin0 -> 1332 bytes
-rw-r--r--app/assets/images/emoji/man_with_gua_pi_mao_tone3.pngbin0 -> 1329 bytes
-rw-r--r--app/assets/images/emoji/man_with_gua_pi_mao_tone4.pngbin0 -> 1325 bytes
-rw-r--r--app/assets/images/emoji/man_with_gua_pi_mao_tone5.pngbin0 -> 1337 bytes
-rw-r--r--app/assets/images/emoji/man_with_turban.pngbin0 -> 1618 bytes
-rw-r--r--app/assets/images/emoji/man_with_turban_tone1.pngbin0 -> 1584 bytes
-rw-r--r--app/assets/images/emoji/man_with_turban_tone2.pngbin0 -> 1588 bytes
-rw-r--r--app/assets/images/emoji/man_with_turban_tone3.pngbin0 -> 1584 bytes
-rw-r--r--app/assets/images/emoji/man_with_turban_tone4.pngbin0 -> 1583 bytes
-rw-r--r--app/assets/images/emoji/man_with_turban_tone5.pngbin0 -> 1605 bytes
-rw-r--r--app/assets/images/emoji/mans_shoe.pngbin0 -> 1649 bytes
-rw-r--r--app/assets/images/emoji/map.pngbin0 -> 2352 bytes
-rw-r--r--app/assets/images/emoji/maple_leaf.pngbin0 -> 1117 bytes
-rw-r--r--app/assets/images/emoji/martial_arts_uniform.pngbin0 -> 1412 bytes
-rw-r--r--app/assets/images/emoji/mask.pngbin0 -> 1322 bytes
-rw-r--r--app/assets/images/emoji/massage.pngbin0 -> 1571 bytes
-rw-r--r--app/assets/images/emoji/massage_tone1.pngbin0 -> 1578 bytes
-rw-r--r--app/assets/images/emoji/massage_tone2.pngbin0 -> 1565 bytes
-rw-r--r--app/assets/images/emoji/massage_tone3.pngbin0 -> 1553 bytes
-rw-r--r--app/assets/images/emoji/massage_tone4.pngbin0 -> 1546 bytes
-rw-r--r--app/assets/images/emoji/massage_tone5.pngbin0 -> 1557 bytes
-rw-r--r--app/assets/images/emoji/meat_on_bone.pngbin0 -> 1465 bytes
-rw-r--r--app/assets/images/emoji/medal.pngbin0 -> 1700 bytes
-rw-r--r--app/assets/images/emoji/mega.pngbin0 -> 1751 bytes
-rw-r--r--app/assets/images/emoji/melon.pngbin0 -> 2005 bytes
-rw-r--r--app/assets/images/emoji/menorah.pngbin0 -> 1279 bytes
-rw-r--r--app/assets/images/emoji/mens.pngbin0 -> 561 bytes
-rw-r--r--app/assets/images/emoji/metal.pngbin0 -> 894 bytes
-rw-r--r--app/assets/images/emoji/metal_tone1.pngbin0 -> 894 bytes
-rw-r--r--app/assets/images/emoji/metal_tone2.pngbin0 -> 888 bytes
-rw-r--r--app/assets/images/emoji/metal_tone3.pngbin0 -> 894 bytes
-rw-r--r--app/assets/images/emoji/metal_tone4.pngbin0 -> 888 bytes
-rw-r--r--app/assets/images/emoji/metal_tone5.pngbin0 -> 894 bytes
-rw-r--r--app/assets/images/emoji/metro.pngbin0 -> 1020 bytes
-rw-r--r--app/assets/images/emoji/microphone.pngbin0 -> 1165 bytes
-rw-r--r--app/assets/images/emoji/microphone2.pngbin0 -> 839 bytes
-rw-r--r--app/assets/images/emoji/microscope.pngbin0 -> 1113 bytes
-rw-r--r--app/assets/images/emoji/middle_finger.pngbin0 -> 893 bytes
-rw-r--r--app/assets/images/emoji/middle_finger_tone1.pngbin0 -> 892 bytes
-rw-r--r--app/assets/images/emoji/middle_finger_tone2.pngbin0 -> 892 bytes
-rw-r--r--app/assets/images/emoji/middle_finger_tone3.pngbin0 -> 892 bytes
-rw-r--r--app/assets/images/emoji/middle_finger_tone4.pngbin0 -> 892 bytes
-rw-r--r--app/assets/images/emoji/middle_finger_tone5.pngbin0 -> 892 bytes
-rw-r--r--app/assets/images/emoji/military_medal.pngbin0 -> 949 bytes
-rw-r--r--app/assets/images/emoji/milk.pngbin0 -> 1224 bytes
-rw-r--r--app/assets/images/emoji/milky_way.pngbin0 -> 622 bytes
-rw-r--r--app/assets/images/emoji/minibus.pngbin0 -> 1256 bytes
-rw-r--r--app/assets/images/emoji/minidisc.pngbin0 -> 522 bytes
-rw-r--r--app/assets/images/emoji/mobile_phone_off.pngbin0 -> 621 bytes
-rw-r--r--app/assets/images/emoji/money_mouth.pngbin0 -> 967 bytes
-rw-r--r--app/assets/images/emoji/money_with_wings.pngbin0 -> 2327 bytes
-rw-r--r--app/assets/images/emoji/moneybag.pngbin0 -> 2310 bytes
-rw-r--r--app/assets/images/emoji/monkey.pngbin0 -> 1348 bytes
-rw-r--r--app/assets/images/emoji/monkey_face.pngbin0 -> 1022 bytes
-rw-r--r--app/assets/images/emoji/monorail.pngbin0 -> 1068 bytes
-rw-r--r--app/assets/images/emoji/mortar_board.pngbin0 -> 710 bytes
-rw-r--r--app/assets/images/emoji/mosque.pngbin0 -> 984 bytes
-rw-r--r--app/assets/images/emoji/motor_scooter.pngbin0 -> 1207 bytes
-rw-r--r--app/assets/images/emoji/motorboat.pngbin0 -> 990 bytes
-rw-r--r--app/assets/images/emoji/motorcycle.pngbin0 -> 2081 bytes
-rw-r--r--app/assets/images/emoji/motorway.pngbin0 -> 1102 bytes
-rw-r--r--app/assets/images/emoji/mount_fuji.pngbin0 -> 881 bytes
-rw-r--r--app/assets/images/emoji/mountain.pngbin0 -> 1409 bytes
-rw-r--r--app/assets/images/emoji/mountain_bicyclist.pngbin0 -> 2288 bytes
-rw-r--r--app/assets/images/emoji/mountain_bicyclist_tone1.pngbin0 -> 2294 bytes
-rw-r--r--app/assets/images/emoji/mountain_bicyclist_tone2.pngbin0 -> 2298 bytes
-rw-r--r--app/assets/images/emoji/mountain_bicyclist_tone3.pngbin0 -> 2284 bytes
-rw-r--r--app/assets/images/emoji/mountain_bicyclist_tone4.pngbin0 -> 2288 bytes
-rw-r--r--app/assets/images/emoji/mountain_bicyclist_tone5.pngbin0 -> 2281 bytes
-rw-r--r--app/assets/images/emoji/mountain_cableway.pngbin0 -> 811 bytes
-rw-r--r--app/assets/images/emoji/mountain_railway.pngbin0 -> 1317 bytes
-rw-r--r--app/assets/images/emoji/mountain_snow.pngbin0 -> 1193 bytes
-rw-r--r--app/assets/images/emoji/mouse.pngbin0 -> 1245 bytes
-rw-r--r--app/assets/images/emoji/mouse2.pngbin0 -> 1324 bytes
-rw-r--r--app/assets/images/emoji/mouse_three_button.pngbin0 -> 934 bytes
-rw-r--r--app/assets/images/emoji/movie_camera.pngbin0 -> 576 bytes
-rw-r--r--app/assets/images/emoji/moyai.pngbin0 -> 1593 bytes
-rw-r--r--app/assets/images/emoji/mrs_claus.pngbin0 -> 2206 bytes
-rw-r--r--app/assets/images/emoji/mrs_claus_tone1.pngbin0 -> 1999 bytes
-rw-r--r--app/assets/images/emoji/mrs_claus_tone2.pngbin0 -> 2006 bytes
-rw-r--r--app/assets/images/emoji/mrs_claus_tone3.pngbin0 -> 2017 bytes
-rw-r--r--app/assets/images/emoji/mrs_claus_tone4.pngbin0 -> 2016 bytes
-rw-r--r--app/assets/images/emoji/mrs_claus_tone5.pngbin0 -> 2016 bytes
-rw-r--r--app/assets/images/emoji/muscle.pngbin0 -> 1012 bytes
-rw-r--r--app/assets/images/emoji/muscle_tone1.pngbin0 -> 1012 bytes
-rw-r--r--app/assets/images/emoji/muscle_tone2.pngbin0 -> 1012 bytes
-rw-r--r--app/assets/images/emoji/muscle_tone3.pngbin0 -> 1012 bytes
-rw-r--r--app/assets/images/emoji/muscle_tone4.pngbin0 -> 1012 bytes
-rw-r--r--app/assets/images/emoji/muscle_tone5.pngbin0 -> 1012 bytes
-rw-r--r--app/assets/images/emoji/mushroom.pngbin0 -> 1024 bytes
-rw-r--r--app/assets/images/emoji/musical_keyboard.pngbin0 -> 1695 bytes
-rw-r--r--app/assets/images/emoji/musical_note.pngbin0 -> 419 bytes
-rw-r--r--app/assets/images/emoji/musical_score.pngbin0 -> 1289 bytes
-rw-r--r--app/assets/images/emoji/mute.pngbin0 -> 823 bytes
-rw-r--r--app/assets/images/emoji/nail_care.pngbin0 -> 1639 bytes
-rw-r--r--app/assets/images/emoji/nail_care_tone1.pngbin0 -> 1712 bytes
-rw-r--r--app/assets/images/emoji/nail_care_tone2.pngbin0 -> 1711 bytes
-rw-r--r--app/assets/images/emoji/nail_care_tone3.pngbin0 -> 1727 bytes
-rw-r--r--app/assets/images/emoji/nail_care_tone4.pngbin0 -> 1728 bytes
-rw-r--r--app/assets/images/emoji/nail_care_tone5.pngbin0 -> 1716 bytes
-rw-r--r--app/assets/images/emoji/name_badge.pngbin0 -> 632 bytes
-rw-r--r--app/assets/images/emoji/nauseated_face.pngbin0 -> 965 bytes
-rw-r--r--app/assets/images/emoji/necktie.pngbin0 -> 995 bytes
-rw-r--r--app/assets/images/emoji/negative_squared_cross_mark.pngbin0 -> 370 bytes
-rw-r--r--app/assets/images/emoji/nerd.pngbin0 -> 975 bytes
-rw-r--r--app/assets/images/emoji/neutral_face.pngbin0 -> 517 bytes
-rw-r--r--app/assets/images/emoji/new.pngbin0 -> 486 bytes
-rw-r--r--app/assets/images/emoji/new_moon.pngbin0 -> 829 bytes
-rw-r--r--app/assets/images/emoji/new_moon_with_face.pngbin0 -> 975 bytes
-rw-r--r--app/assets/images/emoji/newspaper.pngbin0 -> 1178 bytes
-rw-r--r--app/assets/images/emoji/newspaper2.pngbin0 -> 1046 bytes
-rw-r--r--app/assets/images/emoji/ng.pngbin0 -> 445 bytes
-rw-r--r--app/assets/images/emoji/night_with_stars.pngbin0 -> 835 bytes
-rw-r--r--app/assets/images/emoji/nine.pngbin0 -> 607 bytes
-rw-r--r--app/assets/images/emoji/no_bell.pngbin0 -> 823 bytes
-rw-r--r--app/assets/images/emoji/no_bicycles.pngbin0 -> 998 bytes
-rw-r--r--app/assets/images/emoji/no_entry.pngbin0 -> 377 bytes
-rw-r--r--app/assets/images/emoji/no_entry_sign.pngbin0 -> 555 bytes
-rw-r--r--app/assets/images/emoji/no_good.pngbin0 -> 1750 bytes
-rw-r--r--app/assets/images/emoji/no_good_tone1.pngbin0 -> 1767 bytes
-rw-r--r--app/assets/images/emoji/no_good_tone2.pngbin0 -> 1756 bytes
-rw-r--r--app/assets/images/emoji/no_good_tone3.pngbin0 -> 1766 bytes
-rw-r--r--app/assets/images/emoji/no_good_tone4.pngbin0 -> 1782 bytes
-rw-r--r--app/assets/images/emoji/no_good_tone5.pngbin0 -> 1784 bytes
-rw-r--r--app/assets/images/emoji/no_mobile_phones.pngbin0 -> 790 bytes
-rw-r--r--app/assets/images/emoji/no_mouth.pngbin0 -> 465 bytes
-rw-r--r--app/assets/images/emoji/no_pedestrians.pngbin0 -> 875 bytes
-rw-r--r--app/assets/images/emoji/no_smoking.pngbin0 -> 1136 bytes
-rw-r--r--app/assets/images/emoji/non-potable_water.pngbin0 -> 827 bytes
-rw-r--r--app/assets/images/emoji/nose.pngbin0 -> 703 bytes
-rw-r--r--app/assets/images/emoji/nose_tone1.pngbin0 -> 703 bytes
-rw-r--r--app/assets/images/emoji/nose_tone2.pngbin0 -> 703 bytes
-rw-r--r--app/assets/images/emoji/nose_tone3.pngbin0 -> 703 bytes
-rw-r--r--app/assets/images/emoji/nose_tone4.pngbin0 -> 703 bytes
-rw-r--r--app/assets/images/emoji/nose_tone5.pngbin0 -> 703 bytes
-rw-r--r--app/assets/images/emoji/notebook.pngbin0 -> 1215 bytes
-rw-r--r--app/assets/images/emoji/notebook_with_decorative_cover.pngbin0 -> 1782 bytes
-rw-r--r--app/assets/images/emoji/notepad_spiral.pngbin0 -> 1377 bytes
-rw-r--r--app/assets/images/emoji/notes.pngbin0 -> 501 bytes
-rw-r--r--app/assets/images/emoji/nut_and_bolt.pngbin0 -> 899 bytes
-rw-r--r--app/assets/images/emoji/o.pngbin0 -> 475 bytes
-rw-r--r--app/assets/images/emoji/o2.pngbin0 -> 425 bytes
-rw-r--r--app/assets/images/emoji/ocean.pngbin0 -> 1018 bytes
-rw-r--r--app/assets/images/emoji/octagonal_sign.pngbin0 -> 260 bytes
-rw-r--r--app/assets/images/emoji/octopus.pngbin0 -> 1188 bytes
-rw-r--r--app/assets/images/emoji/oden.pngbin0 -> 794 bytes
-rw-r--r--app/assets/images/emoji/office.pngbin0 -> 524 bytes
-rw-r--r--app/assets/images/emoji/oil.pngbin0 -> 674 bytes
-rw-r--r--app/assets/images/emoji/ok.pngbin0 -> 511 bytes
-rw-r--r--app/assets/images/emoji/ok_hand.pngbin0 -> 979 bytes
-rw-r--r--app/assets/images/emoji/ok_hand_tone1.pngbin0 -> 979 bytes
-rw-r--r--app/assets/images/emoji/ok_hand_tone2.pngbin0 -> 979 bytes
-rw-r--r--app/assets/images/emoji/ok_hand_tone3.pngbin0 -> 979 bytes
-rw-r--r--app/assets/images/emoji/ok_hand_tone4.pngbin0 -> 979 bytes
-rw-r--r--app/assets/images/emoji/ok_hand_tone5.pngbin0 -> 979 bytes
-rw-r--r--app/assets/images/emoji/ok_woman.pngbin0 -> 1696 bytes
-rw-r--r--app/assets/images/emoji/ok_woman_tone1.pngbin0 -> 1696 bytes
-rw-r--r--app/assets/images/emoji/ok_woman_tone2.pngbin0 -> 1694 bytes
-rw-r--r--app/assets/images/emoji/ok_woman_tone3.pngbin0 -> 1675 bytes
-rw-r--r--app/assets/images/emoji/ok_woman_tone4.pngbin0 -> 1684 bytes
-rw-r--r--app/assets/images/emoji/ok_woman_tone5.pngbin0 -> 1696 bytes
-rw-r--r--app/assets/images/emoji/older_man.pngbin0 -> 1253 bytes
-rw-r--r--app/assets/images/emoji/older_man_tone1.pngbin0 -> 1253 bytes
-rw-r--r--app/assets/images/emoji/older_man_tone2.pngbin0 -> 1253 bytes
-rw-r--r--app/assets/images/emoji/older_man_tone3.pngbin0 -> 1253 bytes
-rw-r--r--app/assets/images/emoji/older_man_tone4.pngbin0 -> 1254 bytes
-rw-r--r--app/assets/images/emoji/older_man_tone5.pngbin0 -> 1254 bytes
-rw-r--r--app/assets/images/emoji/older_woman.pngbin0 -> 1472 bytes
-rw-r--r--app/assets/images/emoji/older_woman_tone1.pngbin0 -> 1562 bytes
-rw-r--r--app/assets/images/emoji/older_woman_tone2.pngbin0 -> 1564 bytes
-rw-r--r--app/assets/images/emoji/older_woman_tone3.pngbin0 -> 1555 bytes
-rw-r--r--app/assets/images/emoji/older_woman_tone4.pngbin0 -> 1562 bytes
-rw-r--r--app/assets/images/emoji/older_woman_tone5.pngbin0 -> 1544 bytes
-rw-r--r--app/assets/images/emoji/om_symbol.pngbin0 -> 773 bytes
-rw-r--r--app/assets/images/emoji/on.pngbin0 -> 459 bytes
-rw-r--r--app/assets/images/emoji/oncoming_automobile.pngbin0 -> 1238 bytes
-rw-r--r--app/assets/images/emoji/oncoming_bus.pngbin0 -> 964 bytes
-rw-r--r--app/assets/images/emoji/oncoming_police_car.pngbin0 -> 1547 bytes
-rw-r--r--app/assets/images/emoji/oncoming_taxi.pngbin0 -> 1405 bytes
-rw-r--r--app/assets/images/emoji/one.pngbin0 -> 442 bytes
-rw-r--r--app/assets/images/emoji/open_file_folder.pngbin0 -> 755 bytes
-rw-r--r--app/assets/images/emoji/open_hands.pngbin0 -> 1053 bytes
-rw-r--r--app/assets/images/emoji/open_hands_tone1.pngbin0 -> 1053 bytes
-rw-r--r--app/assets/images/emoji/open_hands_tone2.pngbin0 -> 1053 bytes
-rw-r--r--app/assets/images/emoji/open_hands_tone3.pngbin0 -> 1053 bytes
-rw-r--r--app/assets/images/emoji/open_hands_tone4.pngbin0 -> 1053 bytes
-rw-r--r--app/assets/images/emoji/open_hands_tone5.pngbin0 -> 1053 bytes
-rw-r--r--app/assets/images/emoji/open_mouth.pngbin0 -> 575 bytes
-rw-r--r--app/assets/images/emoji/ophiuchus.pngbin0 -> 723 bytes
-rw-r--r--app/assets/images/emoji/orange_book.pngbin0 -> 1329 bytes
-rw-r--r--app/assets/images/emoji/orthodox_cross.pngbin0 -> 239 bytes
-rw-r--r--app/assets/images/emoji/outbox_tray.pngbin0 -> 1002 bytes
-rw-r--r--app/assets/images/emoji/owl.pngbin0 -> 2045 bytes
-rw-r--r--app/assets/images/emoji/ox.pngbin0 -> 1436 bytes
-rw-r--r--app/assets/images/emoji/package.pngbin0 -> 950 bytes
-rw-r--r--app/assets/images/emoji/page_facing_up.pngbin0 -> 1110 bytes
-rw-r--r--app/assets/images/emoji/page_with_curl.pngbin0 -> 1157 bytes
-rw-r--r--app/assets/images/emoji/pager.pngbin0 -> 553 bytes
-rw-r--r--app/assets/images/emoji/paintbrush.pngbin0 -> 950 bytes
-rw-r--r--app/assets/images/emoji/palm_tree.pngbin0 -> 1450 bytes
-rw-r--r--app/assets/images/emoji/pancakes.pngbin0 -> 3661 bytes
-rw-r--r--app/assets/images/emoji/panda_face.pngbin0 -> 1478 bytes
-rw-r--r--app/assets/images/emoji/paperclip.pngbin0 -> 439 bytes
-rw-r--r--app/assets/images/emoji/paperclips.pngbin0 -> 642 bytes
-rw-r--r--app/assets/images/emoji/park.pngbin0 -> 929 bytes
-rw-r--r--app/assets/images/emoji/parking.pngbin0 -> 385 bytes
-rw-r--r--app/assets/images/emoji/part_alternation_mark.pngbin0 -> 521 bytes
-rw-r--r--app/assets/images/emoji/partly_sunny.pngbin0 -> 977 bytes
-rw-r--r--app/assets/images/emoji/passport_control.pngbin0 -> 683 bytes
-rw-r--r--app/assets/images/emoji/pause_button.pngbin0 -> 395 bytes
-rw-r--r--app/assets/images/emoji/peace.pngbin0 -> 933 bytes
-rw-r--r--app/assets/images/emoji/peach.pngbin0 -> 1189 bytes
-rw-r--r--app/assets/images/emoji/peanuts.pngbin0 -> 3266 bytes
-rw-r--r--app/assets/images/emoji/pear.pngbin0 -> 747 bytes
-rw-r--r--app/assets/images/emoji/pen_ballpoint.pngbin0 -> 696 bytes
-rw-r--r--app/assets/images/emoji/pen_fountain.pngbin0 -> 623 bytes
-rw-r--r--app/assets/images/emoji/pencil.pngbin0 -> 1624 bytes
-rw-r--r--app/assets/images/emoji/pencil2.pngbin0 -> 654 bytes
-rw-r--r--app/assets/images/emoji/penguin.pngbin0 -> 1034 bytes
-rw-r--r--app/assets/images/emoji/pensive.pngbin0 -> 718 bytes
-rw-r--r--app/assets/images/emoji/performing_arts.pngbin0 -> 1971 bytes
-rw-r--r--app/assets/images/emoji/persevere.pngbin0 -> 891 bytes
-rw-r--r--app/assets/images/emoji/person_frowning.pngbin0 -> 1148 bytes
-rw-r--r--app/assets/images/emoji/person_frowning_tone1.pngbin0 -> 1141 bytes
-rw-r--r--app/assets/images/emoji/person_frowning_tone2.pngbin0 -> 1141 bytes
-rw-r--r--app/assets/images/emoji/person_frowning_tone3.pngbin0 -> 1141 bytes
-rw-r--r--app/assets/images/emoji/person_frowning_tone4.pngbin0 -> 1109 bytes
-rw-r--r--app/assets/images/emoji/person_frowning_tone5.pngbin0 -> 1114 bytes
-rw-r--r--app/assets/images/emoji/person_with_blond_hair.pngbin0 -> 1205 bytes
-rw-r--r--app/assets/images/emoji/person_with_blond_hair_tone1.pngbin0 -> 1181 bytes
-rw-r--r--app/assets/images/emoji/person_with_blond_hair_tone2.pngbin0 -> 1181 bytes
-rw-r--r--app/assets/images/emoji/person_with_blond_hair_tone3.pngbin0 -> 1181 bytes
-rw-r--r--app/assets/images/emoji/person_with_blond_hair_tone4.pngbin0 -> 1189 bytes
-rw-r--r--app/assets/images/emoji/person_with_blond_hair_tone5.pngbin0 -> 1214 bytes
-rw-r--r--app/assets/images/emoji/person_with_pouting_face.pngbin0 -> 1297 bytes
-rw-r--r--app/assets/images/emoji/person_with_pouting_face_tone1.pngbin0 -> 1309 bytes
-rw-r--r--app/assets/images/emoji/person_with_pouting_face_tone2.pngbin0 -> 1292 bytes
-rw-r--r--app/assets/images/emoji/person_with_pouting_face_tone3.pngbin0 -> 1305 bytes
-rw-r--r--app/assets/images/emoji/person_with_pouting_face_tone4.pngbin0 -> 1296 bytes
-rw-r--r--app/assets/images/emoji/person_with_pouting_face_tone5.pngbin0 -> 1303 bytes
-rw-r--r--app/assets/images/emoji/pick.pngbin0 -> 1023 bytes
-rw-r--r--app/assets/images/emoji/pig.pngbin0 -> 1138 bytes
-rw-r--r--app/assets/images/emoji/pig2.pngbin0 -> 1548 bytes
-rw-r--r--app/assets/images/emoji/pig_nose.pngbin0 -> 820 bytes
-rw-r--r--app/assets/images/emoji/pill.pngbin0 -> 442 bytes
-rw-r--r--app/assets/images/emoji/pineapple.pngbin0 -> 1642 bytes
-rw-r--r--app/assets/images/emoji/ping_pong.pngbin0 -> 823 bytes
-rw-r--r--app/assets/images/emoji/pisces.pngbin0 -> 678 bytes
-rw-r--r--app/assets/images/emoji/pizza.pngbin0 -> 2008 bytes
-rw-r--r--app/assets/images/emoji/place_of_worship.pngbin0 -> 487 bytes
-rw-r--r--app/assets/images/emoji/play_pause.pngbin0 -> 509 bytes
-rw-r--r--app/assets/images/emoji/point_down.pngbin0 -> 853 bytes
-rw-r--r--app/assets/images/emoji/point_down_tone1.pngbin0 -> 856 bytes
-rw-r--r--app/assets/images/emoji/point_down_tone2.pngbin0 -> 856 bytes
-rw-r--r--app/assets/images/emoji/point_down_tone3.pngbin0 -> 858 bytes
-rw-r--r--app/assets/images/emoji/point_down_tone4.pngbin0 -> 856 bytes
-rw-r--r--app/assets/images/emoji/point_down_tone5.pngbin0 -> 856 bytes
-rw-r--r--app/assets/images/emoji/point_left.pngbin0 -> 825 bytes
-rw-r--r--app/assets/images/emoji/point_left_tone1.pngbin0 -> 832 bytes
-rw-r--r--app/assets/images/emoji/point_left_tone2.pngbin0 -> 830 bytes
-rw-r--r--app/assets/images/emoji/point_left_tone3.pngbin0 -> 830 bytes
-rw-r--r--app/assets/images/emoji/point_left_tone4.pngbin0 -> 830 bytes
-rw-r--r--app/assets/images/emoji/point_left_tone5.pngbin0 -> 832 bytes
-rw-r--r--app/assets/images/emoji/point_right.pngbin0 -> 805 bytes
-rw-r--r--app/assets/images/emoji/point_right_tone1.pngbin0 -> 805 bytes
-rw-r--r--app/assets/images/emoji/point_right_tone2.pngbin0 -> 805 bytes
-rw-r--r--app/assets/images/emoji/point_right_tone3.pngbin0 -> 805 bytes
-rw-r--r--app/assets/images/emoji/point_right_tone4.pngbin0 -> 805 bytes
-rw-r--r--app/assets/images/emoji/point_right_tone5.pngbin0 -> 805 bytes
-rw-r--r--app/assets/images/emoji/point_up.pngbin0 -> 819 bytes
-rw-r--r--app/assets/images/emoji/point_up_2.pngbin0 -> 822 bytes
-rw-r--r--app/assets/images/emoji/point_up_2_tone1.pngbin0 -> 822 bytes
-rw-r--r--app/assets/images/emoji/point_up_2_tone2.pngbin0 -> 822 bytes
-rw-r--r--app/assets/images/emoji/point_up_2_tone3.pngbin0 -> 871 bytes
-rw-r--r--app/assets/images/emoji/point_up_2_tone4.pngbin0 -> 822 bytes
-rw-r--r--app/assets/images/emoji/point_up_2_tone5.pngbin0 -> 822 bytes
-rw-r--r--app/assets/images/emoji/point_up_tone1.pngbin0 -> 820 bytes
-rw-r--r--app/assets/images/emoji/point_up_tone2.pngbin0 -> 820 bytes
-rw-r--r--app/assets/images/emoji/point_up_tone3.pngbin0 -> 820 bytes
-rw-r--r--app/assets/images/emoji/point_up_tone4.pngbin0 -> 820 bytes
-rw-r--r--app/assets/images/emoji/point_up_tone5.pngbin0 -> 820 bytes
-rw-r--r--app/assets/images/emoji/police_car.pngbin0 -> 1431 bytes
-rw-r--r--app/assets/images/emoji/poodle.pngbin0 -> 1531 bytes
-rw-r--r--app/assets/images/emoji/poop.pngbin0 -> 1273 bytes
-rw-r--r--app/assets/images/emoji/popcorn.pngbin0 -> 1843 bytes
-rw-r--r--app/assets/images/emoji/post_office.pngbin0 -> 676 bytes
-rw-r--r--app/assets/images/emoji/postal_horn.pngbin0 -> 809 bytes
-rw-r--r--app/assets/images/emoji/postbox.pngbin0 -> 1077 bytes
-rw-r--r--app/assets/images/emoji/potable_water.pngbin0 -> 633 bytes
-rw-r--r--app/assets/images/emoji/potato.pngbin0 -> 1246 bytes
-rw-r--r--app/assets/images/emoji/pouch.pngbin0 -> 1259 bytes
-rw-r--r--app/assets/images/emoji/poultry_leg.pngbin0 -> 925 bytes
-rw-r--r--app/assets/images/emoji/pound.pngbin0 -> 452 bytes
-rw-r--r--app/assets/images/emoji/pouting_cat.pngbin0 -> 1675 bytes
-rw-r--r--app/assets/images/emoji/pray.pngbin0 -> 1122 bytes
-rw-r--r--app/assets/images/emoji/pray_tone1.pngbin0 -> 1131 bytes
-rw-r--r--app/assets/images/emoji/pray_tone2.pngbin0 -> 1134 bytes
-rw-r--r--app/assets/images/emoji/pray_tone3.pngbin0 -> 1137 bytes
-rw-r--r--app/assets/images/emoji/pray_tone4.pngbin0 -> 1126 bytes
-rw-r--r--app/assets/images/emoji/pray_tone5.pngbin0 -> 1117 bytes
-rw-r--r--app/assets/images/emoji/prayer_beads.pngbin0 -> 1059 bytes
-rw-r--r--app/assets/images/emoji/pregnant_woman.pngbin0 -> 1252 bytes
-rw-r--r--app/assets/images/emoji/pregnant_woman_tone1.pngbin0 -> 1255 bytes
-rw-r--r--app/assets/images/emoji/pregnant_woman_tone2.pngbin0 -> 1246 bytes
-rw-r--r--app/assets/images/emoji/pregnant_woman_tone3.pngbin0 -> 1237 bytes
-rw-r--r--app/assets/images/emoji/pregnant_woman_tone4.pngbin0 -> 1246 bytes
-rw-r--r--app/assets/images/emoji/pregnant_woman_tone5.pngbin0 -> 1235 bytes
-rw-r--r--app/assets/images/emoji/prince.pngbin0 -> 1616 bytes
-rw-r--r--app/assets/images/emoji/prince_tone1.pngbin0 -> 1618 bytes
-rw-r--r--app/assets/images/emoji/prince_tone2.pngbin0 -> 1621 bytes
-rw-r--r--app/assets/images/emoji/prince_tone3.pngbin0 -> 1619 bytes
-rw-r--r--app/assets/images/emoji/prince_tone4.pngbin0 -> 1619 bytes
-rw-r--r--app/assets/images/emoji/prince_tone5.pngbin0 -> 1616 bytes
-rw-r--r--app/assets/images/emoji/princess.pngbin0 -> 1812 bytes
-rw-r--r--app/assets/images/emoji/princess_tone1.pngbin0 -> 1812 bytes
-rw-r--r--app/assets/images/emoji/princess_tone2.pngbin0 -> 1805 bytes
-rw-r--r--app/assets/images/emoji/princess_tone3.pngbin0 -> 1805 bytes
-rw-r--r--app/assets/images/emoji/princess_tone4.pngbin0 -> 1813 bytes
-rw-r--r--app/assets/images/emoji/princess_tone5.pngbin0 -> 1812 bytes
-rw-r--r--app/assets/images/emoji/printer.pngbin0 -> 926 bytes
-rw-r--r--app/assets/images/emoji/projector.pngbin0 -> 943 bytes
-rw-r--r--app/assets/images/emoji/punch.pngbin0 -> 838 bytes
-rw-r--r--app/assets/images/emoji/punch_tone1.pngbin0 -> 838 bytes
-rw-r--r--app/assets/images/emoji/punch_tone2.pngbin0 -> 838 bytes
-rw-r--r--app/assets/images/emoji/punch_tone3.pngbin0 -> 838 bytes
-rw-r--r--app/assets/images/emoji/punch_tone4.pngbin0 -> 838 bytes
-rw-r--r--app/assets/images/emoji/punch_tone5.pngbin0 -> 838 bytes
-rw-r--r--app/assets/images/emoji/purple_heart.pngbin0 -> 435 bytes
-rw-r--r--app/assets/images/emoji/purse.pngbin0 -> 1558 bytes
-rw-r--r--app/assets/images/emoji/pushpin.pngbin0 -> 640 bytes
-rw-r--r--app/assets/images/emoji/put_litter_in_its_place.pngbin0 -> 650 bytes
-rw-r--r--app/assets/images/emoji/question.pngbin0 -> 449 bytes
-rw-r--r--app/assets/images/emoji/rabbit.pngbin0 -> 1660 bytes
-rw-r--r--app/assets/images/emoji/rabbit2.pngbin0 -> 1805 bytes
-rw-r--r--app/assets/images/emoji/race_car.pngbin0 -> 2140 bytes
-rw-r--r--app/assets/images/emoji/racehorse.pngbin0 -> 1401 bytes
-rw-r--r--app/assets/images/emoji/radio.pngbin0 -> 851 bytes
-rw-r--r--app/assets/images/emoji/radio_button.pngbin0 -> 674 bytes
-rw-r--r--app/assets/images/emoji/radioactive.pngbin0 -> 858 bytes
-rw-r--r--app/assets/images/emoji/rage.pngbin0 -> 845 bytes
-rw-r--r--app/assets/images/emoji/railway_car.pngbin0 -> 847 bytes
-rw-r--r--app/assets/images/emoji/railway_track.pngbin0 -> 1550 bytes
-rw-r--r--app/assets/images/emoji/rainbow.pngbin0 -> 1299 bytes
-rw-r--r--app/assets/images/emoji/raised_back_of_hand.pngbin0 -> 848 bytes
-rw-r--r--app/assets/images/emoji/raised_back_of_hand_tone1.pngbin0 -> 848 bytes
-rw-r--r--app/assets/images/emoji/raised_back_of_hand_tone2.pngbin0 -> 848 bytes
-rw-r--r--app/assets/images/emoji/raised_back_of_hand_tone3.pngbin0 -> 848 bytes
-rw-r--r--app/assets/images/emoji/raised_back_of_hand_tone4.pngbin0 -> 848 bytes
-rw-r--r--app/assets/images/emoji/raised_back_of_hand_tone5.pngbin0 -> 848 bytes
-rw-r--r--app/assets/images/emoji/raised_hand.pngbin0 -> 791 bytes
-rw-r--r--app/assets/images/emoji/raised_hand_tone1.pngbin0 -> 791 bytes
-rw-r--r--app/assets/images/emoji/raised_hand_tone2.pngbin0 -> 791 bytes
-rw-r--r--app/assets/images/emoji/raised_hand_tone3.pngbin0 -> 791 bytes
-rw-r--r--app/assets/images/emoji/raised_hand_tone4.pngbin0 -> 791 bytes
-rw-r--r--app/assets/images/emoji/raised_hand_tone5.pngbin0 -> 791 bytes
-rw-r--r--app/assets/images/emoji/raised_hands.pngbin0 -> 1098 bytes
-rw-r--r--app/assets/images/emoji/raised_hands_tone1.pngbin0 -> 1098 bytes
-rw-r--r--app/assets/images/emoji/raised_hands_tone2.pngbin0 -> 1098 bytes
-rw-r--r--app/assets/images/emoji/raised_hands_tone3.pngbin0 -> 1098 bytes
-rw-r--r--app/assets/images/emoji/raised_hands_tone4.pngbin0 -> 1098 bytes
-rw-r--r--app/assets/images/emoji/raised_hands_tone5.pngbin0 -> 1098 bytes
-rw-r--r--app/assets/images/emoji/raising_hand.pngbin0 -> 1664 bytes
-rw-r--r--app/assets/images/emoji/raising_hand_tone1.pngbin0 -> 1678 bytes
-rw-r--r--app/assets/images/emoji/raising_hand_tone2.pngbin0 -> 1665 bytes
-rw-r--r--app/assets/images/emoji/raising_hand_tone3.pngbin0 -> 1657 bytes
-rw-r--r--app/assets/images/emoji/raising_hand_tone4.pngbin0 -> 1657 bytes
-rw-r--r--app/assets/images/emoji/raising_hand_tone5.pngbin0 -> 1661 bytes
-rw-r--r--app/assets/images/emoji/ram.pngbin0 -> 1951 bytes
-rw-r--r--app/assets/images/emoji/ramen.pngbin0 -> 1992 bytes
-rw-r--r--app/assets/images/emoji/rat.pngbin0 -> 1193 bytes
-rw-r--r--app/assets/images/emoji/record_button.pngbin0 -> 475 bytes
-rw-r--r--app/assets/images/emoji/recycle.pngbin0 -> 914 bytes
-rw-r--r--app/assets/images/emoji/red_car.pngbin0 -> 1065 bytes
-rw-r--r--app/assets/images/emoji/red_circle.pngbin0 -> 374 bytes
-rw-r--r--app/assets/images/emoji/registered.pngbin0 -> 547 bytes
-rw-r--r--app/assets/images/emoji/relaxed.pngbin0 -> 636 bytes
-rw-r--r--app/assets/images/emoji/relieved.pngbin0 -> 785 bytes
-rw-r--r--app/assets/images/emoji/reminder_ribbon.pngbin0 -> 921 bytes
-rw-r--r--app/assets/images/emoji/repeat.pngbin0 -> 644 bytes
-rw-r--r--app/assets/images/emoji/repeat_one.pngbin0 -> 688 bytes
-rw-r--r--app/assets/images/emoji/restroom.pngbin0 -> 676 bytes
-rw-r--r--app/assets/images/emoji/revolving_hearts.pngbin0 -> 920 bytes
-rw-r--r--app/assets/images/emoji/rewind.pngbin0 -> 523 bytes
-rw-r--r--app/assets/images/emoji/rhino.pngbin0 -> 1558 bytes
-rw-r--r--app/assets/images/emoji/ribbon.pngbin0 -> 968 bytes
-rw-r--r--app/assets/images/emoji/rice.pngbin0 -> 1195 bytes
-rw-r--r--app/assets/images/emoji/rice_ball.pngbin0 -> 1091 bytes
-rw-r--r--app/assets/images/emoji/rice_cracker.pngbin0 -> 1443 bytes
-rw-r--r--app/assets/images/emoji/rice_scene.pngbin0 -> 1349 bytes
-rw-r--r--app/assets/images/emoji/right_facing_fist.pngbin0 -> 975 bytes
-rw-r--r--app/assets/images/emoji/right_facing_fist_tone1.pngbin0 -> 964 bytes
-rw-r--r--app/assets/images/emoji/right_facing_fist_tone2.pngbin0 -> 964 bytes
-rw-r--r--app/assets/images/emoji/right_facing_fist_tone3.pngbin0 -> 964 bytes
-rw-r--r--app/assets/images/emoji/right_facing_fist_tone4.pngbin0 -> 964 bytes
-rw-r--r--app/assets/images/emoji/right_facing_fist_tone5.pngbin0 -> 964 bytes
-rw-r--r--app/assets/images/emoji/ring.pngbin0 -> 1113 bytes
-rw-r--r--app/assets/images/emoji/robot.pngbin0 -> 1228 bytes
-rw-r--r--app/assets/images/emoji/rocket.pngbin0 -> 1639 bytes
-rw-r--r--app/assets/images/emoji/rofl.pngbin0 -> 1760 bytes
-rw-r--r--app/assets/images/emoji/roller_coaster.pngbin0 -> 1723 bytes
-rw-r--r--app/assets/images/emoji/rolling_eyes.pngbin0 -> 743 bytes
-rw-r--r--app/assets/images/emoji/rooster.pngbin0 -> 1333 bytes
-rw-r--r--app/assets/images/emoji/rose.pngbin0 -> 1182 bytes
-rw-r--r--app/assets/images/emoji/rosette.pngbin0 -> 1023 bytes
-rw-r--r--app/assets/images/emoji/rotating_light.pngbin0 -> 1969 bytes
-rw-r--r--app/assets/images/emoji/round_pushpin.pngbin0 -> 455 bytes
-rw-r--r--app/assets/images/emoji/rowboat.pngbin0 -> 1963 bytes
-rw-r--r--app/assets/images/emoji/rowboat_tone1.pngbin0 -> 1971 bytes
-rw-r--r--app/assets/images/emoji/rowboat_tone2.pngbin0 -> 1972 bytes
-rw-r--r--app/assets/images/emoji/rowboat_tone3.pngbin0 -> 1967 bytes
-rw-r--r--app/assets/images/emoji/rowboat_tone4.pngbin0 -> 1974 bytes
-rw-r--r--app/assets/images/emoji/rowboat_tone5.pngbin0 -> 1971 bytes
-rw-r--r--app/assets/images/emoji/rugby_football.pngbin0 -> 1618 bytes
-rw-r--r--app/assets/images/emoji/runner.pngbin0 -> 1161 bytes
-rw-r--r--app/assets/images/emoji/runner_tone1.pngbin0 -> 1163 bytes
-rw-r--r--app/assets/images/emoji/runner_tone2.pngbin0 -> 1162 bytes
-rw-r--r--app/assets/images/emoji/runner_tone3.pngbin0 -> 1151 bytes
-rw-r--r--app/assets/images/emoji/runner_tone4.pngbin0 -> 1156 bytes
-rw-r--r--app/assets/images/emoji/runner_tone5.pngbin0 -> 1145 bytes
-rw-r--r--app/assets/images/emoji/running_shirt_with_sash.pngbin0 -> 784 bytes
-rw-r--r--app/assets/images/emoji/sa.pngbin0 -> 420 bytes
-rw-r--r--app/assets/images/emoji/sagittarius.pngbin0 -> 602 bytes
-rw-r--r--app/assets/images/emoji/sailboat.pngbin0 -> 1274 bytes
-rw-r--r--app/assets/images/emoji/sake.pngbin0 -> 826 bytes
-rw-r--r--app/assets/images/emoji/salad.pngbin0 -> 2398 bytes
-rw-r--r--app/assets/images/emoji/sandal.pngbin0 -> 1180 bytes
-rw-r--r--app/assets/images/emoji/santa.pngbin0 -> 1585 bytes
-rw-r--r--app/assets/images/emoji/santa_tone1.pngbin0 -> 1585 bytes
-rw-r--r--app/assets/images/emoji/santa_tone2.pngbin0 -> 1578 bytes
-rw-r--r--app/assets/images/emoji/santa_tone3.pngbin0 -> 1578 bytes
-rw-r--r--app/assets/images/emoji/santa_tone4.pngbin0 -> 1578 bytes
-rw-r--r--app/assets/images/emoji/santa_tone5.pngbin0 -> 1578 bytes
-rw-r--r--app/assets/images/emoji/satellite.pngbin0 -> 1173 bytes
-rw-r--r--app/assets/images/emoji/satellite_orbital.pngbin0 -> 762 bytes
-rw-r--r--app/assets/images/emoji/saxophone.pngbin0 -> 1442 bytes
-rw-r--r--app/assets/images/emoji/scales.pngbin0 -> 1181 bytes
-rw-r--r--app/assets/images/emoji/school.pngbin0 -> 1234 bytes
-rw-r--r--app/assets/images/emoji/school_satchel.pngbin0 -> 1490 bytes
-rw-r--r--app/assets/images/emoji/scissors.pngbin0 -> 937 bytes
-rw-r--r--app/assets/images/emoji/scooter.pngbin0 -> 1228 bytes
-rw-r--r--app/assets/images/emoji/scorpion.pngbin0 -> 1503 bytes
-rw-r--r--app/assets/images/emoji/scorpius.pngbin0 -> 612 bytes
-rw-r--r--app/assets/images/emoji/scream.pngbin0 -> 1583 bytes
-rw-r--r--app/assets/images/emoji/scream_cat.pngbin0 -> 2120 bytes
-rw-r--r--app/assets/images/emoji/scroll.pngbin0 -> 989 bytes
-rw-r--r--app/assets/images/emoji/seat.pngbin0 -> 884 bytes
-rw-r--r--app/assets/images/emoji/second_place.pngbin0 -> 1511 bytes
-rw-r--r--app/assets/images/emoji/secret.pngbin0 -> 857 bytes
-rw-r--r--app/assets/images/emoji/see_no_evil.pngbin0 -> 1227 bytes
-rw-r--r--app/assets/images/emoji/seedling.pngbin0 -> 749 bytes
-rw-r--r--app/assets/images/emoji/selfie.pngbin0 -> 1160 bytes
-rw-r--r--app/assets/images/emoji/selfie_tone1.pngbin0 -> 1166 bytes
-rw-r--r--app/assets/images/emoji/selfie_tone2.pngbin0 -> 1167 bytes
-rw-r--r--app/assets/images/emoji/selfie_tone3.pngbin0 -> 1154 bytes
-rw-r--r--app/assets/images/emoji/selfie_tone4.pngbin0 -> 1153 bytes
-rw-r--r--app/assets/images/emoji/selfie_tone5.pngbin0 -> 1148 bytes
-rw-r--r--app/assets/images/emoji/seven.pngbin0 -> 522 bytes
-rw-r--r--app/assets/images/emoji/shallow_pan_of_food.pngbin0 -> 1738 bytes
-rw-r--r--app/assets/images/emoji/shamrock.pngbin0 -> 1023 bytes
-rw-r--r--app/assets/images/emoji/shark.pngbin0 -> 1811 bytes
-rw-r--r--app/assets/images/emoji/shaved_ice.pngbin0 -> 997 bytes
-rw-r--r--app/assets/images/emoji/sheep.pngbin0 -> 1372 bytes
-rw-r--r--app/assets/images/emoji/shell.pngbin0 -> 1497 bytes
-rw-r--r--app/assets/images/emoji/shield.pngbin0 -> 1602 bytes
-rw-r--r--app/assets/images/emoji/shinto_shrine.pngbin0 -> 579 bytes
-rw-r--r--app/assets/images/emoji/ship.pngbin0 -> 1405 bytes
-rw-r--r--app/assets/images/emoji/shirt.pngbin0 -> 670 bytes
-rw-r--r--app/assets/images/emoji/shopping_bags.pngbin0 -> 1234 bytes
-rw-r--r--app/assets/images/emoji/shopping_cart.pngbin0 -> 1072 bytes
-rw-r--r--app/assets/images/emoji/shower.pngbin0 -> 2537 bytes
-rw-r--r--app/assets/images/emoji/shrimp.pngbin0 -> 1376 bytes
-rw-r--r--app/assets/images/emoji/shrug.pngbin0 -> 1671 bytes
-rw-r--r--app/assets/images/emoji/shrug_tone1.pngbin0 -> 1676 bytes
-rw-r--r--app/assets/images/emoji/shrug_tone2.pngbin0 -> 1671 bytes
-rw-r--r--app/assets/images/emoji/shrug_tone3.pngbin0 -> 1675 bytes
-rw-r--r--app/assets/images/emoji/shrug_tone4.pngbin0 -> 1641 bytes
-rw-r--r--app/assets/images/emoji/shrug_tone5.pngbin0 -> 1634 bytes
-rw-r--r--app/assets/images/emoji/signal_strength.pngbin0 -> 445 bytes
-rw-r--r--app/assets/images/emoji/six.pngbin0 -> 612 bytes
-rw-r--r--app/assets/images/emoji/six_pointed_star.pngbin0 -> 540 bytes
-rw-r--r--app/assets/images/emoji/ski.pngbin0 -> 1762 bytes
-rw-r--r--app/assets/images/emoji/skier.pngbin0 -> 1539 bytes
-rw-r--r--app/assets/images/emoji/skull.pngbin0 -> 628 bytes
-rw-r--r--app/assets/images/emoji/skull_crossbones.pngbin0 -> 726 bytes
-rw-r--r--app/assets/images/emoji/sleeping.pngbin0 -> 1075 bytes
-rw-r--r--app/assets/images/emoji/sleeping_accommodation.pngbin0 -> 926 bytes
-rw-r--r--app/assets/images/emoji/sleepy.pngbin0 -> 1185 bytes
-rw-r--r--app/assets/images/emoji/slight_frown.pngbin0 -> 580 bytes
-rw-r--r--app/assets/images/emoji/slight_smile.pngbin0 -> 600 bytes
-rw-r--r--app/assets/images/emoji/slot_machine.pngbin0 -> 1648 bytes
-rw-r--r--app/assets/images/emoji/small_blue_diamond.pngbin0 -> 191 bytes
-rw-r--r--app/assets/images/emoji/small_orange_diamond.pngbin0 -> 194 bytes
-rw-r--r--app/assets/images/emoji/small_red_triangle.pngbin0 -> 273 bytes
-rw-r--r--app/assets/images/emoji/small_red_triangle_down.pngbin0 -> 291 bytes
-rw-r--r--app/assets/images/emoji/smile.pngbin0 -> 737 bytes
-rw-r--r--app/assets/images/emoji/smile_cat.pngbin0 -> 1405 bytes
-rw-r--r--app/assets/images/emoji/smiley.pngbin0 -> 686 bytes
-rw-r--r--app/assets/images/emoji/smiley_cat.pngbin0 -> 1669 bytes
-rw-r--r--app/assets/images/emoji/smiling_imp.pngbin0 -> 1078 bytes
-rw-r--r--app/assets/images/emoji/smirk.pngbin0 -> 775 bytes
-rw-r--r--app/assets/images/emoji/smirk_cat.pngbin0 -> 1663 bytes
-rw-r--r--app/assets/images/emoji/smoking.pngbin0 -> 417 bytes
-rw-r--r--app/assets/images/emoji/snail.pngbin0 -> 1731 bytes
-rw-r--r--app/assets/images/emoji/snake.pngbin0 -> 1575 bytes
-rw-r--r--app/assets/images/emoji/sneezing_face.pngbin0 -> 1289 bytes
-rw-r--r--app/assets/images/emoji/snowboarder.pngbin0 -> 2020 bytes
-rw-r--r--app/assets/images/emoji/snowflake.pngbin0 -> 691 bytes
-rw-r--r--app/assets/images/emoji/snowman.pngbin0 -> 1481 bytes
-rw-r--r--app/assets/images/emoji/snowman2.pngbin0 -> 2176 bytes
-rw-r--r--app/assets/images/emoji/sob.pngbin0 -> 1236 bytes
-rw-r--r--app/assets/images/emoji/soccer.pngbin0 -> 1034 bytes
-rw-r--r--app/assets/images/emoji/soon.pngbin0 -> 483 bytes
-rw-r--r--app/assets/images/emoji/sos.pngbin0 -> 604 bytes
-rw-r--r--app/assets/images/emoji/sound.pngbin0 -> 690 bytes
-rw-r--r--app/assets/images/emoji/space_invader.pngbin0 -> 1325 bytes
-rw-r--r--app/assets/images/emoji/spades.pngbin0 -> 454 bytes
-rw-r--r--app/assets/images/emoji/spaghetti.pngbin0 -> 1796 bytes
-rw-r--r--app/assets/images/emoji/sparkle.pngbin0 -> 663 bytes
-rw-r--r--app/assets/images/emoji/sparkler.pngbin0 -> 910 bytes
-rw-r--r--app/assets/images/emoji/sparkles.pngbin0 -> 651 bytes
-rw-r--r--app/assets/images/emoji/sparkling_heart.pngbin0 -> 821 bytes
-rw-r--r--app/assets/images/emoji/speak_no_evil.pngbin0 -> 1497 bytes
-rw-r--r--app/assets/images/emoji/speaker.pngbin0 -> 575 bytes
-rw-r--r--app/assets/images/emoji/speaking_head.pngbin0 -> 531 bytes
-rw-r--r--app/assets/images/emoji/speech_balloon.pngbin0 -> 384 bytes
-rw-r--r--app/assets/images/emoji/speedboat.pngbin0 -> 1255 bytes
-rw-r--r--app/assets/images/emoji/spider.pngbin0 -> 1724 bytes
-rw-r--r--app/assets/images/emoji/spider_web.pngbin0 -> 929 bytes
-rw-r--r--app/assets/images/emoji/spoon.pngbin0 -> 700 bytes
-rw-r--r--app/assets/images/emoji/spy.pngbin0 -> 1650 bytes
-rw-r--r--app/assets/images/emoji/spy_tone1.pngbin0 -> 1639 bytes
-rw-r--r--app/assets/images/emoji/spy_tone2.pngbin0 -> 1632 bytes
-rw-r--r--app/assets/images/emoji/spy_tone3.pngbin0 -> 1645 bytes
-rw-r--r--app/assets/images/emoji/spy_tone4.pngbin0 -> 1639 bytes
-rw-r--r--app/assets/images/emoji/spy_tone5.pngbin0 -> 1639 bytes
-rw-r--r--app/assets/images/emoji/squid.pngbin0 -> 1394 bytes
-rw-r--r--app/assets/images/emoji/stadium.pngbin0 -> 1515 bytes
-rw-r--r--app/assets/images/emoji/star.pngbin0 -> 456 bytes
-rw-r--r--app/assets/images/emoji/star2.pngbin0 -> 732 bytes
-rw-r--r--app/assets/images/emoji/star_and_crescent.pngbin0 -> 490 bytes
-rw-r--r--app/assets/images/emoji/star_of_david.pngbin0 -> 491 bytes
-rw-r--r--app/assets/images/emoji/stars.pngbin0 -> 1048 bytes
-rw-r--r--app/assets/images/emoji/station.pngbin0 -> 1336 bytes
-rw-r--r--app/assets/images/emoji/statue_of_liberty.pngbin0 -> 1145 bytes
-rw-r--r--app/assets/images/emoji/steam_locomotive.pngbin0 -> 1736 bytes
-rw-r--r--app/assets/images/emoji/stew.pngbin0 -> 1960 bytes
-rw-r--r--app/assets/images/emoji/stop_button.pngbin0 -> 385 bytes
-rw-r--r--app/assets/images/emoji/stopwatch.pngbin0 -> 1329 bytes
-rw-r--r--app/assets/images/emoji/straight_ruler.pngbin0 -> 1406 bytes
-rw-r--r--app/assets/images/emoji/strawberry.pngbin0 -> 1206 bytes
-rw-r--r--app/assets/images/emoji/stuck_out_tongue.pngbin0 -> 752 bytes
-rw-r--r--app/assets/images/emoji/stuck_out_tongue_closed_eyes.pngbin0 -> 867 bytes
-rw-r--r--app/assets/images/emoji/stuck_out_tongue_winking_eye.pngbin0 -> 1061 bytes
-rw-r--r--app/assets/images/emoji/stuffed_flatbread.pngbin0 -> 2160 bytes
-rw-r--r--app/assets/images/emoji/sun_with_face.pngbin0 -> 741 bytes
-rw-r--r--app/assets/images/emoji/sunflower.pngbin0 -> 1915 bytes
-rw-r--r--app/assets/images/emoji/sunglasses.pngbin0 -> 824 bytes
-rw-r--r--app/assets/images/emoji/sunny.pngbin0 -> 746 bytes
-rw-r--r--app/assets/images/emoji/sunrise.pngbin0 -> 812 bytes
-rw-r--r--app/assets/images/emoji/sunrise_over_mountains.pngbin0 -> 1576 bytes
-rw-r--r--app/assets/images/emoji/surfer.pngbin0 -> 1777 bytes
-rw-r--r--app/assets/images/emoji/surfer_tone1.pngbin0 -> 1781 bytes
-rw-r--r--app/assets/images/emoji/surfer_tone2.pngbin0 -> 1769 bytes
-rw-r--r--app/assets/images/emoji/surfer_tone3.pngbin0 -> 1777 bytes
-rw-r--r--app/assets/images/emoji/surfer_tone4.pngbin0 -> 1784 bytes
-rw-r--r--app/assets/images/emoji/surfer_tone5.pngbin0 -> 1782 bytes
-rw-r--r--app/assets/images/emoji/sushi.pngbin0 -> 2101 bytes
-rw-r--r--app/assets/images/emoji/suspension_railway.pngbin0 -> 927 bytes
-rw-r--r--app/assets/images/emoji/sweat.pngbin0 -> 861 bytes
-rw-r--r--app/assets/images/emoji/sweat_drops.pngbin0 -> 549 bytes
-rw-r--r--app/assets/images/emoji/sweat_smile.pngbin0 -> 851 bytes
-rw-r--r--app/assets/images/emoji/sweet_potato.pngbin0 -> 951 bytes
-rw-r--r--app/assets/images/emoji/swimmer.pngbin0 -> 1184 bytes
-rw-r--r--app/assets/images/emoji/swimmer_tone1.pngbin0 -> 1184 bytes
-rw-r--r--app/assets/images/emoji/swimmer_tone2.pngbin0 -> 1184 bytes
-rw-r--r--app/assets/images/emoji/swimmer_tone3.pngbin0 -> 1184 bytes
-rw-r--r--app/assets/images/emoji/swimmer_tone4.pngbin0 -> 1184 bytes
-rw-r--r--app/assets/images/emoji/swimmer_tone5.pngbin0 -> 1184 bytes
-rw-r--r--app/assets/images/emoji/symbols.pngbin0 -> 746 bytes
-rw-r--r--app/assets/images/emoji/synagogue.pngbin0 -> 1309 bytes
-rw-r--r--app/assets/images/emoji/syringe.pngbin0 -> 737 bytes
-rw-r--r--app/assets/images/emoji/taco.pngbin0 -> 3045 bytes
-rw-r--r--app/assets/images/emoji/tada.pngbin0 -> 1778 bytes
-rw-r--r--app/assets/images/emoji/tanabata_tree.pngbin0 -> 1479 bytes
-rw-r--r--app/assets/images/emoji/tangerine.pngbin0 -> 1184 bytes
-rw-r--r--app/assets/images/emoji/taurus.pngbin0 -> 701 bytes
-rw-r--r--app/assets/images/emoji/taxi.pngbin0 -> 1230 bytes
-rw-r--r--app/assets/images/emoji/tea.pngbin0 -> 1297 bytes
-rw-r--r--app/assets/images/emoji/telephone.pngbin0 -> 1760 bytes
-rw-r--r--app/assets/images/emoji/telephone_receiver.pngbin0 -> 941 bytes
-rw-r--r--app/assets/images/emoji/telescope.pngbin0 -> 1256 bytes
-rw-r--r--app/assets/images/emoji/ten.pngbin0 -> 621 bytes
-rw-r--r--app/assets/images/emoji/tennis.pngbin0 -> 1561 bytes
-rw-r--r--app/assets/images/emoji/tent.pngbin0 -> 1684 bytes
-rw-r--r--app/assets/images/emoji/thermometer.pngbin0 -> 759 bytes
-rw-r--r--app/assets/images/emoji/thermometer_face.pngbin0 -> 1503 bytes
-rw-r--r--app/assets/images/emoji/thinking.pngbin0 -> 1345 bytes
-rw-r--r--app/assets/images/emoji/third_place.pngbin0 -> 1529 bytes
-rw-r--r--app/assets/images/emoji/thought_balloon.pngbin0 -> 489 bytes
-rw-r--r--app/assets/images/emoji/three.pngbin0 -> 602 bytes
-rw-r--r--app/assets/images/emoji/thumbsdown.pngbin0 -> 815 bytes
-rw-r--r--app/assets/images/emoji/thumbsdown_tone1.pngbin0 -> 815 bytes
-rw-r--r--app/assets/images/emoji/thumbsdown_tone2.pngbin0 -> 815 bytes
-rw-r--r--app/assets/images/emoji/thumbsdown_tone3.pngbin0 -> 815 bytes
-rw-r--r--app/assets/images/emoji/thumbsdown_tone4.pngbin0 -> 815 bytes
-rw-r--r--app/assets/images/emoji/thumbsdown_tone5.pngbin0 -> 815 bytes
-rw-r--r--app/assets/images/emoji/thumbsup.pngbin0 -> 814 bytes
-rw-r--r--app/assets/images/emoji/thumbsup_tone1.pngbin0 -> 814 bytes
-rw-r--r--app/assets/images/emoji/thumbsup_tone2.pngbin0 -> 814 bytes
-rw-r--r--app/assets/images/emoji/thumbsup_tone3.pngbin0 -> 814 bytes
-rw-r--r--app/assets/images/emoji/thumbsup_tone4.pngbin0 -> 814 bytes
-rw-r--r--app/assets/images/emoji/thumbsup_tone5.pngbin0 -> 814 bytes
-rw-r--r--app/assets/images/emoji/thunder_cloud_rain.pngbin0 -> 1020 bytes
-rw-r--r--app/assets/images/emoji/ticket.pngbin0 -> 763 bytes
-rw-r--r--app/assets/images/emoji/tickets.pngbin0 -> 1750 bytes
-rw-r--r--app/assets/images/emoji/tiger.pngbin0 -> 2104 bytes
-rw-r--r--app/assets/images/emoji/tiger2.pngbin0 -> 2623 bytes
-rw-r--r--app/assets/images/emoji/timer.pngbin0 -> 1897 bytes
-rw-r--r--app/assets/images/emoji/tired_face.pngbin0 -> 1126 bytes
-rw-r--r--app/assets/images/emoji/tm.pngbin0 -> 300 bytes
-rw-r--r--app/assets/images/emoji/toilet.pngbin0 -> 726 bytes
-rw-r--r--app/assets/images/emoji/tokyo_tower.pngbin0 -> 765 bytes
-rw-r--r--app/assets/images/emoji/tomato.pngbin0 -> 1055 bytes
-rw-r--r--app/assets/images/emoji/tone1.pngbin0 -> 372 bytes
-rw-r--r--app/assets/images/emoji/tone2.pngbin0 -> 372 bytes
-rw-r--r--app/assets/images/emoji/tone3.pngbin0 -> 375 bytes
-rw-r--r--app/assets/images/emoji/tone4.pngbin0 -> 374 bytes
-rw-r--r--app/assets/images/emoji/tone5.pngbin0 -> 374 bytes
-rw-r--r--app/assets/images/emoji/tongue.pngbin0 -> 599 bytes
-rw-r--r--app/assets/images/emoji/tools.pngbin0 -> 1225 bytes
-rw-r--r--app/assets/images/emoji/top.pngbin0 -> 389 bytes
-rw-r--r--app/assets/images/emoji/tophat.pngbin0 -> 845 bytes
-rw-r--r--app/assets/images/emoji/track_next.pngbin0 -> 551 bytes
-rw-r--r--app/assets/images/emoji/track_previous.pngbin0 -> 549 bytes
-rw-r--r--app/assets/images/emoji/trackball.pngbin0 -> 892 bytes
-rw-r--r--app/assets/images/emoji/tractor.pngbin0 -> 1192 bytes
-rw-r--r--app/assets/images/emoji/traffic_light.pngbin0 -> 590 bytes
-rw-r--r--app/assets/images/emoji/train.pngbin0 -> 1031 bytes
-rw-r--r--app/assets/images/emoji/train2.pngbin0 -> 1499 bytes
-rw-r--r--app/assets/images/emoji/tram.pngbin0 -> 1065 bytes
-rw-r--r--app/assets/images/emoji/triangular_flag_on_post.pngbin0 -> 415 bytes
-rw-r--r--app/assets/images/emoji/triangular_ruler.pngbin0 -> 369 bytes
-rw-r--r--app/assets/images/emoji/trident.pngbin0 -> 668 bytes
-rw-r--r--app/assets/images/emoji/triumph.pngbin0 -> 1529 bytes
-rw-r--r--app/assets/images/emoji/trolleybus.pngbin0 -> 1168 bytes
-rw-r--r--app/assets/images/emoji/trophy.pngbin0 -> 863 bytes
-rw-r--r--app/assets/images/emoji/tropical_drink.pngbin0 -> 1428 bytes
-rw-r--r--app/assets/images/emoji/tropical_fish.pngbin0 -> 1676 bytes
-rw-r--r--app/assets/images/emoji/truck.pngbin0 -> 1366 bytes
-rw-r--r--app/assets/images/emoji/trumpet.pngbin0 -> 1281 bytes
-rw-r--r--app/assets/images/emoji/tulip.pngbin0 -> 1065 bytes
-rw-r--r--app/assets/images/emoji/tumbler_glass.pngbin0 -> 2312 bytes
-rw-r--r--app/assets/images/emoji/turkey.pngbin0 -> 1240 bytes
-rw-r--r--app/assets/images/emoji/turtle.pngbin0 -> 1515 bytes
-rw-r--r--app/assets/images/emoji/tv.pngbin0 -> 776 bytes
-rw-r--r--app/assets/images/emoji/twisted_rightwards_arrows.pngbin0 -> 574 bytes
-rw-r--r--app/assets/images/emoji/two.pngbin0 -> 567 bytes
-rw-r--r--app/assets/images/emoji/two_hearts.pngbin0 -> 493 bytes
-rw-r--r--app/assets/images/emoji/two_men_holding_hands.pngbin0 -> 1347 bytes
-rw-r--r--app/assets/images/emoji/two_women_holding_hands.pngbin0 -> 1544 bytes
-rw-r--r--app/assets/images/emoji/u5272.pngbin0 -> 411 bytes
-rw-r--r--app/assets/images/emoji/u5408.pngbin0 -> 484 bytes
-rw-r--r--app/assets/images/emoji/u55b6.pngbin0 -> 460 bytes
-rw-r--r--app/assets/images/emoji/u6307.pngbin0 -> 504 bytes
-rw-r--r--app/assets/images/emoji/u6708.pngbin0 -> 409 bytes
-rw-r--r--app/assets/images/emoji/u6709.pngbin0 -> 434 bytes
-rw-r--r--app/assets/images/emoji/u6e80.pngbin0 -> 564 bytes
-rw-r--r--app/assets/images/emoji/u7121.pngbin0 -> 534 bytes
-rw-r--r--app/assets/images/emoji/u7533.pngbin0 -> 306 bytes
-rw-r--r--app/assets/images/emoji/u7981.pngbin0 -> 584 bytes
-rw-r--r--app/assets/images/emoji/u7a7a.pngbin0 -> 456 bytes
-rw-r--r--app/assets/images/emoji/umbrella.pngbin0 -> 1229 bytes
-rw-r--r--app/assets/images/emoji/umbrella2.pngbin0 -> 897 bytes
-rw-r--r--app/assets/images/emoji/unamused.pngbin0 -> 632 bytes
-rw-r--r--app/assets/images/emoji/underage.pngbin0 -> 863 bytes
-rw-r--r--app/assets/images/emoji/unicorn.pngbin0 -> 2107 bytes
-rw-r--r--app/assets/images/emoji/unlock.pngbin0 -> 856 bytes
-rw-r--r--app/assets/images/emoji/up.pngbin0 -> 405 bytes
-rw-r--r--app/assets/images/emoji/upside_down.pngbin0 -> 602 bytes
-rw-r--r--app/assets/images/emoji/urn.pngbin0 -> 742 bytes
-rw-r--r--app/assets/images/emoji/v.pngbin0 -> 1009 bytes
-rw-r--r--app/assets/images/emoji/v_tone1.pngbin0 -> 1009 bytes
-rw-r--r--app/assets/images/emoji/v_tone2.pngbin0 -> 1009 bytes
-rw-r--r--app/assets/images/emoji/v_tone3.pngbin0 -> 1009 bytes
-rw-r--r--app/assets/images/emoji/v_tone4.pngbin0 -> 1009 bytes
-rw-r--r--app/assets/images/emoji/v_tone5.pngbin0 -> 1009 bytes
-rw-r--r--app/assets/images/emoji/vertical_traffic_light.pngbin0 -> 752 bytes
-rw-r--r--app/assets/images/emoji/vhs.pngbin0 -> 632 bytes
-rw-r--r--app/assets/images/emoji/vibration_mode.pngbin0 -> 683 bytes
-rw-r--r--app/assets/images/emoji/video_camera.pngbin0 -> 1611 bytes
-rw-r--r--app/assets/images/emoji/video_game.pngbin0 -> 765 bytes
-rw-r--r--app/assets/images/emoji/violin.pngbin0 -> 1156 bytes
-rw-r--r--app/assets/images/emoji/virgo.pngbin0 -> 618 bytes
-rw-r--r--app/assets/images/emoji/volcano.pngbin0 -> 1257 bytes
-rw-r--r--app/assets/images/emoji/volleyball.pngbin0 -> 1202 bytes
-rw-r--r--app/assets/images/emoji/vs.pngbin0 -> 604 bytes
-rw-r--r--app/assets/images/emoji/vulcan.pngbin0 -> 1083 bytes
-rw-r--r--app/assets/images/emoji/vulcan_tone1.pngbin0 -> 1083 bytes
-rw-r--r--app/assets/images/emoji/vulcan_tone2.pngbin0 -> 1083 bytes
-rw-r--r--app/assets/images/emoji/vulcan_tone3.pngbin0 -> 1083 bytes
-rw-r--r--app/assets/images/emoji/vulcan_tone4.pngbin0 -> 1083 bytes
-rw-r--r--app/assets/images/emoji/vulcan_tone5.pngbin0 -> 1083 bytes
-rw-r--r--app/assets/images/emoji/walking.pngbin0 -> 1082 bytes
-rw-r--r--app/assets/images/emoji/walking_tone1.pngbin0 -> 1084 bytes
-rw-r--r--app/assets/images/emoji/walking_tone2.pngbin0 -> 1084 bytes
-rw-r--r--app/assets/images/emoji/walking_tone3.pngbin0 -> 1066 bytes
-rw-r--r--app/assets/images/emoji/walking_tone4.pngbin0 -> 1075 bytes
-rw-r--r--app/assets/images/emoji/walking_tone5.pngbin0 -> 1065 bytes
-rw-r--r--app/assets/images/emoji/waning_crescent_moon.pngbin0 -> 1213 bytes
-rw-r--r--app/assets/images/emoji/waning_gibbous_moon.pngbin0 -> 1208 bytes
-rw-r--r--app/assets/images/emoji/warning.pngbin0 -> 565 bytes
-rw-r--r--app/assets/images/emoji/wastebasket.pngbin0 -> 2414 bytes
-rw-r--r--app/assets/images/emoji/watch.pngbin0 -> 785 bytes
-rw-r--r--app/assets/images/emoji/water_buffalo.pngbin0 -> 1536 bytes
-rw-r--r--app/assets/images/emoji/water_polo.pngbin0 -> 1755 bytes
-rw-r--r--app/assets/images/emoji/water_polo_tone1.pngbin0 -> 1758 bytes
-rw-r--r--app/assets/images/emoji/water_polo_tone2.pngbin0 -> 1756 bytes
-rw-r--r--app/assets/images/emoji/water_polo_tone3.pngbin0 -> 1760 bytes
-rw-r--r--app/assets/images/emoji/water_polo_tone4.pngbin0 -> 1749 bytes
-rw-r--r--app/assets/images/emoji/water_polo_tone5.pngbin0 -> 1748 bytes
-rw-r--r--app/assets/images/emoji/watermelon.pngbin0 -> 1275 bytes
-rw-r--r--app/assets/images/emoji/wave.pngbin0 -> 1300 bytes
-rw-r--r--app/assets/images/emoji/wave_tone1.pngbin0 -> 1300 bytes
-rw-r--r--app/assets/images/emoji/wave_tone2.pngbin0 -> 1300 bytes
-rw-r--r--app/assets/images/emoji/wave_tone3.pngbin0 -> 1295 bytes
-rw-r--r--app/assets/images/emoji/wave_tone4.pngbin0 -> 1300 bytes
-rw-r--r--app/assets/images/emoji/wave_tone5.pngbin0 -> 1300 bytes
-rw-r--r--app/assets/images/emoji/wavy_dash.pngbin0 -> 359 bytes
-rw-r--r--app/assets/images/emoji/waxing_crescent_moon.pngbin0 -> 1199 bytes
-rw-r--r--app/assets/images/emoji/waxing_gibbous_moon.pngbin0 -> 1229 bytes
-rw-r--r--app/assets/images/emoji/wc.pngbin0 -> 752 bytes
-rw-r--r--app/assets/images/emoji/weary.pngbin0 -> 871 bytes
-rw-r--r--app/assets/images/emoji/wedding.pngbin0 -> 1260 bytes
-rw-r--r--app/assets/images/emoji/whale.pngbin0 -> 1572 bytes
-rw-r--r--app/assets/images/emoji/whale2.pngbin0 -> 1196 bytes
-rw-r--r--app/assets/images/emoji/wheel_of_dharma.pngbin0 -> 666 bytes
-rw-r--r--app/assets/images/emoji/wheelchair.pngbin0 -> 683 bytes
-rw-r--r--app/assets/images/emoji/white_check_mark.pngbin0 -> 547 bytes
-rw-r--r--app/assets/images/emoji/white_circle.pngbin0 -> 351 bytes
-rw-r--r--app/assets/images/emoji/white_flower.pngbin0 -> 941 bytes
-rw-r--r--app/assets/images/emoji/white_large_square.pngbin0 -> 110 bytes
-rw-r--r--app/assets/images/emoji/white_medium_small_square.pngbin0 -> 110 bytes
-rw-r--r--app/assets/images/emoji/white_medium_square.pngbin0 -> 108 bytes
-rw-r--r--app/assets/images/emoji/white_small_square.pngbin0 -> 108 bytes
-rw-r--r--app/assets/images/emoji/white_square_button.pngbin0 -> 122 bytes
-rw-r--r--app/assets/images/emoji/white_sun_cloud.pngbin0 -> 968 bytes
-rw-r--r--app/assets/images/emoji/white_sun_rain_cloud.pngbin0 -> 1161 bytes
-rw-r--r--app/assets/images/emoji/white_sun_small_cloud.pngbin0 -> 989 bytes
-rw-r--r--app/assets/images/emoji/wilted_rose.pngbin0 -> 1349 bytes
-rw-r--r--app/assets/images/emoji/wind_blowing_face.pngbin0 -> 1827 bytes
-rw-r--r--app/assets/images/emoji/wind_chime.pngbin0 -> 1046 bytes
-rw-r--r--app/assets/images/emoji/wine_glass.pngbin0 -> 655 bytes
-rw-r--r--app/assets/images/emoji/wink.pngbin0 -> 746 bytes
-rw-r--r--app/assets/images/emoji/wolf.pngbin0 -> 1528 bytes
-rw-r--r--app/assets/images/emoji/woman.pngbin0 -> 1212 bytes
-rw-r--r--app/assets/images/emoji/woman_tone1.pngbin0 -> 1212 bytes
-rw-r--r--app/assets/images/emoji/woman_tone2.pngbin0 -> 1212 bytes
-rw-r--r--app/assets/images/emoji/woman_tone3.pngbin0 -> 1202 bytes
-rw-r--r--app/assets/images/emoji/woman_tone4.pngbin0 -> 1195 bytes
-rw-r--r--app/assets/images/emoji/woman_tone5.pngbin0 -> 1202 bytes
-rw-r--r--app/assets/images/emoji/womans_clothes.pngbin0 -> 1042 bytes
-rw-r--r--app/assets/images/emoji/womans_hat.pngbin0 -> 1553 bytes
-rw-r--r--app/assets/images/emoji/womens.pngbin0 -> 577 bytes
-rw-r--r--app/assets/images/emoji/worried.pngbin0 -> 715 bytes
-rw-r--r--app/assets/images/emoji/wrench.pngbin0 -> 418 bytes
-rw-r--r--app/assets/images/emoji/wrestlers.pngbin0 -> 2556 bytes
-rw-r--r--app/assets/images/emoji/wrestlers_tone1.pngbin0 -> 2563 bytes
-rw-r--r--app/assets/images/emoji/wrestlers_tone2.pngbin0 -> 2553 bytes
-rw-r--r--app/assets/images/emoji/wrestlers_tone3.pngbin0 -> 2541 bytes
-rw-r--r--app/assets/images/emoji/wrestlers_tone4.pngbin0 -> 2553 bytes
-rw-r--r--app/assets/images/emoji/wrestlers_tone5.pngbin0 -> 2542 bytes
-rw-r--r--app/assets/images/emoji/writing_hand.pngbin0 -> 1001 bytes
-rw-r--r--app/assets/images/emoji/writing_hand_tone1.pngbin0 -> 988 bytes
-rw-r--r--app/assets/images/emoji/writing_hand_tone2.pngbin0 -> 987 bytes
-rw-r--r--app/assets/images/emoji/writing_hand_tone3.pngbin0 -> 977 bytes
-rw-r--r--app/assets/images/emoji/writing_hand_tone4.pngbin0 -> 973 bytes
-rw-r--r--app/assets/images/emoji/writing_hand_tone5.pngbin0 -> 970 bytes
-rw-r--r--app/assets/images/emoji/x.pngbin0 -> 298 bytes
-rw-r--r--app/assets/images/emoji/yellow_heart.pngbin0 -> 435 bytes
-rw-r--r--app/assets/images/emoji/yen.pngbin0 -> 421 bytes
-rw-r--r--app/assets/images/emoji/yin_yang.pngbin0 -> 776 bytes
-rw-r--r--app/assets/images/emoji/yum.pngbin0 -> 896 bytes
-rw-r--r--app/assets/images/emoji/zap.pngbin0 -> 413 bytes
-rw-r--r--app/assets/images/emoji/zero.pngbin0 -> 560 bytes
-rw-r--r--app/assets/images/emoji/zipper_mouth.pngbin0 -> 722 bytes
-rw-r--r--app/assets/images/emoji/zzz.pngbin0 -> 540 bytes
-rw-r--r--app/assets/images/emoji@2x.pngbin2652225 -> 2976505 bytes
-rw-r--r--app/assets/javascripts/awards_handler.js876
-rw-r--r--app/assets/javascripts/behaviors/gl_emoji.js217
-rw-r--r--app/assets/javascripts/behaviors/gl_emoji/spread_string.js50
-rw-r--r--app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js154
-rw-r--r--app/assets/javascripts/boards/components/board_list.js14
-rw-r--r--app/assets/javascripts/boards/models/issue.js5
-rw-r--r--app/assets/javascripts/boards/models/list.js25
-rw-r--r--app/assets/javascripts/boards/services/board_service.js6
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js6
-rw-r--r--app/assets/javascripts/copy_as_gfm.js3
-rw-r--r--app/assets/javascripts/dispatcher.js5
-rw-r--r--app/assets/javascripts/droplab/droplab_ajax.js6
-rw-r--r--app/assets/javascripts/extensions/string.js2
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_hint.js19
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_user.js7
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_utils.js72
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_bundle.js1
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown.js2
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js51
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js176
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js200
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js17
-rw-r--r--app/assets/javascripts/main.js19
-rw-r--r--app/assets/javascripts/monitoring/prometheus_graph.js333
-rw-r--r--app/assets/javascripts/shortcuts_navigation.js6
-rw-r--r--app/assets/javascripts/test_utils/simulate_drag.js29
-rw-r--r--app/assets/javascripts/users_select.js23
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/awards.scss10
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss1
-rw-r--r--app/assets/stylesheets/framework/emoji-sprites.scss1811
-rw-r--r--app/assets/stylesheets/framework/emojis.scss1813
-rw-r--r--app/assets/stylesheets/framework/filters.scss133
-rw-r--r--app/assets/stylesheets/framework/lists.scss38
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss7
-rw-r--r--app/assets/stylesheets/framework/mobile.scss3
-rw-r--r--app/assets/stylesheets/framework/panels.scss8
-rw-r--r--app/assets/stylesheets/framework/variables.scss11
-rw-r--r--app/assets/stylesheets/pages/commits.scss32
-rw-r--r--app/assets/stylesheets/pages/environments.scss68
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss77
-rw-r--r--app/assets/stylesheets/pages/notes.scss4
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss2
-rw-r--r--app/assets/stylesheets/pages/projects.scss29
-rw-r--r--app/assets/stylesheets/pages/settings.scss11
-rw-r--r--app/assets/stylesheets/pages/settings_ci_cd.scss12
-rw-r--r--app/assets/stylesheets/pages/tree.scss10
-rw-r--r--app/controllers/admin/applications_controller.rb2
-rw-r--r--app/controllers/admin/impersonation_tokens_controller.rb53
-rw-r--r--app/controllers/concerns/repository_settings_redirect.rb7
-rw-r--r--app/controllers/emojis_controller.rb6
-rw-r--r--app/controllers/oauth/authorizations_controller.rb44
-rw-r--r--app/controllers/profiles/personal_access_tokens_controller.rb17
-rw-r--r--app/controllers/projects/autocomplete_sources_controller.rb6
-rw-r--r--app/controllers/projects/boards/issues_controller.rb11
-rw-r--r--app/controllers/projects/deploy_keys_controller.rb30
-rw-r--r--app/controllers/projects/environments_controller.rb15
-rw-r--r--app/controllers/projects/protected_branches_controller.rb36
-rw-r--r--app/controllers/projects/settings/repository_controller.rb50
-rw-r--r--app/controllers/projects/triggers_controller.rb59
-rw-r--r--app/controllers/uploads_controller.rb2
-rw-r--r--app/finders/personal_access_tokens_finder.rb45
-rw-r--r--app/helpers/application_settings_helper.rb4
-rw-r--r--app/helpers/builds_helper.rb2
-rw-r--r--app/helpers/ci_status_helper.rb2
-rw-r--r--app/helpers/emoji_helper.rb5
-rw-r--r--app/helpers/events_helper.rb7
-rw-r--r--app/helpers/gitlab_routing_helper.rb4
-rw-r--r--app/helpers/issues_helper.rb28
-rw-r--r--app/helpers/preferences_helper.rb5
-rw-r--r--app/helpers/sorting_helper.rb2
-rw-r--r--app/models/chat_team.rb1
-rw-r--r--app/models/ci/build.rb58
-rw-r--r--app/models/ci/runner.rb13
-rw-r--r--app/models/ci/trigger.rb8
-rw-r--r--app/models/concerns/awardable.rb2
-rw-r--r--app/models/concerns/has_status.rb2
-rw-r--r--app/models/concerns/relative_positioning.rb101
-rw-r--r--app/models/environment.rb8
-rw-r--r--app/models/issue.rb1
-rw-r--r--app/models/oauth_access_grant.rb4
-rw-r--r--app/models/oauth_access_token.rb2
-rw-r--r--app/models/personal_access_token.rb26
-rw-r--r--app/models/project.rb11
-rw-r--r--app/models/project_services/kubernetes_service.rb2
-rw-r--r--app/models/project_services/monitoring_service.rb16
-rw-r--r--app/models/project_services/prometheus_service.rb93
-rw-r--r--app/models/repository.rb16
-rw-r--r--app/models/service.rb1
-rw-r--r--app/models/user.rb3
-rw-r--r--app/policies/ci/trigger_policy.rb13
-rw-r--r--app/presenters/projects/settings/deploy_keys_presenter.rb60
-rw-r--r--app/services/boards/issues/list_service.rb7
-rw-r--r--app/services/boards/issues/move_service.rb28
-rw-r--r--app/services/ci/register_job_service.rb (renamed from app/services/ci/register_build_service.rb)2
-rw-r--r--app/services/git_push_service.rb2
-rw-r--r--app/services/issuable_base_service.rb2
-rw-r--r--app/services/issues/create_service.rb1
-rw-r--r--app/services/issues/update_service.rb20
-rw-r--r--app/validators/namespace_validator.rb17
-rw-r--r--app/validators/project_path_validator.rb6
-rw-r--r--app/views/admin/abuse_reports/index.html.haml2
-rw-r--r--app/views/admin/impersonation_tokens/index.html.haml8
-rw-r--r--app/views/admin/users/_head.html.haml2
-rw-r--r--app/views/award_emoji/_awards_block.html.haml2
-rw-r--r--app/views/doorkeeper/authorizations/new.html.haml2
-rw-r--r--app/views/emojis/index.html.haml11
-rw-r--r--app/views/help/_shortcuts.html.haml12
-rw-r--r--app/views/layouts/_init_auto_complete.html.haml1
-rw-r--r--app/views/layouts/nav/_project.html.haml49
-rw-r--r--app/views/layouts/nav/_project_settings.html.haml28
-rw-r--r--app/views/profiles/personal_access_tokens/_form.html.haml21
-rw-r--r--app/views/profiles/personal_access_tokens/index.html.haml73
-rw-r--r--app/views/projects/blob/_blob.html.haml2
-rw-r--r--app/views/projects/boards/components/_board_list.html.haml5
-rw-r--r--app/views/projects/builds/show.html.haml1
-rw-r--r--app/views/projects/commit/_commit_box.html.haml5
-rw-r--r--app/views/projects/commits/_commit.html.haml51
-rw-r--r--app/views/projects/commits/_commit_list.html.haml2
-rw-r--r--app/views/projects/commits/_commits.html.haml2
-rw-r--r--app/views/projects/deploy_keys/_deploy_key.html.haml2
-rw-r--r--app/views/projects/deploy_keys/_form.html.haml4
-rw-r--r--app/views/projects/deploy_keys/_index.html.haml34
-rw-r--r--app/views/projects/deploy_keys/index.html.haml36
-rw-r--r--app/views/projects/edit.html.haml1
-rw-r--r--app/views/projects/environments/_metrics_button.html.haml6
-rw-r--r--app/views/projects/environments/metrics.html.haml21
-rw-r--r--app/views/projects/environments/show.html.haml1
-rw-r--r--app/views/projects/issues/_form.html.haml6
-rw-r--r--app/views/projects/merge_requests/_form.html.haml6
-rw-r--r--app/views/projects/merge_requests/_new_compare.html.haml6
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml5
-rw-r--r--app/views/projects/merge_requests/_show.html.haml4
-rw-r--r--app/views/projects/merge_requests/widget/_heading.html.haml4
-rw-r--r--app/views/projects/merge_requests/widget/_merged.html.haml60
-rw-r--r--app/views/projects/merge_requests/widget/_merged_buttons.haml2
-rw-r--r--app/views/projects/merge_requests/widget/_open.html.haml2
-rw-r--r--app/views/projects/merge_requests/widget/open/_conflicts.html.haml26
-rw-r--r--app/views/projects/merge_requests/widget/open/_manual.html.haml4
-rw-r--r--app/views/projects/merge_requests/widget/open/_merge_when_pipeline_succeeds.html.haml23
-rw-r--r--app/views/projects/pages/show.html.haml2
-rw-r--r--app/views/projects/pipelines/_stage.html.haml8
-rw-r--r--app/views/projects/protected_branches/_branches_list.html.haml2
-rw-r--r--app/views/projects/protected_branches/_create_protected_branch.html.haml2
-rw-r--r--app/views/projects/protected_branches/_index.html.haml (renamed from app/views/projects/protected_branches/index.html.haml)7
-rw-r--r--app/views/projects/protected_branches/_protected_branch.html.haml2
-rw-r--r--app/views/projects/settings/_head.html.haml33
-rw-r--r--app/views/projects/settings/ci_cd/show.html.haml1
-rw-r--r--app/views/projects/settings/integrations/show.html.haml1
-rw-r--r--app/views/projects/settings/members/show.html.haml1
-rw-r--r--app/views/projects/settings/repository/show.html.haml5
-rw-r--r--app/views/projects/show.html.haml5
-rw-r--r--app/views/projects/triggers/_content.html.haml14
-rw-r--r--app/views/projects/triggers/_form.html.haml11
-rw-r--r--app/views/projects/triggers/_index.html.haml24
-rw-r--r--app/views/projects/triggers/_trigger.html.haml40
-rw-r--r--app/views/projects/triggers/edit.html.haml9
-rw-r--r--app/views/search/_results.html.haml2
-rw-r--r--app/views/shared/_personal_access_tokens_form.html.haml39
-rw-r--r--app/views/shared/_personal_access_tokens_table.html.haml60
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml13
-rw-r--r--app/views/shared/issuable/form/_metadata.html.haml4
-rw-r--r--app/workers/post_receive.rb4
-rw-r--r--app/workers/system_hook_push_worker.rb8
-rw-r--r--app/workers/update_merge_requests_worker.rb3
-rw-r--r--changelogs/unreleased/1381-present-commits-pagination-headers-correctly.yml4
-rw-r--r--changelogs/unreleased/18962-update-issues-button-jumps.yml4
-rw-r--r--changelogs/unreleased/23948-assign-to-me.yml4
-rw-r--r--changelogs/unreleased/25367-add-impersonation-token.yml4
-rw-r--r--changelogs/unreleased/26188-tag-creation-404-for-guests.yml4
-rw-r--r--changelogs/unreleased/26371-native-emojis-v3-code.yml4
-rw-r--r--changelogs/unreleased/26732-combine-deploy-keys-and-push-rules-and-mirror-repository-and-protect-branches-settings-pages.yml5
-rw-r--r--changelogs/unreleased/26790-label-color-todos.yml4
-rw-r--r--changelogs/unreleased/27568-refactor-very-slow-dropdown-asignee-spec.yml4
-rw-r--r--changelogs/unreleased/27936-make-all-uploads-require-revalidation-on-each-browser-fetch.yml4
-rw-r--r--changelogs/unreleased/28019-make-builds-show-faster.yml4
-rw-r--r--changelogs/unreleased/28447-hybrid-repository-storages.yml4
-rw-r--r--changelogs/unreleased/28516-default-kubernetes-namespace.yml4
-rw-r--r--changelogs/unreleased/28538-restore-nav-shortcuts.yml4
-rw-r--r--changelogs/unreleased/28598-narrow-environment-payload-by-using-basic-project.yml4
-rw-r--r--changelogs/unreleased/28609-fix-redirect-to-home-page-url.yml4
-rw-r--r--changelogs/unreleased/28835-jobs-head.yml4
-rw-r--r--changelogs/unreleased/28850-fix-broken-migration.yml4
-rw-r--r--changelogs/unreleased/add-changelog-filtered-search-visual-tokens.yml4
-rw-r--r--changelogs/unreleased/add-git-version-to-system-info.yml4
-rw-r--r--changelogs/unreleased/add-pipeline-triggers.yml4
-rw-r--r--changelogs/unreleased/clear-connections-before-starting-sidekiq.yml4
-rw-r--r--changelogs/unreleased/dm-dont-copy-toolip.yml4
-rw-r--r--changelogs/unreleased/dm-fix-api-create-file-on-empty-repo.yml4
-rw-r--r--changelogs/unreleased/dm-fix-cherry-pick.yml4
-rw-r--r--changelogs/unreleased/dz-nested-groups-restrictions.yml4
-rw-r--r--changelogs/unreleased/feature-openid-connect.yml4
-rw-r--r--changelogs/unreleased/feature-runner-jobs-v4-api.yml4
-rw-r--r--changelogs/unreleased/feature-syshook_commits.yml4
-rw-r--r--changelogs/unreleased/fix-gb-deprecate-ci-config-types.yml4
-rw-r--r--changelogs/unreleased/issue_16834.yml4
-rw-r--r--changelogs/unreleased/priority-to-label-priority.yml4
-rw-r--r--changelogs/unreleased/remove-readme-option.yml4
-rw-r--r--changelogs/unreleased/rfr-20170307-change-default-project-number-limit.yml4
-rw-r--r--changelogs/unreleased/set-default-cache-key-for-jobs.yml4
-rw-r--r--changelogs/unreleased/settings-tab.yml4
-rw-r--r--changelogs/unreleased/sort-builds-in-stage-dropdown.yml4
-rw-r--r--changelogs/unreleased/tc-api-pipeline-jobs.yml4
-rw-r--r--changelogs/unreleased/use-redis-channel-to-post-runner-notifcations.yml4
-rw-r--r--changelogs/unreleased/use-v3-api-on-frontend.yml4
-rw-r--r--changelogs/unreleased/zj-variables-build-job.yml4
-rw-r--r--config/application.rb1
-rw-r--r--config/gitlab.yml.example6
-rw-r--r--config/initializers/1_settings.rb17
-rw-r--r--config/initializers/6_validations.rb20
-rw-r--r--config/initializers/doorkeeper.rb11
-rw-r--r--config/initializers/doorkeeper_openid_connect.rb36
-rw-r--r--config/initializers/rspec_profiling.rb37
-rw-r--r--config/initializers/secret_token.rb7
-rw-r--r--config/initializers/sidekiq.rb6
-rw-r--r--config/locales/doorkeeper.en.yml1
-rw-r--r--config/routes.rb5
-rw-r--r--config/routes/admin.rb5
-rw-r--r--config/routes/project.rb9
-rw-r--r--config/sidekiq_queues.yml1
-rw-r--r--config/webpack.config.js2
-rw-r--r--db/fixtures/development/15_award_emoji.rb2
-rw-r--r--db/migrate/20140502125220_migrate_repo_size.rb2
-rw-r--r--db/migrate/20160615142710_add_index_on_requested_at_to_members.rb8
-rw-r--r--db/migrate/20160620115026_add_index_on_runners_locked.rb8
-rw-r--r--db/migrate/20160715134306_add_index_for_pipeline_user_id.rb8
-rw-r--r--db/migrate/20160805041956_add_deleted_at_to_namespaces.rb9
-rw-r--r--db/migrate/20160808085602_add_index_for_build_token.rb6
-rw-r--r--db/migrate/20160819221631_add_index_to_note_discussion_id.rb6
-rw-r--r--db/migrate/20160819232256_add_incoming_email_token_to_users.rb9
-rw-r--r--db/migrate/20160919145149_add_group_id_to_labels.rb8
-rw-r--r--db/migrate/20160920160832_add_index_to_labels_title.rb6
-rw-r--r--db/migrate/20161020083353_add_pipeline_id_to_merge_request_metrics.rb10
-rw-r--r--db/migrate/20161106185620_add_project_import_data_project_index.rb6
-rw-r--r--db/migrate/20161124111395_add_index_to_parent_id.rb6
-rw-r--r--db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb2
-rw-r--r--db/migrate/20161202152035_add_index_to_routes.rb7
-rw-r--r--db/migrate/20161209153400_add_unique_index_for_environment_slug.rb6
-rw-r--r--db/migrate/20161209165216_create_doorkeeper_openid_connect_tables.rb37
-rw-r--r--db/migrate/20161220141214_remove_dot_git_from_group_names.rb2
-rw-r--r--db/migrate/20161226122833_remove_dot_git_from_usernames.rb4
-rw-r--r--db/migrate/20161228124936_change_expires_at_to_date_in_personal_access_tokens.rb18
-rw-r--r--db/migrate/20161228135550_add_impersonation_to_personal_access_tokens.rb18
-rw-r--r--db/migrate/20170131221752_add_relative_position_to_issues.rb37
-rw-r--r--db/migrate/20170204181513_add_index_to_labels_for_type_and_project.rb6
-rw-r--r--db/migrate/20170210062829_add_index_to_labels_for_title_and_project.rb7
-rw-r--r--db/migrate/20170210075922_add_index_to_ci_trigger_requests_for_commit_id.rb6
-rw-r--r--db/migrate/20170210103609_add_index_to_user_agent_detail.rb8
-rw-r--r--db/migrate/20170216135621_add_index_for_latest_successful_pipeline.rb8
-rw-r--r--db/post_migrate/20170209140523_validate_foreign_keys_on_oauth_openid_requests.rb20
-rw-r--r--db/schema.rb15
-rw-r--r--doc/administration/auth/crowd.md68
-rw-r--r--doc/administration/auth/img/crowd_application.pngbin0 -> 55811 bytes
-rw-r--r--doc/administration/container_registry.md58
-rw-r--r--doc/administration/repository_storage_paths.md15
-rw-r--r--doc/api/README.md10
-rw-r--r--doc/api/award_emoji.md108
-rw-r--r--doc/api/builds.md1
-rw-r--r--doc/api/ci/builds.md2
-rw-r--r--doc/api/issues.md144
-rw-r--r--doc/api/jobs.md120
-rw-r--r--doc/api/merge_requests.md196
-rw-r--r--doc/api/projects.md2
-rw-r--r--doc/api/repositories.md12
-rw-r--r--doc/api/repository_files.md39
-rw-r--r--doc/api/settings.md6
-rw-r--r--doc/api/users.md96
-rw-r--r--doc/api/v3_to_v4.md18
-rw-r--r--doc/ci/variables/README.md78
-rw-r--r--doc/ci/yaml/README.md21
-rw-r--r--doc/integration/README.md1
-rw-r--r--doc/integration/crowd.md64
-rw-r--r--doc/integration/omniauth.md2
-rw-r--r--doc/integration/openid_connect_provider.md47
-rw-r--r--doc/update/8.17-to-9.0.md65
-rw-r--r--doc/workflow/shortcuts.md2
-rw-r--r--features/project/active_tab.feature43
-rw-r--r--features/project/shortcuts.feature15
-rw-r--r--features/steps/project/active_tab.rb48
-rw-r--r--features/steps/project/deploy_keys.rb2
-rw-r--r--features/steps/project/issues/award_emoji.rb2
-rw-r--r--features/steps/project/project_shortcuts.rb5
-rw-r--r--features/steps/shared/project_tab.rb20
-rw-r--r--fixtures/emojis/digests.json15206
-rw-r--r--lib/api/api.rb2
-rw-r--r--lib/api/award_emoji.rb20
-rw-r--r--lib/api/commits.rb28
-rw-r--r--lib/api/entities.rb96
-rw-r--r--lib/api/files.rb62
-rw-r--r--lib/api/helpers.rb12
-rw-r--r--lib/api/helpers/internal_helpers.rb6
-rw-r--r--lib/api/helpers/runner.rb54
-rw-r--r--lib/api/issues.rb24
-rw-r--r--lib/api/jobs.rb39
-rw-r--r--lib/api/merge_request_diffs.rb12
-rw-r--r--lib/api/merge_requests.rb44
-rw-r--r--lib/api/repositories.rb55
-rw-r--r--lib/api/runner.rb198
-rw-r--r--lib/api/services.rb9
-rw-r--r--lib/api/time_tracking_endpoints.rb6
-rw-r--r--lib/api/todos.rb8
-rw-r--r--lib/api/users.rb75
-rw-r--r--lib/api/v3/award_emoji.rb79
-rw-r--r--lib/api/v3/builds.rb24
-rw-r--r--lib/api/v3/helpers.rb19
-rw-r--r--lib/api/v3/repositories.rb54
-rw-r--r--lib/api/v3/time_tracking_endpoints.rb116
-rw-r--r--lib/backup/repository.rb5
-rw-r--r--lib/banzai/filter/emoji_filter.rb64
-rw-r--r--lib/ci/api/builds.rb2
-rw-r--r--lib/gitlab/auth.rb33
-rw-r--r--lib/gitlab/award_emoji.rb84
-rw-r--r--lib/gitlab/ci/build/image.rb33
-rw-r--r--lib/gitlab/ci/build/step.rb46
-rw-r--r--lib/gitlab/ci/config/entry/cache.rb6
-rw-r--r--lib/gitlab/ci/config/entry/key.rb4
-rw-r--r--lib/gitlab/ci/config/entry/node.rb6
-rw-r--r--lib/gitlab/ci/config/entry/undefined.rb4
-rw-r--r--lib/gitlab/emoji.rb43
-rw-r--r--lib/gitlab/git/repository.rb12
-rw-r--r--lib/gitlab/gon_helper.rb3
-rw-r--r--lib/gitlab/middleware/go.rb66
-rw-r--r--lib/gitlab/prometheus.rb70
-rw-r--r--lib/gitlab/workhorse.rb13
-rw-r--r--lib/tasks/gemojione.rake88
-rw-r--r--lib/tasks/gitlab/check.rake16
-rw-r--r--lib/tasks/gitlab/cleanup.rake6
-rw-r--r--lib/tasks/gitlab/import.rake3
-rw-r--r--lib/tasks/gitlab/info.rake7
-rw-r--r--lib/tasks/gitlab/task_helpers.rb6
-rw-r--r--package.json4
-rw-r--r--rubocop/cop/migration/add_concurrent_index.rb34
-rw-r--r--rubocop/rubocop.rb1
-rw-r--r--spec/controllers/admin/applications_controller_spec.rb65
-rw-r--r--spec/controllers/profiles/personal_access_tokens_spec.rb45
-rw-r--r--spec/controllers/projects/boards/issues_controller_spec.rb1
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb46
-rw-r--r--spec/controllers/projects/settings/repository_controller_spec.rb20
-rw-r--r--spec/controllers/projects_controller_spec.rb8
-rw-r--r--spec/controllers/uploads_controller_spec.rb82
-rw-r--r--spec/factories/chat_teams.rb9
-rw-r--r--spec/factories/ci/builds.rb30
-rw-r--r--spec/factories/oauth_access_grants.rb11
-rw-r--r--spec/factories/oauth_access_tokens.rb3
-rw-r--r--spec/factories/oauth_applications.rb2
-rw-r--r--spec/factories/personal_access_tokens.rb17
-rw-r--r--spec/factories/projects.rb11
-rw-r--r--spec/features/admin/admin_users_impersonation_tokens_spec.rb72
-rw-r--r--spec/features/atom/users_spec.rb2
-rw-r--r--spec/features/boards/boards_spec.rb18
-rw-r--r--spec/features/boards/issue_ordering_spec.rb166
-rw-r--r--spec/features/boards/sidebar_spec.rb4
-rw-r--r--spec/features/copy_as_gfm_spec.rb2
-rw-r--r--spec/features/groups_spec.rb2
-rw-r--r--spec/features/issues/award_emoji_spec.rb10
-rw-r--r--spec/features/issues/filtered_search/dropdown_assignee_spec.rb82
-rw-r--r--spec/features/issues/filtered_search/dropdown_author_spec.rb7
-rw-r--r--spec/features/issues/filtered_search/dropdown_hint_spec.rb63
-rw-r--r--spec/features/issues/filtered_search/dropdown_label_spec.rb31
-rw-r--r--spec/features/issues/filtered_search/dropdown_milestone_spec.rb28
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb367
-rw-r--r--spec/features/issues/filtered_search/search_bar_spec.rb4
-rw-r--r--spec/features/issues/filtered_search/visual_tokens_spec.rb322
-rw-r--r--spec/features/issues/form_spec.rb14
-rw-r--r--spec/features/merge_requests/filter_by_milestone_spec.rb3
-rw-r--r--spec/features/merge_requests/filter_merge_requests_spec.rb88
-rw-r--r--spec/features/merge_requests/form_spec.rb10
-rw-r--r--spec/features/merge_requests/reset_filters_spec.rb32
-rw-r--r--spec/features/merge_requests/widget_spec.rb27
-rw-r--r--spec/features/profiles/personal_access_tokens_spec.rb20
-rw-r--r--spec/features/projects/environments/environment_metrics_spec.rb39
-rw-r--r--spec/features/projects/environments/environment_spec.rb (renamed from spec/features/environment_spec.rb)19
-rw-r--r--spec/features/projects/environments/environments_spec.rb (renamed from spec/features/environments_spec.rb)0
-rw-r--r--spec/features/projects/labels/issues_sorted_by_priority_spec.rb4
-rw-r--r--spec/features/projects/members/user_requests_access_spec.rb8
-rw-r--r--spec/features/projects_spec.rb2
-rw-r--r--spec/features/search_spec.rb13
-rw-r--r--spec/features/security/project/internal_access_spec.rb14
-rw-r--r--spec/features/security/project/private_access_spec.rb14
-rw-r--r--spec/features/security/project/public_access_spec.rb14
-rw-r--r--spec/features/todos/todos_sorting_spec.rb8
-rw-r--r--spec/features/triggers_spec.rb169
-rw-r--r--spec/finders/personal_access_tokens_finder_spec.rb196
-rw-r--r--spec/fixtures/api/schemas/issue.json1
-rw-r--r--spec/helpers/events_helper_spec.rb7
-rw-r--r--spec/helpers/gitlab_markdown_helper_spec.rb2
-rw-r--r--spec/initializers/6_validations_spec.rb28
-rw-r--r--spec/initializers/doorkeeper_spec.rb71
-rw-r--r--spec/initializers/secret_token_spec.rb25
-rw-r--r--spec/javascripts/awards_handler_spec.js153
-rw-r--r--spec/javascripts/boards/boards_store_spec.js75
-rw-r--r--spec/javascripts/boards/issue_spec.js16
-rw-r--r--spec/javascripts/boards/list_spec.js3
-rw-r--r--spec/javascripts/build_spec.js10
-rw-r--r--spec/javascripts/filtered_search/dropdown_user_spec.js8
-rw-r--r--spec/javascripts/filtered_search/dropdown_utils_spec.js25
-rw-r--r--spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js72
-rw-r--r--spec/javascripts/filtered_search/filtered_search_manager_spec.js232
-rw-r--r--spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js600
-rw-r--r--spec/javascripts/fixtures/emoji_menu.js4
-rw-r--r--spec/javascripts/fixtures/environments/metrics.html.haml12
-rw-r--r--spec/javascripts/gl_emoji_spec.js367
-rw-r--r--spec/javascripts/helpers/filtered_search_spec_helper.js52
-rw-r--r--spec/javascripts/monitoring/prometheus_graph_spec.js78
-rw-r--r--spec/javascripts/monitoring/prometheus_mock_data.js1014
-rw-r--r--spec/lib/banzai/filter/emoji_filter_spec.rb112
-rw-r--r--spec/lib/gitlab/auth_spec.rb64
-rw-r--r--spec/lib/gitlab/award_emoji_spec.rb41
-rw-r--r--spec/lib/gitlab/ci/build/image_spec.rb67
-rw-r--r--spec/lib/gitlab/ci/build/step_spec.rb39
-rw-r--r--spec/lib/gitlab/ci/config/entry/cache_spec.rb14
-rw-r--r--spec/lib/gitlab/ci/config/entry/factory_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/entry/global_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/key_spec.rb6
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb26
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml1
-rw-r--r--spec/lib/gitlab/middleware/go_spec.rb95
-rw-r--r--spec/lib/gitlab/prometheus_spec.rb143
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb54
-rw-r--r--spec/models/chat_team_spec.rb5
-rw-r--r--spec/models/ci/build_spec.rb41
-rw-r--r--spec/models/ci/trigger_spec.rb58
-rw-r--r--spec/models/concerns/relative_positioning_spec.rb104
-rw-r--r--spec/models/environment_spec.rb83
-rw-r--r--spec/models/namespace_spec.rb16
-rw-r--r--spec/models/personal_access_token_spec.rb60
-rw-r--r--spec/models/project_services/kubernetes_service_spec.rb6
-rw-r--r--spec/models/project_services/prometheus_service_spec.rb104
-rw-r--r--spec/models/project_spec.rb8
-rw-r--r--spec/models/repository_spec.rb29
-rw-r--r--spec/policies/ci/trigger_policy_spec.rb103
-rw-r--r--spec/presenters/projects/settings/deploy_keys_presenter_spec.rb66
-rw-r--r--spec/requests/api/api_internal_helpers_spec.rb2
-rw-r--r--spec/requests/api/award_emoji_spec.rb50
-rw-r--r--spec/requests/api/commits_spec.rb94
-rw-r--r--spec/requests/api/doorkeeper_access_spec.rb18
-rw-r--r--spec/requests/api/environments_spec.rb5
-rw-r--r--spec/requests/api/files_spec.rb182
-rw-r--r--spec/requests/api/issues_spec.rb156
-rw-r--r--spec/requests/api/jobs_spec.rb76
-rw-r--r--spec/requests/api/merge_request_diffs_spec.rb32
-rw-r--r--spec/requests/api/merge_requests_spec.rb197
-rw-r--r--spec/requests/api/oauth_tokens_spec.rb22
-rw-r--r--spec/requests/api/repositories_spec.rb100
-rw-r--r--spec/requests/api/runner_spec.rb874
-rw-r--r--spec/requests/api/session_spec.rb18
-rw-r--r--spec/requests/api/todos_spec.rb6
-rw-r--r--spec/requests/api/users_spec.rb185
-rw-r--r--spec/requests/api/v3/award_emoji_spec.rb225
-rw-r--r--spec/requests/api/v3/issues_spec.rb2
-rw-r--r--spec/requests/api/v3/merge_request_diffs_spec.rb11
-rw-r--r--spec/requests/api/v3/merge_requests_spec.rb2
-rw-r--r--spec/requests/api/v3/repositories_spec.rb222
-rw-r--r--spec/requests/api/v3/services_spec.rb4
-rw-r--r--spec/requests/git_http_spec.rb12
-rw-r--r--spec/requests/openid_connect_spec.rb134
-rw-r--r--spec/routing/openid_connect_spec.rb30
-rw-r--r--spec/routing/project_routing_spec.rb3
-rw-r--r--spec/rubocop/cop/migration/add_concurrent_index_spec.rb41
-rw-r--r--spec/services/boards/issues/list_service_spec.rb26
-rw-r--r--spec/services/boards/issues/move_service_spec.rb18
-rw-r--r--spec/services/ci/register_job_service_spec.rb (renamed from spec/services/ci/register_build_service_spec.rb)8
-rw-r--r--spec/services/git_push_service_spec.rb7
-rw-r--r--spec/services/issues/update_service_spec.rb16
-rw-r--r--spec/services/todo_service_spec.rb2
-rw-r--r--spec/support/api/time_tracking_shared_examples.rb28
-rw-r--r--spec/support/api/v3/time_tracking_shared_examples.rb128
-rw-r--r--spec/support/filtered_search_helpers.rb36
-rw-r--r--spec/support/matchers/markdown_matchers.rb7
-rw-r--r--spec/support/prometheus_helpers.rb117
-rw-r--r--spec/support/test_env.rb2
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb4
-rw-r--r--spec/tasks/gitlab/info_rake_spec.rb37
-rw-r--r--spec/views/projects/commit/_commit_box.html.haml_spec.rb28
-rw-r--r--spec/views/projects/pipelines/_stage.html.haml_spec.rb19
-rw-r--r--spec/workers/post_receive_spec.rb2
-rw-r--r--spec/workers/system_hook_push_worker_spec.rb19
-rw-r--r--spec/workers/update_merge_requests_worker_spec.rb11
-rw-r--r--yarn.lock16
2277 files changed, 23943 insertions, 12899 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e075de055e3..42e094bdfc6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,15 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 8.17.3 (2017-03-07)
+
+- Fix the redirect to custom home page URL. !9518
+- Fix broken migration when upgrading straight to 8.17.1. !9613
+- Make projects dropdown only show projects you are a member of. !9614
+- Fix creating a file in an empty repo using the API. !9632
+- Don't copy tooltip when copying GFM.
+- Fix cherry-picking or reverting through an MR.
+
## 8.17.2 (2017-03-01)
- Expire all webpack assets after 8.17.1 included a badly compiled asset. !9602
diff --git a/Gemfile b/Gemfile
index 0869eba116b..2f813324a35 100644
--- a/Gemfile
+++ b/Gemfile
@@ -18,25 +18,26 @@ gem 'pg', '~> 0.18.2', group: :postgres
gem 'rugged', '~> 0.24.0'
# Authentication libraries
-gem 'devise', '~> 4.2'
-gem 'doorkeeper', '~> 4.2.0'
-gem 'omniauth', '~> 1.4.2'
-gem 'omniauth-auth0', '~> 1.4.1'
-gem 'omniauth-azure-oauth2', '~> 0.0.6'
-gem 'omniauth-cas3', '~> 1.1.2'
-gem 'omniauth-facebook', '~> 4.0.0'
-gem 'omniauth-github', '~> 1.1.1'
-gem 'omniauth-gitlab', '~> 1.0.2'
+gem 'devise', '~> 4.2'
+gem 'doorkeeper', '~> 4.2.0'
+gem 'doorkeeper-openid_connect', '~> 1.1.0'
+gem 'omniauth', '~> 1.4.2'
+gem 'omniauth-auth0', '~> 1.4.1'
+gem 'omniauth-azure-oauth2', '~> 0.0.6'
+gem 'omniauth-cas3', '~> 1.1.2'
+gem 'omniauth-facebook', '~> 4.0.0'
+gem 'omniauth-github', '~> 1.1.1'
+gem 'omniauth-gitlab', '~> 1.0.2'
gem 'omniauth-google-oauth2', '~> 0.4.1'
-gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
+gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-oauth2-generic', '~> 0.2.2'
-gem 'omniauth-saml', '~> 1.7.0'
-gem 'omniauth-shibboleth', '~> 1.2.0'
-gem 'omniauth-twitter', '~> 1.2.0'
-gem 'omniauth_crowd', '~> 2.2.0'
-gem 'omniauth-authentiq', '~> 0.3.0'
-gem 'rack-oauth2', '~> 1.2.1'
-gem 'jwt', '~> 1.5.6'
+gem 'omniauth-saml', '~> 1.7.0'
+gem 'omniauth-shibboleth', '~> 1.2.0'
+gem 'omniauth-twitter', '~> 1.2.0'
+gem 'omniauth_crowd', '~> 2.2.0'
+gem 'omniauth-authentiq', '~> 0.3.0'
+gem 'rack-oauth2', '~> 1.2.1'
+gem 'jwt', '~> 1.5.6'
# Spam and anti-bot protection
gem 'recaptcha', '~> 3.0', require: 'recaptcha/rails'
@@ -68,9 +69,9 @@ gem 'gollum-rugged_adapter', '~> 0.4.2', require: false
gem 'github-linguist', '~> 4.7.0', require: 'linguist'
# API
-gem 'grape', '~> 0.19.0'
+gem 'grape', '~> 0.19.0'
gem 'grape-entity', '~> 0.6.0'
-gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
+gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
# Pagination
gem 'kaminari', '~> 0.17.0'
@@ -102,19 +103,19 @@ gem 'unf', '~> 0.1.4'
gem 'seed-fu', '~> 2.3.5'
# Markdown and HTML processing
-gem 'html-pipeline', '~> 1.11.0'
-gem 'deckar01-task_list', '1.0.6', require: 'task_list/railtie'
-gem 'gitlab-markup', '~> 1.5.1'
-gem 'redcarpet', '~> 3.4'
-gem 'RedCloth', '~> 4.3.2'
-gem 'rdoc', '~> 4.2'
-gem 'org-ruby', '~> 0.9.12'
-gem 'creole', '~> 0.5.0'
-gem 'wikicloth', '0.8.1'
-gem 'asciidoctor', '~> 1.5.2'
+gem 'html-pipeline', '~> 1.11.0'
+gem 'deckar01-task_list', '1.0.6', require: 'task_list/railtie'
+gem 'gitlab-markup', '~> 1.5.1'
+gem 'redcarpet', '~> 3.4'
+gem 'RedCloth', '~> 4.3.2'
+gem 'rdoc', '~> 4.2'
+gem 'org-ruby', '~> 0.9.12'
+gem 'creole', '~> 0.5.0'
+gem 'wikicloth', '0.8.1'
+gem 'asciidoctor', '~> 1.5.2'
gem 'asciidoctor-plantuml', '0.0.7'
-gem 'rouge', '~> 2.0'
-gem 'truncato', '~> 0.7.8'
+gem 'rouge', '~> 2.0'
+gem 'truncato', '~> 0.7.8'
# See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s
# and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM
@@ -229,18 +230,18 @@ gem 'sass-rails', '~> 5.0.6'
gem 'coffee-rails', '~> 4.1.0'
gem 'uglifier', '~> 2.7.2'
-gem 'addressable', '~> 2.3.8'
-gem 'bootstrap-sass', '~> 3.3.0'
-gem 'font-awesome-rails', '~> 4.6.1'
-gem 'gemojione', '~> 3.0'
-gem 'gon', '~> 6.1.0'
+gem 'addressable', '~> 2.3.8'
+gem 'bootstrap-sass', '~> 3.3.0'
+gem 'font-awesome-rails', '~> 4.7'
+gem 'gemojione', '~> 3.0'
+gem 'gon', '~> 6.1.0'
gem 'jquery-atwho-rails', '~> 1.3.2'
-gem 'jquery-rails', '~> 4.1.0'
-gem 'request_store', '~> 1.3'
-gem 'select2-rails', '~> 3.5.9'
-gem 'virtus', '~> 1.0.1'
-gem 'net-ssh', '~> 3.0.1'
-gem 'base32', '~> 0.3.0'
+gem 'jquery-rails', '~> 4.1.0'
+gem 'request_store', '~> 1.3'
+gem 'select2-rails', '~> 3.5.9'
+gem 'virtus', '~> 1.0.1'
+gem 'net-ssh', '~> 3.0.1'
+gem 'base32', '~> 0.3.0'
# Sentry integration
gem 'sentry-raven', '~> 2.0.0'
@@ -278,13 +279,13 @@ group :development, :test do
gem 'awesome_print', '~> 1.2.0', require: false
gem 'fuubar', '~> 2.0.0'
- gem 'database_cleaner', '~> 1.5.0'
+ gem 'database_cleaner', '~> 1.5.0'
gem 'factory_girl_rails', '~> 4.7.0'
- gem 'rspec-rails', '~> 3.5.0'
- gem 'rspec-retry', '~> 0.4.5'
- gem 'spinach-rails', '~> 0.2.1'
+ gem 'rspec-rails', '~> 3.5.0'
+ gem 'rspec-retry', '~> 0.4.5'
+ gem 'spinach-rails', '~> 0.2.1'
gem 'spinach-rerun-reporter', '~> 0.0.2'
- gem 'rspec_profiling', '~> 0.0.5'
+ gem 'rspec_profiling', '~> 0.0.5'
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
gem 'minitest', '~> 5.7.0'
@@ -292,13 +293,13 @@ group :development, :test do
# Generate Fake data
gem 'ffaker', '~> 2.4'
- gem 'capybara', '~> 2.6.2'
+ gem 'capybara', '~> 2.6.2'
gem 'capybara-screenshot', '~> 1.0.0'
- gem 'poltergeist', '~> 1.9.0'
+ gem 'poltergeist', '~> 1.9.0'
- gem 'spring', '~> 1.7.0'
- gem 'spring-commands-rspec', '~> 1.0.4'
- gem 'spring-commands-spinach', '~> 1.1.0'
+ gem 'spring', '~> 1.7.0'
+ gem 'spring-commands-rspec', '~> 1.0.4'
+ gem 'spring-commands-spinach', '~> 1.1.0'
gem 'rubocop', '~> 0.47.1', require: false
gem 'rubocop-rspec', '~> 1.12.0', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index f59bde27bc4..62388628eaa 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -78,6 +78,7 @@ GEM
better_errors (1.0.1)
coderay (>= 1.0.0)
erubis (>= 2.6.6)
+ bindata (2.3.5)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
bootstrap-sass (3.3.6)
@@ -167,6 +168,9 @@ GEM
unf (>= 0.0.5, < 1.0.0)
doorkeeper (4.2.0)
railties (>= 4.2)
+ doorkeeper-openid_connect (1.1.2)
+ doorkeeper (~> 4.0)
+ json-jwt (~> 1.6)
dropzonejs-rails (0.7.2)
rails (> 3.1)
email_reply_trimmer (0.1.6)
@@ -232,7 +236,7 @@ GEM
fog-xml (0.1.2)
fog-core
nokogiri (~> 1.5, >= 1.5.11)
- font-awesome-rails (4.6.1.0)
+ font-awesome-rails (4.7.0.1)
railties (>= 3.2, < 5.1)
foreman (0.78.0)
thor (~> 0.19.1)
@@ -376,6 +380,12 @@ GEM
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
json (1.8.6)
+ json-jwt (1.7.1)
+ activesupport
+ bindata
+ multi_json (>= 1.3)
+ securecompare
+ url_safe_base64
json-schema (2.6.2)
addressable (~> 2.3.8)
jwt (1.5.6)
@@ -684,6 +694,7 @@ GEM
scss_lint (0.47.1)
rake (>= 0.9, < 11)
sass (~> 3.4.15)
+ securecompare (1.0.0)
seed-fu (2.3.6)
activerecord (>= 3.1)
activesupport (>= 3.1)
@@ -789,6 +800,7 @@ GEM
get_process_mem (~> 0)
unicorn (>= 4, < 6)
uniform_notifier (1.10.0)
+ url_safe_base64 (0.2.2)
validates_hostname (1.0.6)
activerecord (>= 3.0)
activesupport (>= 3.0)
@@ -866,6 +878,7 @@ DEPENDENCIES
devise-two-factor (~> 3.0.0)
diffy (~> 3.1.0)
doorkeeper (~> 4.2.0)
+ doorkeeper-openid_connect (~> 1.1.0)
dropzonejs-rails (~> 0.7.1)
email_reply_trimmer (~> 0.1)
email_spec (~> 1.6.0)
@@ -878,7 +891,7 @@ DEPENDENCIES
fog-local (~> 0.3)
fog-openstack (~> 0.1)
fog-rackspace (~> 0.1.1)
- font-awesome-rails (~> 4.6.1)
+ font-awesome-rails (~> 4.7)
foreman (~> 0.78.0)
fuubar (~> 2.0.0)
gemnasium-gitlab-service (~> 0.2)
diff --git a/app/assets/images/emoji.png b/app/assets/images/emoji.png
index 6f1a34a5591..5dcd9c09b70 100644
--- a/app/assets/images/emoji.png
+++ b/app/assets/images/emoji.png
Binary files differ
diff --git a/app/assets/images/emoji/100.png b/app/assets/images/emoji/100.png
new file mode 100644
index 00000000000..6903ff0304a
--- /dev/null
+++ b/app/assets/images/emoji/100.png
Binary files differ
diff --git a/app/assets/images/emoji/1234.png b/app/assets/images/emoji/1234.png
new file mode 100644
index 00000000000..248dc7e55b6
--- /dev/null
+++ b/app/assets/images/emoji/1234.png
Binary files differ
diff --git a/app/assets/images/emoji/1F627.png b/app/assets/images/emoji/1F627.png
new file mode 100644
index 00000000000..f99026a3bc7
--- /dev/null
+++ b/app/assets/images/emoji/1F627.png
Binary files differ
diff --git a/app/assets/images/emoji/8ball.png b/app/assets/images/emoji/8ball.png
new file mode 100644
index 00000000000..38ca662eded
--- /dev/null
+++ b/app/assets/images/emoji/8ball.png
Binary files differ
diff --git a/app/assets/images/emoji/a.png b/app/assets/images/emoji/a.png
new file mode 100644
index 00000000000..8603ff05a17
--- /dev/null
+++ b/app/assets/images/emoji/a.png
Binary files differ
diff --git a/app/assets/images/emoji/ab.png b/app/assets/images/emoji/ab.png
new file mode 100644
index 00000000000..d9f2d17dea0
--- /dev/null
+++ b/app/assets/images/emoji/ab.png
Binary files differ
diff --git a/app/assets/images/emoji/abc.png b/app/assets/images/emoji/abc.png
new file mode 100644
index 00000000000..7688de692a9
--- /dev/null
+++ b/app/assets/images/emoji/abc.png
Binary files differ
diff --git a/app/assets/images/emoji/abcd.png b/app/assets/images/emoji/abcd.png
new file mode 100644
index 00000000000..0996a870570
--- /dev/null
+++ b/app/assets/images/emoji/abcd.png
Binary files differ
diff --git a/app/assets/images/emoji/accept.png b/app/assets/images/emoji/accept.png
new file mode 100644
index 00000000000..8afd7ce99cf
--- /dev/null
+++ b/app/assets/images/emoji/accept.png
Binary files differ
diff --git a/app/assets/images/emoji/aerial_tramway.png b/app/assets/images/emoji/aerial_tramway.png
new file mode 100644
index 00000000000..3eb4b61bf1d
--- /dev/null
+++ b/app/assets/images/emoji/aerial_tramway.png
Binary files differ
diff --git a/app/assets/images/emoji/airplane.png b/app/assets/images/emoji/airplane.png
new file mode 100644
index 00000000000..268d2ac3c8e
--- /dev/null
+++ b/app/assets/images/emoji/airplane.png
Binary files differ
diff --git a/app/assets/images/emoji/airplane_arriving.png b/app/assets/images/emoji/airplane_arriving.png
new file mode 100644
index 00000000000..d66841962f2
--- /dev/null
+++ b/app/assets/images/emoji/airplane_arriving.png
Binary files differ
diff --git a/app/assets/images/emoji/airplane_departure.png b/app/assets/images/emoji/airplane_departure.png
new file mode 100644
index 00000000000..a5766f9f4ae
--- /dev/null
+++ b/app/assets/images/emoji/airplane_departure.png
Binary files differ
diff --git a/app/assets/images/emoji/airplane_small.png b/app/assets/images/emoji/airplane_small.png
new file mode 100644
index 00000000000..b731b15e3a8
--- /dev/null
+++ b/app/assets/images/emoji/airplane_small.png
Binary files differ
diff --git a/app/assets/images/emoji/alarm_clock.png b/app/assets/images/emoji/alarm_clock.png
new file mode 100644
index 00000000000..cdbc2fbb950
--- /dev/null
+++ b/app/assets/images/emoji/alarm_clock.png
Binary files differ
diff --git a/app/assets/images/emoji/alembic.png b/app/assets/images/emoji/alembic.png
new file mode 100644
index 00000000000..307a7324249
--- /dev/null
+++ b/app/assets/images/emoji/alembic.png
Binary files differ
diff --git a/app/assets/images/emoji/alien.png b/app/assets/images/emoji/alien.png
new file mode 100644
index 00000000000..3b90e97433b
--- /dev/null
+++ b/app/assets/images/emoji/alien.png
Binary files differ
diff --git a/app/assets/images/emoji/ambulance.png b/app/assets/images/emoji/ambulance.png
new file mode 100644
index 00000000000..6fb8076d766
--- /dev/null
+++ b/app/assets/images/emoji/ambulance.png
Binary files differ
diff --git a/app/assets/images/emoji/amphora.png b/app/assets/images/emoji/amphora.png
new file mode 100644
index 00000000000..96de5056059
--- /dev/null
+++ b/app/assets/images/emoji/amphora.png
Binary files differ
diff --git a/app/assets/images/emoji/anchor.png b/app/assets/images/emoji/anchor.png
new file mode 100644
index 00000000000..b036f70a00b
--- /dev/null
+++ b/app/assets/images/emoji/anchor.png
Binary files differ
diff --git a/app/assets/images/emoji/angel.png b/app/assets/images/emoji/angel.png
new file mode 100644
index 00000000000..66ea97a3b99
--- /dev/null
+++ b/app/assets/images/emoji/angel.png
Binary files differ
diff --git a/app/assets/images/emoji/angel_tone1.png b/app/assets/images/emoji/angel_tone1.png
new file mode 100644
index 00000000000..391694dc07e
--- /dev/null
+++ b/app/assets/images/emoji/angel_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/angel_tone2.png b/app/assets/images/emoji/angel_tone2.png
new file mode 100644
index 00000000000..700cbe6ed2c
--- /dev/null
+++ b/app/assets/images/emoji/angel_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/angel_tone3.png b/app/assets/images/emoji/angel_tone3.png
new file mode 100644
index 00000000000..be597437d25
--- /dev/null
+++ b/app/assets/images/emoji/angel_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/angel_tone4.png b/app/assets/images/emoji/angel_tone4.png
new file mode 100644
index 00000000000..b06d3c853ef
--- /dev/null
+++ b/app/assets/images/emoji/angel_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/angel_tone5.png b/app/assets/images/emoji/angel_tone5.png
new file mode 100644
index 00000000000..17bd677e334
--- /dev/null
+++ b/app/assets/images/emoji/angel_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/anger.png b/app/assets/images/emoji/anger.png
new file mode 100644
index 00000000000..d63c2e000e4
--- /dev/null
+++ b/app/assets/images/emoji/anger.png
Binary files differ
diff --git a/app/assets/images/emoji/anger_right.png b/app/assets/images/emoji/anger_right.png
new file mode 100644
index 00000000000..f5c97c4d297
--- /dev/null
+++ b/app/assets/images/emoji/anger_right.png
Binary files differ
diff --git a/app/assets/images/emoji/angry.png b/app/assets/images/emoji/angry.png
new file mode 100644
index 00000000000..cfc4a6ecde5
--- /dev/null
+++ b/app/assets/images/emoji/angry.png
Binary files differ
diff --git a/app/assets/images/emoji/ant.png b/app/assets/images/emoji/ant.png
new file mode 100644
index 00000000000..994127ed6b3
--- /dev/null
+++ b/app/assets/images/emoji/ant.png
Binary files differ
diff --git a/app/assets/images/emoji/apple.png b/app/assets/images/emoji/apple.png
new file mode 100644
index 00000000000..da650c60f62
--- /dev/null
+++ b/app/assets/images/emoji/apple.png
Binary files differ
diff --git a/app/assets/images/emoji/aquarius.png b/app/assets/images/emoji/aquarius.png
new file mode 100644
index 00000000000..641a4f68889
--- /dev/null
+++ b/app/assets/images/emoji/aquarius.png
Binary files differ
diff --git a/app/assets/images/emoji/aries.png b/app/assets/images/emoji/aries.png
new file mode 100644
index 00000000000..21a189d0ede
--- /dev/null
+++ b/app/assets/images/emoji/aries.png
Binary files differ
diff --git a/app/assets/images/emoji/arrow_backward.png b/app/assets/images/emoji/arrow_backward.png
new file mode 100644
index 00000000000..ee38e3b038e
--- /dev/null
+++ b/app/assets/images/emoji/arrow_backward.png
Binary files differ
diff --git a/app/assets/images/emoji/arrow_double_down.png b/app/assets/images/emoji/arrow_double_down.png
new file mode 100644
index 00000000000..90193bfcb40
--- /dev/null
+++ b/app/assets/images/emoji/arrow_double_down.png
Binary files differ
diff --git a/app/assets/images/emoji/arrow_double_up.png b/app/assets/images/emoji/arrow_double_up.png
new file mode 100644
index 00000000000..13543d5eef2
--- /dev/null
+++ b/app/assets/images/emoji/arrow_double_up.png
Binary files differ
diff --git a/app/assets/images/emoji/arrow_down.png b/app/assets/images/emoji/arrow_down.png
new file mode 100644
index 00000000000..b8eefd0b19f
--- /dev/null
+++ b/app/assets/images/emoji/arrow_down.png
Binary files differ
diff --git a/app/assets/images/emoji/arrow_down_small.png b/app/assets/images/emoji/arrow_down_small.png
new file mode 100644
index 00000000000..5870b9a2241
--- /dev/null
+++ b/app/assets/images/emoji/arrow_down_small.png
Binary files differ
diff --git a/app/assets/images/emoji/arrow_forward.png b/app/assets/images/emoji/arrow_forward.png
new file mode 100644
index 00000000000..4e2b682857c
--- /dev/null
+++ b/app/assets/images/emoji/arrow_forward.png
Binary files differ
diff --git a/app/assets/images/emoji/arrow_heading_down.png b/app/assets/images/emoji/arrow_heading_down.png
new file mode 100644
index 00000000000..2d9d24bca80
--- /dev/null
+++ b/app/assets/images/emoji/arrow_heading_down.png
Binary files differ
diff --git a/app/assets/images/emoji/arrow_heading_up.png b/app/assets/images/emoji/arrow_heading_up.png
new file mode 100644
index 00000000000..f29bfcfc0de
--- /dev/null
+++ b/app/assets/images/emoji/arrow_heading_up.png
Binary files differ
diff --git a/app/assets/images/emoji/arrow_left.png b/app/assets/images/emoji/arrow_left.png
new file mode 100644
index 00000000000..8c685e0a81b
--- /dev/null
+++ b/app/assets/images/emoji/arrow_left.png
Binary files differ
diff --git a/app/assets/images/emoji/arrow_lower_left.png b/app/assets/images/emoji/arrow_lower_left.png
new file mode 100644
index 00000000000..88b37716078
--- /dev/null
+++ b/app/assets/images/emoji/arrow_lower_left.png
Binary files differ
diff --git a/app/assets/images/emoji/arrow_lower_right.png b/app/assets/images/emoji/arrow_lower_right.png
new file mode 100644
index 00000000000..7e807da7392
--- /dev/null
+++ b/app/assets/images/emoji/arrow_lower_right.png
Binary files differ
diff --git a/app/assets/images/emoji/arrow_right.png b/app/assets/images/emoji/arrow_right.png
new file mode 100644
index 00000000000..4755670b5cc
--- /dev/null
+++ b/app/assets/images/emoji/arrow_right.png
Binary files differ
diff --git a/app/assets/images/emoji/arrow_right_hook.png b/app/assets/images/emoji/arrow_right_hook.png
new file mode 100644
index 00000000000..e7258ad3268
--- /dev/null
+++ b/app/assets/images/emoji/arrow_right_hook.png
Binary files differ
diff --git a/app/assets/images/emoji/arrow_up.png b/app/assets/images/emoji/arrow_up.png
new file mode 100644
index 00000000000..af8218a87f7
--- /dev/null
+++ b/app/assets/images/emoji/arrow_up.png
Binary files differ
diff --git a/app/assets/images/emoji/arrow_up_down.png b/app/assets/images/emoji/arrow_up_down.png
new file mode 100644
index 00000000000..dfa32b97186
--- /dev/null
+++ b/app/assets/images/emoji/arrow_up_down.png
Binary files differ
diff --git a/app/assets/images/emoji/arrow_up_small.png b/app/assets/images/emoji/arrow_up_small.png
new file mode 100644
index 00000000000..20a13dcd5cd
--- /dev/null
+++ b/app/assets/images/emoji/arrow_up_small.png
Binary files differ
diff --git a/app/assets/images/emoji/arrow_upper_left.png b/app/assets/images/emoji/arrow_upper_left.png
new file mode 100644
index 00000000000..f38718fbe34
--- /dev/null
+++ b/app/assets/images/emoji/arrow_upper_left.png
Binary files differ
diff --git a/app/assets/images/emoji/arrow_upper_right.png b/app/assets/images/emoji/arrow_upper_right.png
new file mode 100644
index 00000000000..c43e12d0f64
--- /dev/null
+++ b/app/assets/images/emoji/arrow_upper_right.png
Binary files differ
diff --git a/app/assets/images/emoji/arrows_clockwise.png b/app/assets/images/emoji/arrows_clockwise.png
new file mode 100644
index 00000000000..26e49c38388
--- /dev/null
+++ b/app/assets/images/emoji/arrows_clockwise.png
Binary files differ
diff --git a/app/assets/images/emoji/arrows_counterclockwise.png b/app/assets/images/emoji/arrows_counterclockwise.png
new file mode 100644
index 00000000000..8d06d8e0912
--- /dev/null
+++ b/app/assets/images/emoji/arrows_counterclockwise.png
Binary files differ
diff --git a/app/assets/images/emoji/art.png b/app/assets/images/emoji/art.png
new file mode 100644
index 00000000000..bd6afe9ff06
--- /dev/null
+++ b/app/assets/images/emoji/art.png
Binary files differ
diff --git a/app/assets/images/emoji/articulated_lorry.png b/app/assets/images/emoji/articulated_lorry.png
new file mode 100644
index 00000000000..c8217317132
--- /dev/null
+++ b/app/assets/images/emoji/articulated_lorry.png
Binary files differ
diff --git a/app/assets/images/emoji/asterisk.png b/app/assets/images/emoji/asterisk.png
new file mode 100644
index 00000000000..2f8e5113803
--- /dev/null
+++ b/app/assets/images/emoji/asterisk.png
Binary files differ
diff --git a/app/assets/images/emoji/astonished.png b/app/assets/images/emoji/astonished.png
new file mode 100644
index 00000000000..bd0ac55ec8e
--- /dev/null
+++ b/app/assets/images/emoji/astonished.png
Binary files differ
diff --git a/app/assets/images/emoji/athletic_shoe.png b/app/assets/images/emoji/athletic_shoe.png
new file mode 100644
index 00000000000..423fa07dd5d
--- /dev/null
+++ b/app/assets/images/emoji/athletic_shoe.png
Binary files differ
diff --git a/app/assets/images/emoji/atm.png b/app/assets/images/emoji/atm.png
new file mode 100644
index 00000000000..4d935307b94
--- /dev/null
+++ b/app/assets/images/emoji/atm.png
Binary files differ
diff --git a/app/assets/images/emoji/atom.png b/app/assets/images/emoji/atom.png
new file mode 100644
index 00000000000..5f4567aa093
--- /dev/null
+++ b/app/assets/images/emoji/atom.png
Binary files differ
diff --git a/app/assets/images/emoji/avocado.png b/app/assets/images/emoji/avocado.png
new file mode 100644
index 00000000000..06f0d124aed
--- /dev/null
+++ b/app/assets/images/emoji/avocado.png
Binary files differ
diff --git a/app/assets/images/emoji/b.png b/app/assets/images/emoji/b.png
new file mode 100644
index 00000000000..25875bc6a14
--- /dev/null
+++ b/app/assets/images/emoji/b.png
Binary files differ
diff --git a/app/assets/images/emoji/baby.png b/app/assets/images/emoji/baby.png
new file mode 100644
index 00000000000..a4af92c63c7
--- /dev/null
+++ b/app/assets/images/emoji/baby.png
Binary files differ
diff --git a/app/assets/images/emoji/baby_bottle.png b/app/assets/images/emoji/baby_bottle.png
new file mode 100644
index 00000000000..2bd10524180
--- /dev/null
+++ b/app/assets/images/emoji/baby_bottle.png
Binary files differ
diff --git a/app/assets/images/emoji/baby_chick.png b/app/assets/images/emoji/baby_chick.png
new file mode 100644
index 00000000000..dccd96576ea
--- /dev/null
+++ b/app/assets/images/emoji/baby_chick.png
Binary files differ
diff --git a/app/assets/images/emoji/baby_symbol.png b/app/assets/images/emoji/baby_symbol.png
new file mode 100644
index 00000000000..64a10b71710
--- /dev/null
+++ b/app/assets/images/emoji/baby_symbol.png
Binary files differ
diff --git a/app/assets/images/emoji/baby_tone1.png b/app/assets/images/emoji/baby_tone1.png
new file mode 100644
index 00000000000..d20911d40db
--- /dev/null
+++ b/app/assets/images/emoji/baby_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/baby_tone2.png b/app/assets/images/emoji/baby_tone2.png
new file mode 100644
index 00000000000..b0a9b30ed17
--- /dev/null
+++ b/app/assets/images/emoji/baby_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/baby_tone3.png b/app/assets/images/emoji/baby_tone3.png
new file mode 100644
index 00000000000..7de5286fac1
--- /dev/null
+++ b/app/assets/images/emoji/baby_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/baby_tone4.png b/app/assets/images/emoji/baby_tone4.png
new file mode 100644
index 00000000000..9b7a86ac615
--- /dev/null
+++ b/app/assets/images/emoji/baby_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/baby_tone5.png b/app/assets/images/emoji/baby_tone5.png
new file mode 100644
index 00000000000..fe1be34cb88
--- /dev/null
+++ b/app/assets/images/emoji/baby_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/back.png b/app/assets/images/emoji/back.png
new file mode 100644
index 00000000000..d32c5d4f17f
--- /dev/null
+++ b/app/assets/images/emoji/back.png
Binary files differ
diff --git a/app/assets/images/emoji/bacon.png b/app/assets/images/emoji/bacon.png
new file mode 100644
index 00000000000..f38a485fbe4
--- /dev/null
+++ b/app/assets/images/emoji/bacon.png
Binary files differ
diff --git a/app/assets/images/emoji/badminton.png b/app/assets/images/emoji/badminton.png
new file mode 100644
index 00000000000..7ba15708990
--- /dev/null
+++ b/app/assets/images/emoji/badminton.png
Binary files differ
diff --git a/app/assets/images/emoji/baggage_claim.png b/app/assets/images/emoji/baggage_claim.png
new file mode 100644
index 00000000000..409b593e78a
--- /dev/null
+++ b/app/assets/images/emoji/baggage_claim.png
Binary files differ
diff --git a/app/assets/images/emoji/balloon.png b/app/assets/images/emoji/balloon.png
new file mode 100644
index 00000000000..07916fe6df1
--- /dev/null
+++ b/app/assets/images/emoji/balloon.png
Binary files differ
diff --git a/app/assets/images/emoji/ballot_box.png b/app/assets/images/emoji/ballot_box.png
new file mode 100644
index 00000000000..9b6767aea9e
--- /dev/null
+++ b/app/assets/images/emoji/ballot_box.png
Binary files differ
diff --git a/app/assets/images/emoji/ballot_box_with_check.png b/app/assets/images/emoji/ballot_box_with_check.png
new file mode 100644
index 00000000000..284d9573847
--- /dev/null
+++ b/app/assets/images/emoji/ballot_box_with_check.png
Binary files differ
diff --git a/app/assets/images/emoji/bamboo.png b/app/assets/images/emoji/bamboo.png
new file mode 100644
index 00000000000..5d5e0e728a0
--- /dev/null
+++ b/app/assets/images/emoji/bamboo.png
Binary files differ
diff --git a/app/assets/images/emoji/banana.png b/app/assets/images/emoji/banana.png
new file mode 100644
index 00000000000..f4987279580
--- /dev/null
+++ b/app/assets/images/emoji/banana.png
Binary files differ
diff --git a/app/assets/images/emoji/bangbang.png b/app/assets/images/emoji/bangbang.png
new file mode 100644
index 00000000000..58a9c528fca
--- /dev/null
+++ b/app/assets/images/emoji/bangbang.png
Binary files differ
diff --git a/app/assets/images/emoji/bank.png b/app/assets/images/emoji/bank.png
new file mode 100644
index 00000000000..dffdcef36a1
--- /dev/null
+++ b/app/assets/images/emoji/bank.png
Binary files differ
diff --git a/app/assets/images/emoji/bar_chart.png b/app/assets/images/emoji/bar_chart.png
new file mode 100644
index 00000000000..53c89455008
--- /dev/null
+++ b/app/assets/images/emoji/bar_chart.png
Binary files differ
diff --git a/app/assets/images/emoji/barber.png b/app/assets/images/emoji/barber.png
new file mode 100644
index 00000000000..896f4d716cf
--- /dev/null
+++ b/app/assets/images/emoji/barber.png
Binary files differ
diff --git a/app/assets/images/emoji/baseball.png b/app/assets/images/emoji/baseball.png
new file mode 100644
index 00000000000..f8463f1538b
--- /dev/null
+++ b/app/assets/images/emoji/baseball.png
Binary files differ
diff --git a/app/assets/images/emoji/basketball.png b/app/assets/images/emoji/basketball.png
new file mode 100644
index 00000000000..64c76b79c6d
--- /dev/null
+++ b/app/assets/images/emoji/basketball.png
Binary files differ
diff --git a/app/assets/images/emoji/basketball_player.png b/app/assets/images/emoji/basketball_player.png
new file mode 100644
index 00000000000..8ce90c5cad6
--- /dev/null
+++ b/app/assets/images/emoji/basketball_player.png
Binary files differ
diff --git a/app/assets/images/emoji/basketball_player_tone1.png b/app/assets/images/emoji/basketball_player_tone1.png
new file mode 100644
index 00000000000..cd12c7ab9bf
--- /dev/null
+++ b/app/assets/images/emoji/basketball_player_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/basketball_player_tone2.png b/app/assets/images/emoji/basketball_player_tone2.png
new file mode 100644
index 00000000000..f892fd596da
--- /dev/null
+++ b/app/assets/images/emoji/basketball_player_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/basketball_player_tone3.png b/app/assets/images/emoji/basketball_player_tone3.png
new file mode 100644
index 00000000000..e109997a91a
--- /dev/null
+++ b/app/assets/images/emoji/basketball_player_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/basketball_player_tone4.png b/app/assets/images/emoji/basketball_player_tone4.png
new file mode 100644
index 00000000000..3b90b946af4
--- /dev/null
+++ b/app/assets/images/emoji/basketball_player_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/basketball_player_tone5.png b/app/assets/images/emoji/basketball_player_tone5.png
new file mode 100644
index 00000000000..bafed7828a7
--- /dev/null
+++ b/app/assets/images/emoji/basketball_player_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/bat.png b/app/assets/images/emoji/bat.png
new file mode 100644
index 00000000000..3152c047e00
--- /dev/null
+++ b/app/assets/images/emoji/bat.png
Binary files differ
diff --git a/app/assets/images/emoji/bath.png b/app/assets/images/emoji/bath.png
new file mode 100644
index 00000000000..43fba5c8a28
--- /dev/null
+++ b/app/assets/images/emoji/bath.png
Binary files differ
diff --git a/app/assets/images/emoji/bath_tone1.png b/app/assets/images/emoji/bath_tone1.png
new file mode 100644
index 00000000000..2152eabf2f5
--- /dev/null
+++ b/app/assets/images/emoji/bath_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/bath_tone2.png b/app/assets/images/emoji/bath_tone2.png
new file mode 100644
index 00000000000..2102e6133e3
--- /dev/null
+++ b/app/assets/images/emoji/bath_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/bath_tone3.png b/app/assets/images/emoji/bath_tone3.png
new file mode 100644
index 00000000000..fae66181e9f
--- /dev/null
+++ b/app/assets/images/emoji/bath_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/bath_tone4.png b/app/assets/images/emoji/bath_tone4.png
new file mode 100644
index 00000000000..1f8959d0d99
--- /dev/null
+++ b/app/assets/images/emoji/bath_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/bath_tone5.png b/app/assets/images/emoji/bath_tone5.png
new file mode 100644
index 00000000000..c8a08e84f25
--- /dev/null
+++ b/app/assets/images/emoji/bath_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/bathtub.png b/app/assets/images/emoji/bathtub.png
new file mode 100644
index 00000000000..9a5f09361eb
--- /dev/null
+++ b/app/assets/images/emoji/bathtub.png
Binary files differ
diff --git a/app/assets/images/emoji/battery.png b/app/assets/images/emoji/battery.png
new file mode 100644
index 00000000000..f593e2bdb65
--- /dev/null
+++ b/app/assets/images/emoji/battery.png
Binary files differ
diff --git a/app/assets/images/emoji/beach.png b/app/assets/images/emoji/beach.png
new file mode 100644
index 00000000000..69108c8ea10
--- /dev/null
+++ b/app/assets/images/emoji/beach.png
Binary files differ
diff --git a/app/assets/images/emoji/beach_umbrella.png b/app/assets/images/emoji/beach_umbrella.png
new file mode 100644
index 00000000000..220a74f8132
--- /dev/null
+++ b/app/assets/images/emoji/beach_umbrella.png
Binary files differ
diff --git a/app/assets/images/emoji/bear.png b/app/assets/images/emoji/bear.png
new file mode 100644
index 00000000000..272d56bbbcc
--- /dev/null
+++ b/app/assets/images/emoji/bear.png
Binary files differ
diff --git a/app/assets/images/emoji/bed.png b/app/assets/images/emoji/bed.png
new file mode 100644
index 00000000000..86f964e245d
--- /dev/null
+++ b/app/assets/images/emoji/bed.png
Binary files differ
diff --git a/app/assets/images/emoji/bee.png b/app/assets/images/emoji/bee.png
new file mode 100644
index 00000000000..46156060096
--- /dev/null
+++ b/app/assets/images/emoji/bee.png
Binary files differ
diff --git a/app/assets/images/emoji/beer.png b/app/assets/images/emoji/beer.png
new file mode 100644
index 00000000000..b6d73dc0b7a
--- /dev/null
+++ b/app/assets/images/emoji/beer.png
Binary files differ
diff --git a/app/assets/images/emoji/beers.png b/app/assets/images/emoji/beers.png
new file mode 100644
index 00000000000..b55deb66b41
--- /dev/null
+++ b/app/assets/images/emoji/beers.png
Binary files differ
diff --git a/app/assets/images/emoji/beetle.png b/app/assets/images/emoji/beetle.png
new file mode 100644
index 00000000000..3d93174d7fc
--- /dev/null
+++ b/app/assets/images/emoji/beetle.png
Binary files differ
diff --git a/app/assets/images/emoji/beginner.png b/app/assets/images/emoji/beginner.png
new file mode 100644
index 00000000000..bc434fb7cb5
--- /dev/null
+++ b/app/assets/images/emoji/beginner.png
Binary files differ
diff --git a/app/assets/images/emoji/bell.png b/app/assets/images/emoji/bell.png
new file mode 100644
index 00000000000..5b3b0461999
--- /dev/null
+++ b/app/assets/images/emoji/bell.png
Binary files differ
diff --git a/app/assets/images/emoji/bellhop.png b/app/assets/images/emoji/bellhop.png
new file mode 100644
index 00000000000..6b3297ceaf7
--- /dev/null
+++ b/app/assets/images/emoji/bellhop.png
Binary files differ
diff --git a/app/assets/images/emoji/bento.png b/app/assets/images/emoji/bento.png
new file mode 100644
index 00000000000..83d41ca7eb9
--- /dev/null
+++ b/app/assets/images/emoji/bento.png
Binary files differ
diff --git a/app/assets/images/emoji/bicyclist.png b/app/assets/images/emoji/bicyclist.png
new file mode 100644
index 00000000000..9274da11048
--- /dev/null
+++ b/app/assets/images/emoji/bicyclist.png
Binary files differ
diff --git a/app/assets/images/emoji/bicyclist_tone1.png b/app/assets/images/emoji/bicyclist_tone1.png
new file mode 100644
index 00000000000..decc2f728fe
--- /dev/null
+++ b/app/assets/images/emoji/bicyclist_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/bicyclist_tone2.png b/app/assets/images/emoji/bicyclist_tone2.png
new file mode 100644
index 00000000000..0067717b80a
--- /dev/null
+++ b/app/assets/images/emoji/bicyclist_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/bicyclist_tone3.png b/app/assets/images/emoji/bicyclist_tone3.png
new file mode 100644
index 00000000000..a4f7b5e2776
--- /dev/null
+++ b/app/assets/images/emoji/bicyclist_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/bicyclist_tone4.png b/app/assets/images/emoji/bicyclist_tone4.png
new file mode 100644
index 00000000000..a3c8a797db4
--- /dev/null
+++ b/app/assets/images/emoji/bicyclist_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/bicyclist_tone5.png b/app/assets/images/emoji/bicyclist_tone5.png
new file mode 100644
index 00000000000..1606a874051
--- /dev/null
+++ b/app/assets/images/emoji/bicyclist_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/bike.png b/app/assets/images/emoji/bike.png
new file mode 100644
index 00000000000..556ed70f1a7
--- /dev/null
+++ b/app/assets/images/emoji/bike.png
Binary files differ
diff --git a/app/assets/images/emoji/bikini.png b/app/assets/images/emoji/bikini.png
new file mode 100644
index 00000000000..77a8a0aae5b
--- /dev/null
+++ b/app/assets/images/emoji/bikini.png
Binary files differ
diff --git a/app/assets/images/emoji/biohazard.png b/app/assets/images/emoji/biohazard.png
new file mode 100644
index 00000000000..007b4fc2d85
--- /dev/null
+++ b/app/assets/images/emoji/biohazard.png
Binary files differ
diff --git a/app/assets/images/emoji/bird.png b/app/assets/images/emoji/bird.png
new file mode 100644
index 00000000000..e201c22be33
--- /dev/null
+++ b/app/assets/images/emoji/bird.png
Binary files differ
diff --git a/app/assets/images/emoji/birthday.png b/app/assets/images/emoji/birthday.png
new file mode 100644
index 00000000000..317e9a41949
--- /dev/null
+++ b/app/assets/images/emoji/birthday.png
Binary files differ
diff --git a/app/assets/images/emoji/black_circle.png b/app/assets/images/emoji/black_circle.png
new file mode 100644
index 00000000000..b62b87170e8
--- /dev/null
+++ b/app/assets/images/emoji/black_circle.png
Binary files differ
diff --git a/app/assets/images/emoji/black_heart.png b/app/assets/images/emoji/black_heart.png
new file mode 100644
index 00000000000..b4068c3e6e8
--- /dev/null
+++ b/app/assets/images/emoji/black_heart.png
Binary files differ
diff --git a/app/assets/images/emoji/black_joker.png b/app/assets/images/emoji/black_joker.png
new file mode 100644
index 00000000000..3d0924b68aa
--- /dev/null
+++ b/app/assets/images/emoji/black_joker.png
Binary files differ
diff --git a/app/assets/images/emoji/black_large_square.png b/app/assets/images/emoji/black_large_square.png
new file mode 100644
index 00000000000..162f2bb4290
--- /dev/null
+++ b/app/assets/images/emoji/black_large_square.png
Binary files differ
diff --git a/app/assets/images/emoji/black_medium_small_square.png b/app/assets/images/emoji/black_medium_small_square.png
new file mode 100644
index 00000000000..39765bba610
--- /dev/null
+++ b/app/assets/images/emoji/black_medium_small_square.png
Binary files differ
diff --git a/app/assets/images/emoji/black_medium_square.png b/app/assets/images/emoji/black_medium_square.png
new file mode 100644
index 00000000000..05a30a6aa2d
--- /dev/null
+++ b/app/assets/images/emoji/black_medium_square.png
Binary files differ
diff --git a/app/assets/images/emoji/black_nib.png b/app/assets/images/emoji/black_nib.png
new file mode 100644
index 00000000000..872d0ae1598
--- /dev/null
+++ b/app/assets/images/emoji/black_nib.png
Binary files differ
diff --git a/app/assets/images/emoji/black_small_square.png b/app/assets/images/emoji/black_small_square.png
new file mode 100644
index 00000000000..48595d3e1a9
--- /dev/null
+++ b/app/assets/images/emoji/black_small_square.png
Binary files differ
diff --git a/app/assets/images/emoji/black_square_button.png b/app/assets/images/emoji/black_square_button.png
new file mode 100644
index 00000000000..a78fc2f6b63
--- /dev/null
+++ b/app/assets/images/emoji/black_square_button.png
Binary files differ
diff --git a/app/assets/images/emoji/blossom.png b/app/assets/images/emoji/blossom.png
new file mode 100644
index 00000000000..4083026c157
--- /dev/null
+++ b/app/assets/images/emoji/blossom.png
Binary files differ
diff --git a/app/assets/images/emoji/blowfish.png b/app/assets/images/emoji/blowfish.png
new file mode 100644
index 00000000000..a10f4f84e35
--- /dev/null
+++ b/app/assets/images/emoji/blowfish.png
Binary files differ
diff --git a/app/assets/images/emoji/blue_book.png b/app/assets/images/emoji/blue_book.png
new file mode 100644
index 00000000000..e1e455401cc
--- /dev/null
+++ b/app/assets/images/emoji/blue_book.png
Binary files differ
diff --git a/app/assets/images/emoji/blue_car.png b/app/assets/images/emoji/blue_car.png
new file mode 100644
index 00000000000..e8ba817d393
--- /dev/null
+++ b/app/assets/images/emoji/blue_car.png
Binary files differ
diff --git a/app/assets/images/emoji/blue_heart.png b/app/assets/images/emoji/blue_heart.png
new file mode 100644
index 00000000000..bdf1287e55e
--- /dev/null
+++ b/app/assets/images/emoji/blue_heart.png
Binary files differ
diff --git a/app/assets/images/emoji/blush.png b/app/assets/images/emoji/blush.png
new file mode 100644
index 00000000000..aac1a424ad4
--- /dev/null
+++ b/app/assets/images/emoji/blush.png
Binary files differ
diff --git a/app/assets/images/emoji/boar.png b/app/assets/images/emoji/boar.png
new file mode 100644
index 00000000000..fead972633c
--- /dev/null
+++ b/app/assets/images/emoji/boar.png
Binary files differ
diff --git a/app/assets/images/emoji/bomb.png b/app/assets/images/emoji/bomb.png
new file mode 100644
index 00000000000..c7f8f81c939
--- /dev/null
+++ b/app/assets/images/emoji/bomb.png
Binary files differ
diff --git a/app/assets/images/emoji/book.png b/app/assets/images/emoji/book.png
new file mode 100644
index 00000000000..0f4447ed396
--- /dev/null
+++ b/app/assets/images/emoji/book.png
Binary files differ
diff --git a/app/assets/images/emoji/bookmark.png b/app/assets/images/emoji/bookmark.png
new file mode 100644
index 00000000000..bbb444611f0
--- /dev/null
+++ b/app/assets/images/emoji/bookmark.png
Binary files differ
diff --git a/app/assets/images/emoji/bookmark_tabs.png b/app/assets/images/emoji/bookmark_tabs.png
new file mode 100644
index 00000000000..f8d9e01b428
--- /dev/null
+++ b/app/assets/images/emoji/bookmark_tabs.png
Binary files differ
diff --git a/app/assets/images/emoji/books.png b/app/assets/images/emoji/books.png
new file mode 100644
index 00000000000..59a8bafeb0d
--- /dev/null
+++ b/app/assets/images/emoji/books.png
Binary files differ
diff --git a/app/assets/images/emoji/boom.png b/app/assets/images/emoji/boom.png
new file mode 100644
index 00000000000..9b0f027b1a8
--- /dev/null
+++ b/app/assets/images/emoji/boom.png
Binary files differ
diff --git a/app/assets/images/emoji/boot.png b/app/assets/images/emoji/boot.png
new file mode 100644
index 00000000000..11f1065ed07
--- /dev/null
+++ b/app/assets/images/emoji/boot.png
Binary files differ
diff --git a/app/assets/images/emoji/bouquet.png b/app/assets/images/emoji/bouquet.png
new file mode 100644
index 00000000000..11455af6df4
--- /dev/null
+++ b/app/assets/images/emoji/bouquet.png
Binary files differ
diff --git a/app/assets/images/emoji/bow.png b/app/assets/images/emoji/bow.png
new file mode 100644
index 00000000000..d8f793088dc
--- /dev/null
+++ b/app/assets/images/emoji/bow.png
Binary files differ
diff --git a/app/assets/images/emoji/bow_and_arrow.png b/app/assets/images/emoji/bow_and_arrow.png
new file mode 100644
index 00000000000..6a538bf475f
--- /dev/null
+++ b/app/assets/images/emoji/bow_and_arrow.png
Binary files differ
diff --git a/app/assets/images/emoji/bow_tone1.png b/app/assets/images/emoji/bow_tone1.png
new file mode 100644
index 00000000000..87afb7b54cf
--- /dev/null
+++ b/app/assets/images/emoji/bow_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/bow_tone2.png b/app/assets/images/emoji/bow_tone2.png
new file mode 100644
index 00000000000..3ccf7dc0850
--- /dev/null
+++ b/app/assets/images/emoji/bow_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/bow_tone3.png b/app/assets/images/emoji/bow_tone3.png
new file mode 100644
index 00000000000..8b9eb64f926
--- /dev/null
+++ b/app/assets/images/emoji/bow_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/bow_tone4.png b/app/assets/images/emoji/bow_tone4.png
new file mode 100644
index 00000000000..683795ff40d
--- /dev/null
+++ b/app/assets/images/emoji/bow_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/bow_tone5.png b/app/assets/images/emoji/bow_tone5.png
new file mode 100644
index 00000000000..7969d971752
--- /dev/null
+++ b/app/assets/images/emoji/bow_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/bowling.png b/app/assets/images/emoji/bowling.png
new file mode 100644
index 00000000000..63add89e53b
--- /dev/null
+++ b/app/assets/images/emoji/bowling.png
Binary files differ
diff --git a/app/assets/images/emoji/boxing_glove.png b/app/assets/images/emoji/boxing_glove.png
new file mode 100644
index 00000000000..9838f24e51a
--- /dev/null
+++ b/app/assets/images/emoji/boxing_glove.png
Binary files differ
diff --git a/app/assets/images/emoji/boy.png b/app/assets/images/emoji/boy.png
new file mode 100644
index 00000000000..8ecfb0a4e92
--- /dev/null
+++ b/app/assets/images/emoji/boy.png
Binary files differ
diff --git a/app/assets/images/emoji/boy_tone1.png b/app/assets/images/emoji/boy_tone1.png
new file mode 100644
index 00000000000..2fc436ea512
--- /dev/null
+++ b/app/assets/images/emoji/boy_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/boy_tone2.png b/app/assets/images/emoji/boy_tone2.png
new file mode 100644
index 00000000000..09a5f18d360
--- /dev/null
+++ b/app/assets/images/emoji/boy_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/boy_tone3.png b/app/assets/images/emoji/boy_tone3.png
new file mode 100644
index 00000000000..3cfe675dd3a
--- /dev/null
+++ b/app/assets/images/emoji/boy_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/boy_tone4.png b/app/assets/images/emoji/boy_tone4.png
new file mode 100644
index 00000000000..780be0ace36
--- /dev/null
+++ b/app/assets/images/emoji/boy_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/boy_tone5.png b/app/assets/images/emoji/boy_tone5.png
new file mode 100644
index 00000000000..f32fe22e35c
--- /dev/null
+++ b/app/assets/images/emoji/boy_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/bread.png b/app/assets/images/emoji/bread.png
new file mode 100644
index 00000000000..6676510aaa5
--- /dev/null
+++ b/app/assets/images/emoji/bread.png
Binary files differ
diff --git a/app/assets/images/emoji/bride_with_veil.png b/app/assets/images/emoji/bride_with_veil.png
new file mode 100644
index 00000000000..eaf4bd97890
--- /dev/null
+++ b/app/assets/images/emoji/bride_with_veil.png
Binary files differ
diff --git a/app/assets/images/emoji/bride_with_veil_tone1.png b/app/assets/images/emoji/bride_with_veil_tone1.png
new file mode 100644
index 00000000000..c4fb141ae8f
--- /dev/null
+++ b/app/assets/images/emoji/bride_with_veil_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/bride_with_veil_tone2.png b/app/assets/images/emoji/bride_with_veil_tone2.png
new file mode 100644
index 00000000000..c248769fc06
--- /dev/null
+++ b/app/assets/images/emoji/bride_with_veil_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/bride_with_veil_tone3.png b/app/assets/images/emoji/bride_with_veil_tone3.png
new file mode 100644
index 00000000000..962c0a6eedb
--- /dev/null
+++ b/app/assets/images/emoji/bride_with_veil_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/bride_with_veil_tone4.png b/app/assets/images/emoji/bride_with_veil_tone4.png
new file mode 100644
index 00000000000..740ca208cd4
--- /dev/null
+++ b/app/assets/images/emoji/bride_with_veil_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/bride_with_veil_tone5.png b/app/assets/images/emoji/bride_with_veil_tone5.png
new file mode 100644
index 00000000000..5cc5598587d
--- /dev/null
+++ b/app/assets/images/emoji/bride_with_veil_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/bridge_at_night.png b/app/assets/images/emoji/bridge_at_night.png
new file mode 100644
index 00000000000..1d444e0be65
--- /dev/null
+++ b/app/assets/images/emoji/bridge_at_night.png
Binary files differ
diff --git a/app/assets/images/emoji/briefcase.png b/app/assets/images/emoji/briefcase.png
new file mode 100644
index 00000000000..b9912ba2148
--- /dev/null
+++ b/app/assets/images/emoji/briefcase.png
Binary files differ
diff --git a/app/assets/images/emoji/broken_heart.png b/app/assets/images/emoji/broken_heart.png
new file mode 100644
index 00000000000..718e26ee122
--- /dev/null
+++ b/app/assets/images/emoji/broken_heart.png
Binary files differ
diff --git a/app/assets/images/emoji/bug.png b/app/assets/images/emoji/bug.png
new file mode 100644
index 00000000000..e64e72f259a
--- /dev/null
+++ b/app/assets/images/emoji/bug.png
Binary files differ
diff --git a/app/assets/images/emoji/bulb.png b/app/assets/images/emoji/bulb.png
new file mode 100644
index 00000000000..38e32e02d9f
--- /dev/null
+++ b/app/assets/images/emoji/bulb.png
Binary files differ
diff --git a/app/assets/images/emoji/bullettrain_front.png b/app/assets/images/emoji/bullettrain_front.png
new file mode 100644
index 00000000000..4f698e056fa
--- /dev/null
+++ b/app/assets/images/emoji/bullettrain_front.png
Binary files differ
diff --git a/app/assets/images/emoji/bullettrain_side.png b/app/assets/images/emoji/bullettrain_side.png
new file mode 100644
index 00000000000..ed61c67bf07
--- /dev/null
+++ b/app/assets/images/emoji/bullettrain_side.png
Binary files differ
diff --git a/app/assets/images/emoji/burrito.png b/app/assets/images/emoji/burrito.png
new file mode 100644
index 00000000000..02bd5601df7
--- /dev/null
+++ b/app/assets/images/emoji/burrito.png
Binary files differ
diff --git a/app/assets/images/emoji/bus.png b/app/assets/images/emoji/bus.png
new file mode 100644
index 00000000000..641ddc56ca7
--- /dev/null
+++ b/app/assets/images/emoji/bus.png
Binary files differ
diff --git a/app/assets/images/emoji/busstop.png b/app/assets/images/emoji/busstop.png
new file mode 100644
index 00000000000..b2b62208bfd
--- /dev/null
+++ b/app/assets/images/emoji/busstop.png
Binary files differ
diff --git a/app/assets/images/emoji/bust_in_silhouette.png b/app/assets/images/emoji/bust_in_silhouette.png
new file mode 100644
index 00000000000..123b2cbe1fb
--- /dev/null
+++ b/app/assets/images/emoji/bust_in_silhouette.png
Binary files differ
diff --git a/app/assets/images/emoji/busts_in_silhouette.png b/app/assets/images/emoji/busts_in_silhouette.png
new file mode 100644
index 00000000000..d7656860a1c
--- /dev/null
+++ b/app/assets/images/emoji/busts_in_silhouette.png
Binary files differ
diff --git a/app/assets/images/emoji/butterfly.png b/app/assets/images/emoji/butterfly.png
new file mode 100644
index 00000000000..5631fe99226
--- /dev/null
+++ b/app/assets/images/emoji/butterfly.png
Binary files differ
diff --git a/app/assets/images/emoji/cactus.png b/app/assets/images/emoji/cactus.png
new file mode 100644
index 00000000000..9b48ccf3d0c
--- /dev/null
+++ b/app/assets/images/emoji/cactus.png
Binary files differ
diff --git a/app/assets/images/emoji/cake.png b/app/assets/images/emoji/cake.png
new file mode 100644
index 00000000000..4368177be9a
--- /dev/null
+++ b/app/assets/images/emoji/cake.png
Binary files differ
diff --git a/app/assets/images/emoji/calendar.png b/app/assets/images/emoji/calendar.png
new file mode 100644
index 00000000000..47353b74447
--- /dev/null
+++ b/app/assets/images/emoji/calendar.png
Binary files differ
diff --git a/app/assets/images/emoji/calendar_spiral.png b/app/assets/images/emoji/calendar_spiral.png
new file mode 100644
index 00000000000..dec8d49bfa8
--- /dev/null
+++ b/app/assets/images/emoji/calendar_spiral.png
Binary files differ
diff --git a/app/assets/images/emoji/call_me.png b/app/assets/images/emoji/call_me.png
new file mode 100644
index 00000000000..a10c59ba711
--- /dev/null
+++ b/app/assets/images/emoji/call_me.png
Binary files differ
diff --git a/app/assets/images/emoji/call_me_tone1.png b/app/assets/images/emoji/call_me_tone1.png
new file mode 100644
index 00000000000..2c93201181a
--- /dev/null
+++ b/app/assets/images/emoji/call_me_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/call_me_tone2.png b/app/assets/images/emoji/call_me_tone2.png
new file mode 100644
index 00000000000..c39f45a41ed
--- /dev/null
+++ b/app/assets/images/emoji/call_me_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/call_me_tone3.png b/app/assets/images/emoji/call_me_tone3.png
new file mode 100644
index 00000000000..83a57f63c29
--- /dev/null
+++ b/app/assets/images/emoji/call_me_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/call_me_tone4.png b/app/assets/images/emoji/call_me_tone4.png
new file mode 100644
index 00000000000..65b3468fe44
--- /dev/null
+++ b/app/assets/images/emoji/call_me_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/call_me_tone5.png b/app/assets/images/emoji/call_me_tone5.png
new file mode 100644
index 00000000000..94ef68ff3b3
--- /dev/null
+++ b/app/assets/images/emoji/call_me_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/calling.png b/app/assets/images/emoji/calling.png
new file mode 100644
index 00000000000..e2f308f8e46
--- /dev/null
+++ b/app/assets/images/emoji/calling.png
Binary files differ
diff --git a/app/assets/images/emoji/camel.png b/app/assets/images/emoji/camel.png
new file mode 100644
index 00000000000..b421d07a805
--- /dev/null
+++ b/app/assets/images/emoji/camel.png
Binary files differ
diff --git a/app/assets/images/emoji/camera.png b/app/assets/images/emoji/camera.png
new file mode 100644
index 00000000000..0a3429f72ef
--- /dev/null
+++ b/app/assets/images/emoji/camera.png
Binary files differ
diff --git a/app/assets/images/emoji/camera_with_flash.png b/app/assets/images/emoji/camera_with_flash.png
new file mode 100644
index 00000000000..27471da2029
--- /dev/null
+++ b/app/assets/images/emoji/camera_with_flash.png
Binary files differ
diff --git a/app/assets/images/emoji/camping.png b/app/assets/images/emoji/camping.png
new file mode 100644
index 00000000000..d589cc1f44b
--- /dev/null
+++ b/app/assets/images/emoji/camping.png
Binary files differ
diff --git a/app/assets/images/emoji/cancer.png b/app/assets/images/emoji/cancer.png
new file mode 100644
index 00000000000..a64af07cb5f
--- /dev/null
+++ b/app/assets/images/emoji/cancer.png
Binary files differ
diff --git a/app/assets/images/emoji/candle.png b/app/assets/images/emoji/candle.png
new file mode 100644
index 00000000000..0b56444e355
--- /dev/null
+++ b/app/assets/images/emoji/candle.png
Binary files differ
diff --git a/app/assets/images/emoji/candy.png b/app/assets/images/emoji/candy.png
new file mode 100644
index 00000000000..8c67ace3a35
--- /dev/null
+++ b/app/assets/images/emoji/candy.png
Binary files differ
diff --git a/app/assets/images/emoji/canoe.png b/app/assets/images/emoji/canoe.png
new file mode 100644
index 00000000000..e26cdb9da69
--- /dev/null
+++ b/app/assets/images/emoji/canoe.png
Binary files differ
diff --git a/app/assets/images/emoji/capital_abcd.png b/app/assets/images/emoji/capital_abcd.png
new file mode 100644
index 00000000000..fe9482d2d8a
--- /dev/null
+++ b/app/assets/images/emoji/capital_abcd.png
Binary files differ
diff --git a/app/assets/images/emoji/capricorn.png b/app/assets/images/emoji/capricorn.png
new file mode 100644
index 00000000000..6293d31d4b1
--- /dev/null
+++ b/app/assets/images/emoji/capricorn.png
Binary files differ
diff --git a/app/assets/images/emoji/card_box.png b/app/assets/images/emoji/card_box.png
new file mode 100644
index 00000000000..f2e764ce59d
--- /dev/null
+++ b/app/assets/images/emoji/card_box.png
Binary files differ
diff --git a/app/assets/images/emoji/card_index.png b/app/assets/images/emoji/card_index.png
new file mode 100644
index 00000000000..151e11cb3b4
--- /dev/null
+++ b/app/assets/images/emoji/card_index.png
Binary files differ
diff --git a/app/assets/images/emoji/carousel_horse.png b/app/assets/images/emoji/carousel_horse.png
new file mode 100644
index 00000000000..a17074edf05
--- /dev/null
+++ b/app/assets/images/emoji/carousel_horse.png
Binary files differ
diff --git a/app/assets/images/emoji/carrot.png b/app/assets/images/emoji/carrot.png
new file mode 100644
index 00000000000..c68829b58e7
--- /dev/null
+++ b/app/assets/images/emoji/carrot.png
Binary files differ
diff --git a/app/assets/images/emoji/cartwheel.png b/app/assets/images/emoji/cartwheel.png
new file mode 100644
index 00000000000..cbcaa578253
--- /dev/null
+++ b/app/assets/images/emoji/cartwheel.png
Binary files differ
diff --git a/app/assets/images/emoji/cartwheel_tone1.png b/app/assets/images/emoji/cartwheel_tone1.png
new file mode 100644
index 00000000000..db6d65895fb
--- /dev/null
+++ b/app/assets/images/emoji/cartwheel_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/cartwheel_tone2.png b/app/assets/images/emoji/cartwheel_tone2.png
new file mode 100644
index 00000000000..e00ffbc27a8
--- /dev/null
+++ b/app/assets/images/emoji/cartwheel_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/cartwheel_tone3.png b/app/assets/images/emoji/cartwheel_tone3.png
new file mode 100644
index 00000000000..49321be391f
--- /dev/null
+++ b/app/assets/images/emoji/cartwheel_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/cartwheel_tone4.png b/app/assets/images/emoji/cartwheel_tone4.png
new file mode 100644
index 00000000000..d4562b5e3dd
--- /dev/null
+++ b/app/assets/images/emoji/cartwheel_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/cartwheel_tone5.png b/app/assets/images/emoji/cartwheel_tone5.png
new file mode 100644
index 00000000000..6e09a870767
--- /dev/null
+++ b/app/assets/images/emoji/cartwheel_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/cat.png b/app/assets/images/emoji/cat.png
new file mode 100644
index 00000000000..efd82c2abf3
--- /dev/null
+++ b/app/assets/images/emoji/cat.png
Binary files differ
diff --git a/app/assets/images/emoji/cat2.png b/app/assets/images/emoji/cat2.png
new file mode 100644
index 00000000000..46abe8cbc14
--- /dev/null
+++ b/app/assets/images/emoji/cat2.png
Binary files differ
diff --git a/app/assets/images/emoji/cd.png b/app/assets/images/emoji/cd.png
new file mode 100644
index 00000000000..e6b01449cd9
--- /dev/null
+++ b/app/assets/images/emoji/cd.png
Binary files differ
diff --git a/app/assets/images/emoji/chains.png b/app/assets/images/emoji/chains.png
new file mode 100644
index 00000000000..57f46139a06
--- /dev/null
+++ b/app/assets/images/emoji/chains.png
Binary files differ
diff --git a/app/assets/images/emoji/champagne.png b/app/assets/images/emoji/champagne.png
new file mode 100644
index 00000000000..285a79a93d0
--- /dev/null
+++ b/app/assets/images/emoji/champagne.png
Binary files differ
diff --git a/app/assets/images/emoji/champagne_glass.png b/app/assets/images/emoji/champagne_glass.png
new file mode 100644
index 00000000000..31937ae9392
--- /dev/null
+++ b/app/assets/images/emoji/champagne_glass.png
Binary files differ
diff --git a/app/assets/images/emoji/chart.png b/app/assets/images/emoji/chart.png
new file mode 100644
index 00000000000..9773f03be22
--- /dev/null
+++ b/app/assets/images/emoji/chart.png
Binary files differ
diff --git a/app/assets/images/emoji/chart_with_downwards_trend.png b/app/assets/images/emoji/chart_with_downwards_trend.png
new file mode 100644
index 00000000000..5222ec72d85
--- /dev/null
+++ b/app/assets/images/emoji/chart_with_downwards_trend.png
Binary files differ
diff --git a/app/assets/images/emoji/chart_with_upwards_trend.png b/app/assets/images/emoji/chart_with_upwards_trend.png
new file mode 100644
index 00000000000..f13cfcf9956
--- /dev/null
+++ b/app/assets/images/emoji/chart_with_upwards_trend.png
Binary files differ
diff --git a/app/assets/images/emoji/checkered_flag.png b/app/assets/images/emoji/checkered_flag.png
new file mode 100644
index 00000000000..5a71eecb89b
--- /dev/null
+++ b/app/assets/images/emoji/checkered_flag.png
Binary files differ
diff --git a/app/assets/images/emoji/cheese.png b/app/assets/images/emoji/cheese.png
new file mode 100644
index 00000000000..00e99762286
--- /dev/null
+++ b/app/assets/images/emoji/cheese.png
Binary files differ
diff --git a/app/assets/images/emoji/cherries.png b/app/assets/images/emoji/cherries.png
new file mode 100644
index 00000000000..9b10cbaac5e
--- /dev/null
+++ b/app/assets/images/emoji/cherries.png
Binary files differ
diff --git a/app/assets/images/emoji/cherry_blossom.png b/app/assets/images/emoji/cherry_blossom.png
new file mode 100644
index 00000000000..282f3e7bc81
--- /dev/null
+++ b/app/assets/images/emoji/cherry_blossom.png
Binary files differ
diff --git a/app/assets/images/emoji/chestnut.png b/app/assets/images/emoji/chestnut.png
new file mode 100644
index 00000000000..e9fb40468ed
--- /dev/null
+++ b/app/assets/images/emoji/chestnut.png
Binary files differ
diff --git a/app/assets/images/emoji/chicken.png b/app/assets/images/emoji/chicken.png
new file mode 100644
index 00000000000..9a6992e55ba
--- /dev/null
+++ b/app/assets/images/emoji/chicken.png
Binary files differ
diff --git a/app/assets/images/emoji/children_crossing.png b/app/assets/images/emoji/children_crossing.png
new file mode 100644
index 00000000000..fa4c091c7c3
--- /dev/null
+++ b/app/assets/images/emoji/children_crossing.png
Binary files differ
diff --git a/app/assets/images/emoji/chipmunk.png b/app/assets/images/emoji/chipmunk.png
new file mode 100644
index 00000000000..2aac560cb22
--- /dev/null
+++ b/app/assets/images/emoji/chipmunk.png
Binary files differ
diff --git a/app/assets/images/emoji/chocolate_bar.png b/app/assets/images/emoji/chocolate_bar.png
new file mode 100644
index 00000000000..318bbd40ef9
--- /dev/null
+++ b/app/assets/images/emoji/chocolate_bar.png
Binary files differ
diff --git a/app/assets/images/emoji/christmas_tree.png b/app/assets/images/emoji/christmas_tree.png
new file mode 100644
index 00000000000..4197d37a52b
--- /dev/null
+++ b/app/assets/images/emoji/christmas_tree.png
Binary files differ
diff --git a/app/assets/images/emoji/church.png b/app/assets/images/emoji/church.png
new file mode 100644
index 00000000000..8242fd272b3
--- /dev/null
+++ b/app/assets/images/emoji/church.png
Binary files differ
diff --git a/app/assets/images/emoji/cinema.png b/app/assets/images/emoji/cinema.png
new file mode 100644
index 00000000000..65f27b386f2
--- /dev/null
+++ b/app/assets/images/emoji/cinema.png
Binary files differ
diff --git a/app/assets/images/emoji/circus_tent.png b/app/assets/images/emoji/circus_tent.png
new file mode 100644
index 00000000000..b0379775b12
--- /dev/null
+++ b/app/assets/images/emoji/circus_tent.png
Binary files differ
diff --git a/app/assets/images/emoji/city_dusk.png b/app/assets/images/emoji/city_dusk.png
new file mode 100644
index 00000000000..80cdff7cf5d
--- /dev/null
+++ b/app/assets/images/emoji/city_dusk.png
Binary files differ
diff --git a/app/assets/images/emoji/city_sunset.png b/app/assets/images/emoji/city_sunset.png
new file mode 100644
index 00000000000..7cded0ba55b
--- /dev/null
+++ b/app/assets/images/emoji/city_sunset.png
Binary files differ
diff --git a/app/assets/images/emoji/cityscape.png b/app/assets/images/emoji/cityscape.png
new file mode 100644
index 00000000000..d7b9844a0b4
--- /dev/null
+++ b/app/assets/images/emoji/cityscape.png
Binary files differ
diff --git a/app/assets/images/emoji/cl.png b/app/assets/images/emoji/cl.png
new file mode 100644
index 00000000000..8b01b4343e2
--- /dev/null
+++ b/app/assets/images/emoji/cl.png
Binary files differ
diff --git a/app/assets/images/emoji/clap.png b/app/assets/images/emoji/clap.png
new file mode 100644
index 00000000000..b0ffe928920
--- /dev/null
+++ b/app/assets/images/emoji/clap.png
Binary files differ
diff --git a/app/assets/images/emoji/clap_tone1.png b/app/assets/images/emoji/clap_tone1.png
new file mode 100644
index 00000000000..de4bc837b96
--- /dev/null
+++ b/app/assets/images/emoji/clap_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/clap_tone2.png b/app/assets/images/emoji/clap_tone2.png
new file mode 100644
index 00000000000..1323de775ba
--- /dev/null
+++ b/app/assets/images/emoji/clap_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/clap_tone3.png b/app/assets/images/emoji/clap_tone3.png
new file mode 100644
index 00000000000..d448ca19dde
--- /dev/null
+++ b/app/assets/images/emoji/clap_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/clap_tone4.png b/app/assets/images/emoji/clap_tone4.png
new file mode 100644
index 00000000000..c49f44ee91d
--- /dev/null
+++ b/app/assets/images/emoji/clap_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/clap_tone5.png b/app/assets/images/emoji/clap_tone5.png
new file mode 100644
index 00000000000..29ee9bdf37c
--- /dev/null
+++ b/app/assets/images/emoji/clap_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/clapper.png b/app/assets/images/emoji/clapper.png
new file mode 100644
index 00000000000..81390883111
--- /dev/null
+++ b/app/assets/images/emoji/clapper.png
Binary files differ
diff --git a/app/assets/images/emoji/classical_building.png b/app/assets/images/emoji/classical_building.png
new file mode 100644
index 00000000000..de7b559daaf
--- /dev/null
+++ b/app/assets/images/emoji/classical_building.png
Binary files differ
diff --git a/app/assets/images/emoji/clipboard.png b/app/assets/images/emoji/clipboard.png
new file mode 100644
index 00000000000..7edcfc52509
--- /dev/null
+++ b/app/assets/images/emoji/clipboard.png
Binary files differ
diff --git a/app/assets/images/emoji/clock.png b/app/assets/images/emoji/clock.png
new file mode 100644
index 00000000000..ffdb451e3a8
--- /dev/null
+++ b/app/assets/images/emoji/clock.png
Binary files differ
diff --git a/app/assets/images/emoji/clock1.png b/app/assets/images/emoji/clock1.png
new file mode 100644
index 00000000000..d6e34941f23
--- /dev/null
+++ b/app/assets/images/emoji/clock1.png
Binary files differ
diff --git a/app/assets/images/emoji/clock10.png b/app/assets/images/emoji/clock10.png
new file mode 100644
index 00000000000..e62b245cdbe
--- /dev/null
+++ b/app/assets/images/emoji/clock10.png
Binary files differ
diff --git a/app/assets/images/emoji/clock1030.png b/app/assets/images/emoji/clock1030.png
new file mode 100644
index 00000000000..0802b3c65b9
--- /dev/null
+++ b/app/assets/images/emoji/clock1030.png
Binary files differ
diff --git a/app/assets/images/emoji/clock11.png b/app/assets/images/emoji/clock11.png
new file mode 100644
index 00000000000..0983345273b
--- /dev/null
+++ b/app/assets/images/emoji/clock11.png
Binary files differ
diff --git a/app/assets/images/emoji/clock1130.png b/app/assets/images/emoji/clock1130.png
new file mode 100644
index 00000000000..d970d03b809
--- /dev/null
+++ b/app/assets/images/emoji/clock1130.png
Binary files differ
diff --git a/app/assets/images/emoji/clock12.png b/app/assets/images/emoji/clock12.png
new file mode 100644
index 00000000000..e61caa4b3e2
--- /dev/null
+++ b/app/assets/images/emoji/clock12.png
Binary files differ
diff --git a/app/assets/images/emoji/clock1230.png b/app/assets/images/emoji/clock1230.png
new file mode 100644
index 00000000000..f2b1d261721
--- /dev/null
+++ b/app/assets/images/emoji/clock1230.png
Binary files differ
diff --git a/app/assets/images/emoji/clock130.png b/app/assets/images/emoji/clock130.png
new file mode 100644
index 00000000000..86b7689b84e
--- /dev/null
+++ b/app/assets/images/emoji/clock130.png
Binary files differ
diff --git a/app/assets/images/emoji/clock2.png b/app/assets/images/emoji/clock2.png
new file mode 100644
index 00000000000..a54253d7d57
--- /dev/null
+++ b/app/assets/images/emoji/clock2.png
Binary files differ
diff --git a/app/assets/images/emoji/clock230.png b/app/assets/images/emoji/clock230.png
new file mode 100644
index 00000000000..7a787e018e6
--- /dev/null
+++ b/app/assets/images/emoji/clock230.png
Binary files differ
diff --git a/app/assets/images/emoji/clock3.png b/app/assets/images/emoji/clock3.png
new file mode 100644
index 00000000000..27ec4b1f514
--- /dev/null
+++ b/app/assets/images/emoji/clock3.png
Binary files differ
diff --git a/app/assets/images/emoji/clock330.png b/app/assets/images/emoji/clock330.png
new file mode 100644
index 00000000000..c6860395cec
--- /dev/null
+++ b/app/assets/images/emoji/clock330.png
Binary files differ
diff --git a/app/assets/images/emoji/clock4.png b/app/assets/images/emoji/clock4.png
new file mode 100644
index 00000000000..60a1ef4cc13
--- /dev/null
+++ b/app/assets/images/emoji/clock4.png
Binary files differ
diff --git a/app/assets/images/emoji/clock430.png b/app/assets/images/emoji/clock430.png
new file mode 100644
index 00000000000..3c05b362122
--- /dev/null
+++ b/app/assets/images/emoji/clock430.png
Binary files differ
diff --git a/app/assets/images/emoji/clock5.png b/app/assets/images/emoji/clock5.png
new file mode 100644
index 00000000000..c9382d1e094
--- /dev/null
+++ b/app/assets/images/emoji/clock5.png
Binary files differ
diff --git a/app/assets/images/emoji/clock530.png b/app/assets/images/emoji/clock530.png
new file mode 100644
index 00000000000..c21fa926db2
--- /dev/null
+++ b/app/assets/images/emoji/clock530.png
Binary files differ
diff --git a/app/assets/images/emoji/clock6.png b/app/assets/images/emoji/clock6.png
new file mode 100644
index 00000000000..8fd5d3f5bd7
--- /dev/null
+++ b/app/assets/images/emoji/clock6.png
Binary files differ
diff --git a/app/assets/images/emoji/clock630.png b/app/assets/images/emoji/clock630.png
new file mode 100644
index 00000000000..2aec87fefcf
--- /dev/null
+++ b/app/assets/images/emoji/clock630.png
Binary files differ
diff --git a/app/assets/images/emoji/clock7.png b/app/assets/images/emoji/clock7.png
new file mode 100644
index 00000000000..8c7084036f2
--- /dev/null
+++ b/app/assets/images/emoji/clock7.png
Binary files differ
diff --git a/app/assets/images/emoji/clock730.png b/app/assets/images/emoji/clock730.png
new file mode 100644
index 00000000000..f7a1135e03f
--- /dev/null
+++ b/app/assets/images/emoji/clock730.png
Binary files differ
diff --git a/app/assets/images/emoji/clock8.png b/app/assets/images/emoji/clock8.png
new file mode 100644
index 00000000000..fcddf722e95
--- /dev/null
+++ b/app/assets/images/emoji/clock8.png
Binary files differ
diff --git a/app/assets/images/emoji/clock830.png b/app/assets/images/emoji/clock830.png
new file mode 100644
index 00000000000..799b4aebc08
--- /dev/null
+++ b/app/assets/images/emoji/clock830.png
Binary files differ
diff --git a/app/assets/images/emoji/clock9.png b/app/assets/images/emoji/clock9.png
new file mode 100644
index 00000000000..dfbe0117981
--- /dev/null
+++ b/app/assets/images/emoji/clock9.png
Binary files differ
diff --git a/app/assets/images/emoji/clock930.png b/app/assets/images/emoji/clock930.png
new file mode 100644
index 00000000000..4a2092ee6f0
--- /dev/null
+++ b/app/assets/images/emoji/clock930.png
Binary files differ
diff --git a/app/assets/images/emoji/closed_book.png b/app/assets/images/emoji/closed_book.png
new file mode 100644
index 00000000000..6395cf2151e
--- /dev/null
+++ b/app/assets/images/emoji/closed_book.png
Binary files differ
diff --git a/app/assets/images/emoji/closed_lock_with_key.png b/app/assets/images/emoji/closed_lock_with_key.png
new file mode 100644
index 00000000000..1c1cd5d0741
--- /dev/null
+++ b/app/assets/images/emoji/closed_lock_with_key.png
Binary files differ
diff --git a/app/assets/images/emoji/closed_umbrella.png b/app/assets/images/emoji/closed_umbrella.png
new file mode 100644
index 00000000000..ecefba9e446
--- /dev/null
+++ b/app/assets/images/emoji/closed_umbrella.png
Binary files differ
diff --git a/app/assets/images/emoji/cloud.png b/app/assets/images/emoji/cloud.png
new file mode 100644
index 00000000000..5b4f57f77ba
--- /dev/null
+++ b/app/assets/images/emoji/cloud.png
Binary files differ
diff --git a/app/assets/images/emoji/cloud_lightning.png b/app/assets/images/emoji/cloud_lightning.png
new file mode 100644
index 00000000000..0831e88aa31
--- /dev/null
+++ b/app/assets/images/emoji/cloud_lightning.png
Binary files differ
diff --git a/app/assets/images/emoji/cloud_rain.png b/app/assets/images/emoji/cloud_rain.png
new file mode 100644
index 00000000000..385685e0512
--- /dev/null
+++ b/app/assets/images/emoji/cloud_rain.png
Binary files differ
diff --git a/app/assets/images/emoji/cloud_snow.png b/app/assets/images/emoji/cloud_snow.png
new file mode 100644
index 00000000000..9720384eb99
--- /dev/null
+++ b/app/assets/images/emoji/cloud_snow.png
Binary files differ
diff --git a/app/assets/images/emoji/cloud_tornado.png b/app/assets/images/emoji/cloud_tornado.png
new file mode 100644
index 00000000000..4821c89da1e
--- /dev/null
+++ b/app/assets/images/emoji/cloud_tornado.png
Binary files differ
diff --git a/app/assets/images/emoji/clown.png b/app/assets/images/emoji/clown.png
new file mode 100644
index 00000000000..02b7ff70049
--- /dev/null
+++ b/app/assets/images/emoji/clown.png
Binary files differ
diff --git a/app/assets/images/emoji/clubs.png b/app/assets/images/emoji/clubs.png
new file mode 100644
index 00000000000..4f2abf791ca
--- /dev/null
+++ b/app/assets/images/emoji/clubs.png
Binary files differ
diff --git a/app/assets/images/emoji/cocktail.png b/app/assets/images/emoji/cocktail.png
new file mode 100644
index 00000000000..2e50c57e98d
--- /dev/null
+++ b/app/assets/images/emoji/cocktail.png
Binary files differ
diff --git a/app/assets/images/emoji/coffee.png b/app/assets/images/emoji/coffee.png
new file mode 100644
index 00000000000..553061471b1
--- /dev/null
+++ b/app/assets/images/emoji/coffee.png
Binary files differ
diff --git a/app/assets/images/emoji/coffin.png b/app/assets/images/emoji/coffin.png
new file mode 100644
index 00000000000..fb2932aa5f6
--- /dev/null
+++ b/app/assets/images/emoji/coffin.png
Binary files differ
diff --git a/app/assets/images/emoji/cold_sweat.png b/app/assets/images/emoji/cold_sweat.png
new file mode 100644
index 00000000000..85b2231bbf6
--- /dev/null
+++ b/app/assets/images/emoji/cold_sweat.png
Binary files differ
diff --git a/app/assets/images/emoji/comet.png b/app/assets/images/emoji/comet.png
new file mode 100644
index 00000000000..a99751f79be
--- /dev/null
+++ b/app/assets/images/emoji/comet.png
Binary files differ
diff --git a/app/assets/images/emoji/compression.png b/app/assets/images/emoji/compression.png
new file mode 100644
index 00000000000..d7eda7f362a
--- /dev/null
+++ b/app/assets/images/emoji/compression.png
Binary files differ
diff --git a/app/assets/images/emoji/computer.png b/app/assets/images/emoji/computer.png
new file mode 100644
index 00000000000..c1fee27e3a9
--- /dev/null
+++ b/app/assets/images/emoji/computer.png
Binary files differ
diff --git a/app/assets/images/emoji/confetti_ball.png b/app/assets/images/emoji/confetti_ball.png
new file mode 100644
index 00000000000..ba4fd9b12be
--- /dev/null
+++ b/app/assets/images/emoji/confetti_ball.png
Binary files differ
diff --git a/app/assets/images/emoji/confounded.png b/app/assets/images/emoji/confounded.png
new file mode 100644
index 00000000000..aa4b29e9375
--- /dev/null
+++ b/app/assets/images/emoji/confounded.png
Binary files differ
diff --git a/app/assets/images/emoji/confused.png b/app/assets/images/emoji/confused.png
new file mode 100644
index 00000000000..502b6bf0e0b
--- /dev/null
+++ b/app/assets/images/emoji/confused.png
Binary files differ
diff --git a/app/assets/images/emoji/congratulations.png b/app/assets/images/emoji/congratulations.png
new file mode 100644
index 00000000000..ba8c89d95ee
--- /dev/null
+++ b/app/assets/images/emoji/congratulations.png
Binary files differ
diff --git a/app/assets/images/emoji/construction.png b/app/assets/images/emoji/construction.png
new file mode 100644
index 00000000000..ef8db5f471c
--- /dev/null
+++ b/app/assets/images/emoji/construction.png
Binary files differ
diff --git a/app/assets/images/emoji/construction_site.png b/app/assets/images/emoji/construction_site.png
new file mode 100644
index 00000000000..8206a20f63f
--- /dev/null
+++ b/app/assets/images/emoji/construction_site.png
Binary files differ
diff --git a/app/assets/images/emoji/construction_worker.png b/app/assets/images/emoji/construction_worker.png
new file mode 100644
index 00000000000..a9970a89005
--- /dev/null
+++ b/app/assets/images/emoji/construction_worker.png
Binary files differ
diff --git a/app/assets/images/emoji/construction_worker_tone1.png b/app/assets/images/emoji/construction_worker_tone1.png
new file mode 100644
index 00000000000..2f24a2bab24
--- /dev/null
+++ b/app/assets/images/emoji/construction_worker_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/construction_worker_tone2.png b/app/assets/images/emoji/construction_worker_tone2.png
new file mode 100644
index 00000000000..93c8fec5a75
--- /dev/null
+++ b/app/assets/images/emoji/construction_worker_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/construction_worker_tone3.png b/app/assets/images/emoji/construction_worker_tone3.png
new file mode 100644
index 00000000000..abc1f2af2e0
--- /dev/null
+++ b/app/assets/images/emoji/construction_worker_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/construction_worker_tone4.png b/app/assets/images/emoji/construction_worker_tone4.png
new file mode 100644
index 00000000000..eed83289aeb
--- /dev/null
+++ b/app/assets/images/emoji/construction_worker_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/construction_worker_tone5.png b/app/assets/images/emoji/construction_worker_tone5.png
new file mode 100644
index 00000000000..acbb220b8bb
--- /dev/null
+++ b/app/assets/images/emoji/construction_worker_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/control_knobs.png b/app/assets/images/emoji/control_knobs.png
new file mode 100644
index 00000000000..6635ac93b50
--- /dev/null
+++ b/app/assets/images/emoji/control_knobs.png
Binary files differ
diff --git a/app/assets/images/emoji/convenience_store.png b/app/assets/images/emoji/convenience_store.png
new file mode 100644
index 00000000000..26b53b5669e
--- /dev/null
+++ b/app/assets/images/emoji/convenience_store.png
Binary files differ
diff --git a/app/assets/images/emoji/cookie.png b/app/assets/images/emoji/cookie.png
new file mode 100644
index 00000000000..1b6bcb1554f
--- /dev/null
+++ b/app/assets/images/emoji/cookie.png
Binary files differ
diff --git a/app/assets/images/emoji/cooking.png b/app/assets/images/emoji/cooking.png
new file mode 100644
index 00000000000..918c980577a
--- /dev/null
+++ b/app/assets/images/emoji/cooking.png
Binary files differ
diff --git a/app/assets/images/emoji/cool.png b/app/assets/images/emoji/cool.png
new file mode 100644
index 00000000000..74674978d00
--- /dev/null
+++ b/app/assets/images/emoji/cool.png
Binary files differ
diff --git a/app/assets/images/emoji/cop.png b/app/assets/images/emoji/cop.png
new file mode 100644
index 00000000000..0b16d7c17b7
--- /dev/null
+++ b/app/assets/images/emoji/cop.png
Binary files differ
diff --git a/app/assets/images/emoji/cop_tone1.png b/app/assets/images/emoji/cop_tone1.png
new file mode 100644
index 00000000000..6ccba3879dc
--- /dev/null
+++ b/app/assets/images/emoji/cop_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/cop_tone2.png b/app/assets/images/emoji/cop_tone2.png
new file mode 100644
index 00000000000..7814ea9f52d
--- /dev/null
+++ b/app/assets/images/emoji/cop_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/cop_tone3.png b/app/assets/images/emoji/cop_tone3.png
new file mode 100644
index 00000000000..d78e88ec872
--- /dev/null
+++ b/app/assets/images/emoji/cop_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/cop_tone4.png b/app/assets/images/emoji/cop_tone4.png
new file mode 100644
index 00000000000..2e13c508315
--- /dev/null
+++ b/app/assets/images/emoji/cop_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/cop_tone5.png b/app/assets/images/emoji/cop_tone5.png
new file mode 100644
index 00000000000..2980d61cc2e
--- /dev/null
+++ b/app/assets/images/emoji/cop_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/copyright.png b/app/assets/images/emoji/copyright.png
new file mode 100644
index 00000000000..6b9a6adbfd2
--- /dev/null
+++ b/app/assets/images/emoji/copyright.png
Binary files differ
diff --git a/app/assets/images/emoji/corn.png b/app/assets/images/emoji/corn.png
new file mode 100644
index 00000000000..36e20127931
--- /dev/null
+++ b/app/assets/images/emoji/corn.png
Binary files differ
diff --git a/app/assets/images/emoji/couch.png b/app/assets/images/emoji/couch.png
new file mode 100644
index 00000000000..27b19b13bb0
--- /dev/null
+++ b/app/assets/images/emoji/couch.png
Binary files differ
diff --git a/app/assets/images/emoji/couple.png b/app/assets/images/emoji/couple.png
new file mode 100644
index 00000000000..960323f3c16
--- /dev/null
+++ b/app/assets/images/emoji/couple.png
Binary files differ
diff --git a/app/assets/images/emoji/couple_mm.png b/app/assets/images/emoji/couple_mm.png
new file mode 100644
index 00000000000..8759fa5db87
--- /dev/null
+++ b/app/assets/images/emoji/couple_mm.png
Binary files differ
diff --git a/app/assets/images/emoji/couple_with_heart.png b/app/assets/images/emoji/couple_with_heart.png
new file mode 100644
index 00000000000..62111601b36
--- /dev/null
+++ b/app/assets/images/emoji/couple_with_heart.png
Binary files differ
diff --git a/app/assets/images/emoji/couple_ww.png b/app/assets/images/emoji/couple_ww.png
new file mode 100644
index 00000000000..08fdabcdc5c
--- /dev/null
+++ b/app/assets/images/emoji/couple_ww.png
Binary files differ
diff --git a/app/assets/images/emoji/couplekiss.png b/app/assets/images/emoji/couplekiss.png
new file mode 100644
index 00000000000..9aa519da9e8
--- /dev/null
+++ b/app/assets/images/emoji/couplekiss.png
Binary files differ
diff --git a/app/assets/images/emoji/cow.png b/app/assets/images/emoji/cow.png
new file mode 100644
index 00000000000..718a3986d64
--- /dev/null
+++ b/app/assets/images/emoji/cow.png
Binary files differ
diff --git a/app/assets/images/emoji/cow2.png b/app/assets/images/emoji/cow2.png
new file mode 100644
index 00000000000..4d0ca534ff1
--- /dev/null
+++ b/app/assets/images/emoji/cow2.png
Binary files differ
diff --git a/app/assets/images/emoji/cowboy.png b/app/assets/images/emoji/cowboy.png
new file mode 100644
index 00000000000..70dd5d0d9d1
--- /dev/null
+++ b/app/assets/images/emoji/cowboy.png
Binary files differ
diff --git a/app/assets/images/emoji/crab.png b/app/assets/images/emoji/crab.png
new file mode 100644
index 00000000000..19f3047ab61
--- /dev/null
+++ b/app/assets/images/emoji/crab.png
Binary files differ
diff --git a/app/assets/images/emoji/crayon.png b/app/assets/images/emoji/crayon.png
new file mode 100644
index 00000000000..8d7b427aaa3
--- /dev/null
+++ b/app/assets/images/emoji/crayon.png
Binary files differ
diff --git a/app/assets/images/emoji/credit_card.png b/app/assets/images/emoji/credit_card.png
new file mode 100644
index 00000000000..372777d5c61
--- /dev/null
+++ b/app/assets/images/emoji/credit_card.png
Binary files differ
diff --git a/app/assets/images/emoji/crescent_moon.png b/app/assets/images/emoji/crescent_moon.png
new file mode 100644
index 00000000000..765420ecec7
--- /dev/null
+++ b/app/assets/images/emoji/crescent_moon.png
Binary files differ
diff --git a/app/assets/images/emoji/cricket.png b/app/assets/images/emoji/cricket.png
new file mode 100644
index 00000000000..d602294a2cd
--- /dev/null
+++ b/app/assets/images/emoji/cricket.png
Binary files differ
diff --git a/app/assets/images/emoji/crocodile.png b/app/assets/images/emoji/crocodile.png
new file mode 100644
index 00000000000..3005c46f176
--- /dev/null
+++ b/app/assets/images/emoji/crocodile.png
Binary files differ
diff --git a/app/assets/images/emoji/croissant.png b/app/assets/images/emoji/croissant.png
new file mode 100644
index 00000000000..fb33feb1a38
--- /dev/null
+++ b/app/assets/images/emoji/croissant.png
Binary files differ
diff --git a/app/assets/images/emoji/cross.png b/app/assets/images/emoji/cross.png
new file mode 100644
index 00000000000..42b10e82257
--- /dev/null
+++ b/app/assets/images/emoji/cross.png
Binary files differ
diff --git a/app/assets/images/emoji/crossed_flags.png b/app/assets/images/emoji/crossed_flags.png
new file mode 100644
index 00000000000..273bd0f0fe5
--- /dev/null
+++ b/app/assets/images/emoji/crossed_flags.png
Binary files differ
diff --git a/app/assets/images/emoji/crossed_swords.png b/app/assets/images/emoji/crossed_swords.png
new file mode 100644
index 00000000000..907e9607134
--- /dev/null
+++ b/app/assets/images/emoji/crossed_swords.png
Binary files differ
diff --git a/app/assets/images/emoji/crown.png b/app/assets/images/emoji/crown.png
new file mode 100644
index 00000000000..93b82d92f04
--- /dev/null
+++ b/app/assets/images/emoji/crown.png
Binary files differ
diff --git a/app/assets/images/emoji/cruise_ship.png b/app/assets/images/emoji/cruise_ship.png
new file mode 100644
index 00000000000..19d4acbe40c
--- /dev/null
+++ b/app/assets/images/emoji/cruise_ship.png
Binary files differ
diff --git a/app/assets/images/emoji/cry.png b/app/assets/images/emoji/cry.png
new file mode 100644
index 00000000000..b7877f8a173
--- /dev/null
+++ b/app/assets/images/emoji/cry.png
Binary files differ
diff --git a/app/assets/images/emoji/crying_cat_face.png b/app/assets/images/emoji/crying_cat_face.png
new file mode 100644
index 00000000000..b4f49715e00
--- /dev/null
+++ b/app/assets/images/emoji/crying_cat_face.png
Binary files differ
diff --git a/app/assets/images/emoji/crystal_ball.png b/app/assets/images/emoji/crystal_ball.png
new file mode 100644
index 00000000000..485d5c888f1
--- /dev/null
+++ b/app/assets/images/emoji/crystal_ball.png
Binary files differ
diff --git a/app/assets/images/emoji/cucumber.png b/app/assets/images/emoji/cucumber.png
new file mode 100644
index 00000000000..500807059d2
--- /dev/null
+++ b/app/assets/images/emoji/cucumber.png
Binary files differ
diff --git a/app/assets/images/emoji/cupid.png b/app/assets/images/emoji/cupid.png
new file mode 100644
index 00000000000..2df0078ddd1
--- /dev/null
+++ b/app/assets/images/emoji/cupid.png
Binary files differ
diff --git a/app/assets/images/emoji/curly_loop.png b/app/assets/images/emoji/curly_loop.png
new file mode 100644
index 00000000000..440aa56d50e
--- /dev/null
+++ b/app/assets/images/emoji/curly_loop.png
Binary files differ
diff --git a/app/assets/images/emoji/currency_exchange.png b/app/assets/images/emoji/currency_exchange.png
new file mode 100644
index 00000000000..4d46c6050e7
--- /dev/null
+++ b/app/assets/images/emoji/currency_exchange.png
Binary files differ
diff --git a/app/assets/images/emoji/curry.png b/app/assets/images/emoji/curry.png
new file mode 100644
index 00000000000..69657ca8103
--- /dev/null
+++ b/app/assets/images/emoji/curry.png
Binary files differ
diff --git a/app/assets/images/emoji/custard.png b/app/assets/images/emoji/custard.png
new file mode 100644
index 00000000000..fa3df67b8f6
--- /dev/null
+++ b/app/assets/images/emoji/custard.png
Binary files differ
diff --git a/app/assets/images/emoji/customs.png b/app/assets/images/emoji/customs.png
new file mode 100644
index 00000000000..21b7ce2c69e
--- /dev/null
+++ b/app/assets/images/emoji/customs.png
Binary files differ
diff --git a/app/assets/images/emoji/cyclone.png b/app/assets/images/emoji/cyclone.png
new file mode 100644
index 00000000000..ff00b1afe70
--- /dev/null
+++ b/app/assets/images/emoji/cyclone.png
Binary files differ
diff --git a/app/assets/images/emoji/dagger.png b/app/assets/images/emoji/dagger.png
new file mode 100644
index 00000000000..66e97b0aa25
--- /dev/null
+++ b/app/assets/images/emoji/dagger.png
Binary files differ
diff --git a/app/assets/images/emoji/dancer.png b/app/assets/images/emoji/dancer.png
new file mode 100644
index 00000000000..04b166991cb
--- /dev/null
+++ b/app/assets/images/emoji/dancer.png
Binary files differ
diff --git a/app/assets/images/emoji/dancer_tone1.png b/app/assets/images/emoji/dancer_tone1.png
new file mode 100644
index 00000000000..2c7b11c3a6e
--- /dev/null
+++ b/app/assets/images/emoji/dancer_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/dancer_tone2.png b/app/assets/images/emoji/dancer_tone2.png
new file mode 100644
index 00000000000..cb04b1f907e
--- /dev/null
+++ b/app/assets/images/emoji/dancer_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/dancer_tone3.png b/app/assets/images/emoji/dancer_tone3.png
new file mode 100644
index 00000000000..98c5bca7b64
--- /dev/null
+++ b/app/assets/images/emoji/dancer_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/dancer_tone4.png b/app/assets/images/emoji/dancer_tone4.png
new file mode 100644
index 00000000000..fdb1e00cbba
--- /dev/null
+++ b/app/assets/images/emoji/dancer_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/dancer_tone5.png b/app/assets/images/emoji/dancer_tone5.png
new file mode 100644
index 00000000000..0e34e0e23f0
--- /dev/null
+++ b/app/assets/images/emoji/dancer_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/dancers.png b/app/assets/images/emoji/dancers.png
new file mode 100644
index 00000000000..67e6ffacb76
--- /dev/null
+++ b/app/assets/images/emoji/dancers.png
Binary files differ
diff --git a/app/assets/images/emoji/dango.png b/app/assets/images/emoji/dango.png
new file mode 100644
index 00000000000..f73f37b01c7
--- /dev/null
+++ b/app/assets/images/emoji/dango.png
Binary files differ
diff --git a/app/assets/images/emoji/dark_sunglasses.png b/app/assets/images/emoji/dark_sunglasses.png
new file mode 100644
index 00000000000..b1b6db0acff
--- /dev/null
+++ b/app/assets/images/emoji/dark_sunglasses.png
Binary files differ
diff --git a/app/assets/images/emoji/dart.png b/app/assets/images/emoji/dart.png
new file mode 100644
index 00000000000..f6704aeb8ba
--- /dev/null
+++ b/app/assets/images/emoji/dart.png
Binary files differ
diff --git a/app/assets/images/emoji/dash.png b/app/assets/images/emoji/dash.png
new file mode 100644
index 00000000000..064b8525c12
--- /dev/null
+++ b/app/assets/images/emoji/dash.png
Binary files differ
diff --git a/app/assets/images/emoji/date.png b/app/assets/images/emoji/date.png
new file mode 100644
index 00000000000..f05b3da97b8
--- /dev/null
+++ b/app/assets/images/emoji/date.png
Binary files differ
diff --git a/app/assets/images/emoji/deciduous_tree.png b/app/assets/images/emoji/deciduous_tree.png
new file mode 100644
index 00000000000..785fc1c30ea
--- /dev/null
+++ b/app/assets/images/emoji/deciduous_tree.png
Binary files differ
diff --git a/app/assets/images/emoji/deer.png b/app/assets/images/emoji/deer.png
new file mode 100644
index 00000000000..d8698195ff0
--- /dev/null
+++ b/app/assets/images/emoji/deer.png
Binary files differ
diff --git a/app/assets/images/emoji/department_store.png b/app/assets/images/emoji/department_store.png
new file mode 100644
index 00000000000..58867c7a6e1
--- /dev/null
+++ b/app/assets/images/emoji/department_store.png
Binary files differ
diff --git a/app/assets/images/emoji/desert.png b/app/assets/images/emoji/desert.png
new file mode 100644
index 00000000000..e9966ff8c65
--- /dev/null
+++ b/app/assets/images/emoji/desert.png
Binary files differ
diff --git a/app/assets/images/emoji/desktop.png b/app/assets/images/emoji/desktop.png
new file mode 100644
index 00000000000..909bd42b5e1
--- /dev/null
+++ b/app/assets/images/emoji/desktop.png
Binary files differ
diff --git a/app/assets/images/emoji/diamond_shape_with_a_dot_inside.png b/app/assets/images/emoji/diamond_shape_with_a_dot_inside.png
new file mode 100644
index 00000000000..2a22a26d1e2
--- /dev/null
+++ b/app/assets/images/emoji/diamond_shape_with_a_dot_inside.png
Binary files differ
diff --git a/app/assets/images/emoji/diamonds.png b/app/assets/images/emoji/diamonds.png
new file mode 100644
index 00000000000..1f25f51f97a
--- /dev/null
+++ b/app/assets/images/emoji/diamonds.png
Binary files differ
diff --git a/app/assets/images/emoji/disappointed.png b/app/assets/images/emoji/disappointed.png
new file mode 100644
index 00000000000..efe4e67e23c
--- /dev/null
+++ b/app/assets/images/emoji/disappointed.png
Binary files differ
diff --git a/app/assets/images/emoji/disappointed_relieved.png b/app/assets/images/emoji/disappointed_relieved.png
new file mode 100644
index 00000000000..aef864d2b3d
--- /dev/null
+++ b/app/assets/images/emoji/disappointed_relieved.png
Binary files differ
diff --git a/app/assets/images/emoji/dividers.png b/app/assets/images/emoji/dividers.png
new file mode 100644
index 00000000000..46a7e403f9d
--- /dev/null
+++ b/app/assets/images/emoji/dividers.png
Binary files differ
diff --git a/app/assets/images/emoji/dizzy.png b/app/assets/images/emoji/dizzy.png
new file mode 100644
index 00000000000..85f52efad24
--- /dev/null
+++ b/app/assets/images/emoji/dizzy.png
Binary files differ
diff --git a/app/assets/images/emoji/dizzy_face.png b/app/assets/images/emoji/dizzy_face.png
new file mode 100644
index 00000000000..3120316ab5e
--- /dev/null
+++ b/app/assets/images/emoji/dizzy_face.png
Binary files differ
diff --git a/app/assets/images/emoji/do_not_litter.png b/app/assets/images/emoji/do_not_litter.png
new file mode 100644
index 00000000000..341d2575f4f
--- /dev/null
+++ b/app/assets/images/emoji/do_not_litter.png
Binary files differ
diff --git a/app/assets/images/emoji/dog.png b/app/assets/images/emoji/dog.png
new file mode 100644
index 00000000000..281b81d58bd
--- /dev/null
+++ b/app/assets/images/emoji/dog.png
Binary files differ
diff --git a/app/assets/images/emoji/dog2.png b/app/assets/images/emoji/dog2.png
new file mode 100644
index 00000000000..976143dbdbe
--- /dev/null
+++ b/app/assets/images/emoji/dog2.png
Binary files differ
diff --git a/app/assets/images/emoji/dollar.png b/app/assets/images/emoji/dollar.png
new file mode 100644
index 00000000000..a9904c28293
--- /dev/null
+++ b/app/assets/images/emoji/dollar.png
Binary files differ
diff --git a/app/assets/images/emoji/dolls.png b/app/assets/images/emoji/dolls.png
new file mode 100644
index 00000000000..10955615110
--- /dev/null
+++ b/app/assets/images/emoji/dolls.png
Binary files differ
diff --git a/app/assets/images/emoji/dolphin.png b/app/assets/images/emoji/dolphin.png
new file mode 100644
index 00000000000..81434809003
--- /dev/null
+++ b/app/assets/images/emoji/dolphin.png
Binary files differ
diff --git a/app/assets/images/emoji/door.png b/app/assets/images/emoji/door.png
new file mode 100644
index 00000000000..36ae3e27494
--- /dev/null
+++ b/app/assets/images/emoji/door.png
Binary files differ
diff --git a/app/assets/images/emoji/doughnut.png b/app/assets/images/emoji/doughnut.png
new file mode 100644
index 00000000000..0ca4cd0bde8
--- /dev/null
+++ b/app/assets/images/emoji/doughnut.png
Binary files differ
diff --git a/app/assets/images/emoji/dove.png b/app/assets/images/emoji/dove.png
new file mode 100644
index 00000000000..9580c4917d7
--- /dev/null
+++ b/app/assets/images/emoji/dove.png
Binary files differ
diff --git a/app/assets/images/emoji/dragon.png b/app/assets/images/emoji/dragon.png
new file mode 100644
index 00000000000..d6311cf5429
--- /dev/null
+++ b/app/assets/images/emoji/dragon.png
Binary files differ
diff --git a/app/assets/images/emoji/dragon_face.png b/app/assets/images/emoji/dragon_face.png
new file mode 100644
index 00000000000..3c2720446c6
--- /dev/null
+++ b/app/assets/images/emoji/dragon_face.png
Binary files differ
diff --git a/app/assets/images/emoji/dress.png b/app/assets/images/emoji/dress.png
new file mode 100644
index 00000000000..a697ca5c57d
--- /dev/null
+++ b/app/assets/images/emoji/dress.png
Binary files differ
diff --git a/app/assets/images/emoji/dromedary_camel.png b/app/assets/images/emoji/dromedary_camel.png
new file mode 100644
index 00000000000..5271637c7c4
--- /dev/null
+++ b/app/assets/images/emoji/dromedary_camel.png
Binary files differ
diff --git a/app/assets/images/emoji/drooling_face.png b/app/assets/images/emoji/drooling_face.png
new file mode 100644
index 00000000000..a5460532597
--- /dev/null
+++ b/app/assets/images/emoji/drooling_face.png
Binary files differ
diff --git a/app/assets/images/emoji/droplet.png b/app/assets/images/emoji/droplet.png
new file mode 100644
index 00000000000..71241ec3061
--- /dev/null
+++ b/app/assets/images/emoji/droplet.png
Binary files differ
diff --git a/app/assets/images/emoji/drum.png b/app/assets/images/emoji/drum.png
new file mode 100644
index 00000000000..b038727cc99
--- /dev/null
+++ b/app/assets/images/emoji/drum.png
Binary files differ
diff --git a/app/assets/images/emoji/duck.png b/app/assets/images/emoji/duck.png
new file mode 100644
index 00000000000..74330b77ca3
--- /dev/null
+++ b/app/assets/images/emoji/duck.png
Binary files differ
diff --git a/app/assets/images/emoji/dvd.png b/app/assets/images/emoji/dvd.png
new file mode 100644
index 00000000000..045a6f7a08d
--- /dev/null
+++ b/app/assets/images/emoji/dvd.png
Binary files differ
diff --git a/app/assets/images/emoji/e-mail.png b/app/assets/images/emoji/e-mail.png
new file mode 100644
index 00000000000..d22e654a20b
--- /dev/null
+++ b/app/assets/images/emoji/e-mail.png
Binary files differ
diff --git a/app/assets/images/emoji/eagle.png b/app/assets/images/emoji/eagle.png
new file mode 100644
index 00000000000..4f277debeef
--- /dev/null
+++ b/app/assets/images/emoji/eagle.png
Binary files differ
diff --git a/app/assets/images/emoji/ear.png b/app/assets/images/emoji/ear.png
new file mode 100644
index 00000000000..f84f9ff154a
--- /dev/null
+++ b/app/assets/images/emoji/ear.png
Binary files differ
diff --git a/app/assets/images/emoji/ear_of_rice.png b/app/assets/images/emoji/ear_of_rice.png
new file mode 100644
index 00000000000..3564d9d643a
--- /dev/null
+++ b/app/assets/images/emoji/ear_of_rice.png
Binary files differ
diff --git a/app/assets/images/emoji/ear_tone1.png b/app/assets/images/emoji/ear_tone1.png
new file mode 100644
index 00000000000..d09e1e41996
--- /dev/null
+++ b/app/assets/images/emoji/ear_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/ear_tone2.png b/app/assets/images/emoji/ear_tone2.png
new file mode 100644
index 00000000000..300d60a9948
--- /dev/null
+++ b/app/assets/images/emoji/ear_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/ear_tone3.png b/app/assets/images/emoji/ear_tone3.png
new file mode 100644
index 00000000000..2a56eebe445
--- /dev/null
+++ b/app/assets/images/emoji/ear_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/ear_tone4.png b/app/assets/images/emoji/ear_tone4.png
new file mode 100644
index 00000000000..bd270f7763e
--- /dev/null
+++ b/app/assets/images/emoji/ear_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/ear_tone5.png b/app/assets/images/emoji/ear_tone5.png
new file mode 100644
index 00000000000..b96bb441dff
--- /dev/null
+++ b/app/assets/images/emoji/ear_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/earth_africa.png b/app/assets/images/emoji/earth_africa.png
new file mode 100644
index 00000000000..66c3348c23a
--- /dev/null
+++ b/app/assets/images/emoji/earth_africa.png
Binary files differ
diff --git a/app/assets/images/emoji/earth_americas.png b/app/assets/images/emoji/earth_americas.png
new file mode 100644
index 00000000000..538c3cddd68
--- /dev/null
+++ b/app/assets/images/emoji/earth_americas.png
Binary files differ
diff --git a/app/assets/images/emoji/earth_asia.png b/app/assets/images/emoji/earth_asia.png
new file mode 100644
index 00000000000..d8df97fec3c
--- /dev/null
+++ b/app/assets/images/emoji/earth_asia.png
Binary files differ
diff --git a/app/assets/images/emoji/egg.png b/app/assets/images/emoji/egg.png
new file mode 100644
index 00000000000..c171974d993
--- /dev/null
+++ b/app/assets/images/emoji/egg.png
Binary files differ
diff --git a/app/assets/images/emoji/eggplant.png b/app/assets/images/emoji/eggplant.png
new file mode 100644
index 00000000000..fafd7c1a14c
--- /dev/null
+++ b/app/assets/images/emoji/eggplant.png
Binary files differ
diff --git a/app/assets/images/emoji/eight.png b/app/assets/images/emoji/eight.png
new file mode 100644
index 00000000000..8c95874d4c5
--- /dev/null
+++ b/app/assets/images/emoji/eight.png
Binary files differ
diff --git a/app/assets/images/emoji/eight_pointed_black_star.png b/app/assets/images/emoji/eight_pointed_black_star.png
new file mode 100644
index 00000000000..820179bda50
--- /dev/null
+++ b/app/assets/images/emoji/eight_pointed_black_star.png
Binary files differ
diff --git a/app/assets/images/emoji/eight_spoked_asterisk.png b/app/assets/images/emoji/eight_spoked_asterisk.png
new file mode 100644
index 00000000000..3307ffa62ee
--- /dev/null
+++ b/app/assets/images/emoji/eight_spoked_asterisk.png
Binary files differ
diff --git a/app/assets/images/emoji/eject.png b/app/assets/images/emoji/eject.png
new file mode 100644
index 00000000000..ec5cfc48973
--- /dev/null
+++ b/app/assets/images/emoji/eject.png
Binary files differ
diff --git a/app/assets/images/emoji/electric_plug.png b/app/assets/images/emoji/electric_plug.png
new file mode 100644
index 00000000000..31d1eb215b4
--- /dev/null
+++ b/app/assets/images/emoji/electric_plug.png
Binary files differ
diff --git a/app/assets/images/emoji/elephant.png b/app/assets/images/emoji/elephant.png
new file mode 100644
index 00000000000..b8a6d140595
--- /dev/null
+++ b/app/assets/images/emoji/elephant.png
Binary files differ
diff --git a/app/assets/images/emoji/end.png b/app/assets/images/emoji/end.png
new file mode 100644
index 00000000000..ef3ccd5f367
--- /dev/null
+++ b/app/assets/images/emoji/end.png
Binary files differ
diff --git a/app/assets/images/emoji/envelope.png b/app/assets/images/emoji/envelope.png
new file mode 100644
index 00000000000..ec77ac375a4
--- /dev/null
+++ b/app/assets/images/emoji/envelope.png
Binary files differ
diff --git a/app/assets/images/emoji/envelope_with_arrow.png b/app/assets/images/emoji/envelope_with_arrow.png
new file mode 100644
index 00000000000..7448a6b7673
--- /dev/null
+++ b/app/assets/images/emoji/envelope_with_arrow.png
Binary files differ
diff --git a/app/assets/images/emoji/euro.png b/app/assets/images/emoji/euro.png
new file mode 100644
index 00000000000..a49020820e1
--- /dev/null
+++ b/app/assets/images/emoji/euro.png
Binary files differ
diff --git a/app/assets/images/emoji/european_castle.png b/app/assets/images/emoji/european_castle.png
new file mode 100644
index 00000000000..888d11332ce
--- /dev/null
+++ b/app/assets/images/emoji/european_castle.png
Binary files differ
diff --git a/app/assets/images/emoji/european_post_office.png b/app/assets/images/emoji/european_post_office.png
new file mode 100644
index 00000000000..3745aff8dd2
--- /dev/null
+++ b/app/assets/images/emoji/european_post_office.png
Binary files differ
diff --git a/app/assets/images/emoji/evergreen_tree.png b/app/assets/images/emoji/evergreen_tree.png
new file mode 100644
index 00000000000..f679d8dd772
--- /dev/null
+++ b/app/assets/images/emoji/evergreen_tree.png
Binary files differ
diff --git a/app/assets/images/emoji/exclamation.png b/app/assets/images/emoji/exclamation.png
new file mode 100644
index 00000000000..2c14406422f
--- /dev/null
+++ b/app/assets/images/emoji/exclamation.png
Binary files differ
diff --git a/app/assets/images/emoji/expressionless.png b/app/assets/images/emoji/expressionless.png
new file mode 100644
index 00000000000..2954017f6c2
--- /dev/null
+++ b/app/assets/images/emoji/expressionless.png
Binary files differ
diff --git a/app/assets/images/emoji/eye.png b/app/assets/images/emoji/eye.png
new file mode 100644
index 00000000000..9d989cdd375
--- /dev/null
+++ b/app/assets/images/emoji/eye.png
Binary files differ
diff --git a/app/assets/images/emoji/eye_in_speech_bubble.png b/app/assets/images/emoji/eye_in_speech_bubble.png
new file mode 100644
index 00000000000..21bd22bbcce
--- /dev/null
+++ b/app/assets/images/emoji/eye_in_speech_bubble.png
Binary files differ
diff --git a/app/assets/images/emoji/eyeglasses.png b/app/assets/images/emoji/eyeglasses.png
new file mode 100644
index 00000000000..865d8274acf
--- /dev/null
+++ b/app/assets/images/emoji/eyeglasses.png
Binary files differ
diff --git a/app/assets/images/emoji/eyes.png b/app/assets/images/emoji/eyes.png
new file mode 100644
index 00000000000..2102ada7e09
--- /dev/null
+++ b/app/assets/images/emoji/eyes.png
Binary files differ
diff --git a/app/assets/images/emoji/face_palm.png b/app/assets/images/emoji/face_palm.png
new file mode 100644
index 00000000000..defc796cf16
--- /dev/null
+++ b/app/assets/images/emoji/face_palm.png
Binary files differ
diff --git a/app/assets/images/emoji/face_palm_tone1.png b/app/assets/images/emoji/face_palm_tone1.png
new file mode 100644
index 00000000000..2f4b010bb40
--- /dev/null
+++ b/app/assets/images/emoji/face_palm_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/face_palm_tone2.png b/app/assets/images/emoji/face_palm_tone2.png
new file mode 100644
index 00000000000..97fb6831687
--- /dev/null
+++ b/app/assets/images/emoji/face_palm_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/face_palm_tone3.png b/app/assets/images/emoji/face_palm_tone3.png
new file mode 100644
index 00000000000..b5b5c1e5306
--- /dev/null
+++ b/app/assets/images/emoji/face_palm_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/face_palm_tone4.png b/app/assets/images/emoji/face_palm_tone4.png
new file mode 100644
index 00000000000..2840b113483
--- /dev/null
+++ b/app/assets/images/emoji/face_palm_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/face_palm_tone5.png b/app/assets/images/emoji/face_palm_tone5.png
new file mode 100644
index 00000000000..6f070db98be
--- /dev/null
+++ b/app/assets/images/emoji/face_palm_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/factory.png b/app/assets/images/emoji/factory.png
new file mode 100644
index 00000000000..e1d2ddf4a27
--- /dev/null
+++ b/app/assets/images/emoji/factory.png
Binary files differ
diff --git a/app/assets/images/emoji/fallen_leaf.png b/app/assets/images/emoji/fallen_leaf.png
new file mode 100644
index 00000000000..0d60e7bdf2d
--- /dev/null
+++ b/app/assets/images/emoji/fallen_leaf.png
Binary files differ
diff --git a/app/assets/images/emoji/family.png b/app/assets/images/emoji/family.png
new file mode 100644
index 00000000000..26421965791
--- /dev/null
+++ b/app/assets/images/emoji/family.png
Binary files differ
diff --git a/app/assets/images/emoji/family_mmb.png b/app/assets/images/emoji/family_mmb.png
new file mode 100644
index 00000000000..7a2e4e2c491
--- /dev/null
+++ b/app/assets/images/emoji/family_mmb.png
Binary files differ
diff --git a/app/assets/images/emoji/family_mmbb.png b/app/assets/images/emoji/family_mmbb.png
new file mode 100644
index 00000000000..81e6c0fc0ee
--- /dev/null
+++ b/app/assets/images/emoji/family_mmbb.png
Binary files differ
diff --git a/app/assets/images/emoji/family_mmg.png b/app/assets/images/emoji/family_mmg.png
new file mode 100644
index 00000000000..932a85e1fe5
--- /dev/null
+++ b/app/assets/images/emoji/family_mmg.png
Binary files differ
diff --git a/app/assets/images/emoji/family_mmgb.png b/app/assets/images/emoji/family_mmgb.png
new file mode 100644
index 00000000000..41e35166670
--- /dev/null
+++ b/app/assets/images/emoji/family_mmgb.png
Binary files differ
diff --git a/app/assets/images/emoji/family_mmgg.png b/app/assets/images/emoji/family_mmgg.png
new file mode 100644
index 00000000000..8e8ccfe6c7f
--- /dev/null
+++ b/app/assets/images/emoji/family_mmgg.png
Binary files differ
diff --git a/app/assets/images/emoji/family_mwbb.png b/app/assets/images/emoji/family_mwbb.png
new file mode 100644
index 00000000000..b544fbe573f
--- /dev/null
+++ b/app/assets/images/emoji/family_mwbb.png
Binary files differ
diff --git a/app/assets/images/emoji/family_mwg.png b/app/assets/images/emoji/family_mwg.png
new file mode 100644
index 00000000000..71d2681c32a
--- /dev/null
+++ b/app/assets/images/emoji/family_mwg.png
Binary files differ
diff --git a/app/assets/images/emoji/family_mwgb.png b/app/assets/images/emoji/family_mwgb.png
new file mode 100644
index 00000000000..40dbf1f7a18
--- /dev/null
+++ b/app/assets/images/emoji/family_mwgb.png
Binary files differ
diff --git a/app/assets/images/emoji/family_mwgg.png b/app/assets/images/emoji/family_mwgg.png
new file mode 100644
index 00000000000..bfefa4879cb
--- /dev/null
+++ b/app/assets/images/emoji/family_mwgg.png
Binary files differ
diff --git a/app/assets/images/emoji/family_wwb.png b/app/assets/images/emoji/family_wwb.png
new file mode 100644
index 00000000000..836feae7c78
--- /dev/null
+++ b/app/assets/images/emoji/family_wwb.png
Binary files differ
diff --git a/app/assets/images/emoji/family_wwbb.png b/app/assets/images/emoji/family_wwbb.png
new file mode 100644
index 00000000000..6c6ba45e7bb
--- /dev/null
+++ b/app/assets/images/emoji/family_wwbb.png
Binary files differ
diff --git a/app/assets/images/emoji/family_wwg.png b/app/assets/images/emoji/family_wwg.png
new file mode 100644
index 00000000000..41225c6fa5a
--- /dev/null
+++ b/app/assets/images/emoji/family_wwg.png
Binary files differ
diff --git a/app/assets/images/emoji/family_wwgb.png b/app/assets/images/emoji/family_wwgb.png
new file mode 100644
index 00000000000..284d29ab5da
--- /dev/null
+++ b/app/assets/images/emoji/family_wwgb.png
Binary files differ
diff --git a/app/assets/images/emoji/family_wwgg.png b/app/assets/images/emoji/family_wwgg.png
new file mode 100644
index 00000000000..d8d3f49b85f
--- /dev/null
+++ b/app/assets/images/emoji/family_wwgg.png
Binary files differ
diff --git a/app/assets/images/emoji/fast_forward.png b/app/assets/images/emoji/fast_forward.png
new file mode 100644
index 00000000000..c406fedfdb1
--- /dev/null
+++ b/app/assets/images/emoji/fast_forward.png
Binary files differ
diff --git a/app/assets/images/emoji/fax.png b/app/assets/images/emoji/fax.png
new file mode 100644
index 00000000000..6f929e294c2
--- /dev/null
+++ b/app/assets/images/emoji/fax.png
Binary files differ
diff --git a/app/assets/images/emoji/fearful.png b/app/assets/images/emoji/fearful.png
new file mode 100644
index 00000000000..eb8b347cef9
--- /dev/null
+++ b/app/assets/images/emoji/fearful.png
Binary files differ
diff --git a/app/assets/images/emoji/feet.png b/app/assets/images/emoji/feet.png
new file mode 100644
index 00000000000..5fe568cee93
--- /dev/null
+++ b/app/assets/images/emoji/feet.png
Binary files differ
diff --git a/app/assets/images/emoji/fencer.png b/app/assets/images/emoji/fencer.png
new file mode 100644
index 00000000000..5288c920eb9
--- /dev/null
+++ b/app/assets/images/emoji/fencer.png
Binary files differ
diff --git a/app/assets/images/emoji/ferris_wheel.png b/app/assets/images/emoji/ferris_wheel.png
new file mode 100644
index 00000000000..55c8ff0475b
--- /dev/null
+++ b/app/assets/images/emoji/ferris_wheel.png
Binary files differ
diff --git a/app/assets/images/emoji/ferry.png b/app/assets/images/emoji/ferry.png
new file mode 100644
index 00000000000..41816b3ae34
--- /dev/null
+++ b/app/assets/images/emoji/ferry.png
Binary files differ
diff --git a/app/assets/images/emoji/field_hockey.png b/app/assets/images/emoji/field_hockey.png
new file mode 100644
index 00000000000..839637716ee
--- /dev/null
+++ b/app/assets/images/emoji/field_hockey.png
Binary files differ
diff --git a/app/assets/images/emoji/file_cabinet.png b/app/assets/images/emoji/file_cabinet.png
new file mode 100644
index 00000000000..fddc65dde96
--- /dev/null
+++ b/app/assets/images/emoji/file_cabinet.png
Binary files differ
diff --git a/app/assets/images/emoji/file_folder.png b/app/assets/images/emoji/file_folder.png
new file mode 100644
index 00000000000..addedaf0870
--- /dev/null
+++ b/app/assets/images/emoji/file_folder.png
Binary files differ
diff --git a/app/assets/images/emoji/film_frames.png b/app/assets/images/emoji/film_frames.png
new file mode 100644
index 00000000000..30143aedbe6
--- /dev/null
+++ b/app/assets/images/emoji/film_frames.png
Binary files differ
diff --git a/app/assets/images/emoji/fingers_crossed.png b/app/assets/images/emoji/fingers_crossed.png
new file mode 100644
index 00000000000..4cd18514ea3
--- /dev/null
+++ b/app/assets/images/emoji/fingers_crossed.png
Binary files differ
diff --git a/app/assets/images/emoji/fingers_crossed_tone1.png b/app/assets/images/emoji/fingers_crossed_tone1.png
new file mode 100644
index 00000000000..dd2384a6cd5
--- /dev/null
+++ b/app/assets/images/emoji/fingers_crossed_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/fingers_crossed_tone2.png b/app/assets/images/emoji/fingers_crossed_tone2.png
new file mode 100644
index 00000000000..6228401befe
--- /dev/null
+++ b/app/assets/images/emoji/fingers_crossed_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/fingers_crossed_tone3.png b/app/assets/images/emoji/fingers_crossed_tone3.png
new file mode 100644
index 00000000000..b1074da15f5
--- /dev/null
+++ b/app/assets/images/emoji/fingers_crossed_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/fingers_crossed_tone4.png b/app/assets/images/emoji/fingers_crossed_tone4.png
new file mode 100644
index 00000000000..75e05e4d332
--- /dev/null
+++ b/app/assets/images/emoji/fingers_crossed_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/fingers_crossed_tone5.png b/app/assets/images/emoji/fingers_crossed_tone5.png
new file mode 100644
index 00000000000..761aebdc30f
--- /dev/null
+++ b/app/assets/images/emoji/fingers_crossed_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/fire.png b/app/assets/images/emoji/fire.png
new file mode 100644
index 00000000000..bd3775a460b
--- /dev/null
+++ b/app/assets/images/emoji/fire.png
Binary files differ
diff --git a/app/assets/images/emoji/fire_engine.png b/app/assets/images/emoji/fire_engine.png
new file mode 100644
index 00000000000..2cd45b7cf7e
--- /dev/null
+++ b/app/assets/images/emoji/fire_engine.png
Binary files differ
diff --git a/app/assets/images/emoji/fireworks.png b/app/assets/images/emoji/fireworks.png
new file mode 100644
index 00000000000..176c8b58265
--- /dev/null
+++ b/app/assets/images/emoji/fireworks.png
Binary files differ
diff --git a/app/assets/images/emoji/first_place.png b/app/assets/images/emoji/first_place.png
new file mode 100644
index 00000000000..15612b66492
--- /dev/null
+++ b/app/assets/images/emoji/first_place.png
Binary files differ
diff --git a/app/assets/images/emoji/first_quarter_moon.png b/app/assets/images/emoji/first_quarter_moon.png
new file mode 100644
index 00000000000..5dccaf72a4f
--- /dev/null
+++ b/app/assets/images/emoji/first_quarter_moon.png
Binary files differ
diff --git a/app/assets/images/emoji/first_quarter_moon_with_face.png b/app/assets/images/emoji/first_quarter_moon_with_face.png
new file mode 100644
index 00000000000..cd8a3d7acd8
--- /dev/null
+++ b/app/assets/images/emoji/first_quarter_moon_with_face.png
Binary files differ
diff --git a/app/assets/images/emoji/fish.png b/app/assets/images/emoji/fish.png
new file mode 100644
index 00000000000..c2d2faaacd4
--- /dev/null
+++ b/app/assets/images/emoji/fish.png
Binary files differ
diff --git a/app/assets/images/emoji/fish_cake.png b/app/assets/images/emoji/fish_cake.png
new file mode 100644
index 00000000000..157bded65db
--- /dev/null
+++ b/app/assets/images/emoji/fish_cake.png
Binary files differ
diff --git a/app/assets/images/emoji/fishing_pole_and_fish.png b/app/assets/images/emoji/fishing_pole_and_fish.png
new file mode 100644
index 00000000000..dfcdf07eb50
--- /dev/null
+++ b/app/assets/images/emoji/fishing_pole_and_fish.png
Binary files differ
diff --git a/app/assets/images/emoji/fist.png b/app/assets/images/emoji/fist.png
new file mode 100644
index 00000000000..de33592bf98
--- /dev/null
+++ b/app/assets/images/emoji/fist.png
Binary files differ
diff --git a/app/assets/images/emoji/fist_tone1.png b/app/assets/images/emoji/fist_tone1.png
new file mode 100644
index 00000000000..02809e2dd68
--- /dev/null
+++ b/app/assets/images/emoji/fist_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/fist_tone2.png b/app/assets/images/emoji/fist_tone2.png
new file mode 100644
index 00000000000..5de34810383
--- /dev/null
+++ b/app/assets/images/emoji/fist_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/fist_tone3.png b/app/assets/images/emoji/fist_tone3.png
new file mode 100644
index 00000000000..0d5240129b1
--- /dev/null
+++ b/app/assets/images/emoji/fist_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/fist_tone4.png b/app/assets/images/emoji/fist_tone4.png
new file mode 100644
index 00000000000..a95c0dd634b
--- /dev/null
+++ b/app/assets/images/emoji/fist_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/fist_tone5.png b/app/assets/images/emoji/fist_tone5.png
new file mode 100644
index 00000000000..a2f092fd8c7
--- /dev/null
+++ b/app/assets/images/emoji/fist_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/five.png b/app/assets/images/emoji/five.png
new file mode 100644
index 00000000000..d14371f3f27
--- /dev/null
+++ b/app/assets/images/emoji/five.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ac.png b/app/assets/images/emoji/flag_ac.png
new file mode 100644
index 00000000000..286239920c7
--- /dev/null
+++ b/app/assets/images/emoji/flag_ac.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ad.png b/app/assets/images/emoji/flag_ad.png
new file mode 100644
index 00000000000..20f4b14e8ad
--- /dev/null
+++ b/app/assets/images/emoji/flag_ad.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ae.png b/app/assets/images/emoji/flag_ae.png
new file mode 100644
index 00000000000..d16ffe4b862
--- /dev/null
+++ b/app/assets/images/emoji/flag_ae.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_af.png b/app/assets/images/emoji/flag_af.png
new file mode 100644
index 00000000000..a51533b554d
--- /dev/null
+++ b/app/assets/images/emoji/flag_af.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ag.png b/app/assets/images/emoji/flag_ag.png
new file mode 100644
index 00000000000..07f2ce397d0
--- /dev/null
+++ b/app/assets/images/emoji/flag_ag.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ai.png b/app/assets/images/emoji/flag_ai.png
new file mode 100644
index 00000000000..500b5ab09fb
--- /dev/null
+++ b/app/assets/images/emoji/flag_ai.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_al.png b/app/assets/images/emoji/flag_al.png
new file mode 100644
index 00000000000..03a20132cc6
--- /dev/null
+++ b/app/assets/images/emoji/flag_al.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_am.png b/app/assets/images/emoji/flag_am.png
new file mode 100644
index 00000000000..2ad60a273ec
--- /dev/null
+++ b/app/assets/images/emoji/flag_am.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ao.png b/app/assets/images/emoji/flag_ao.png
new file mode 100644
index 00000000000..cb46c31f862
--- /dev/null
+++ b/app/assets/images/emoji/flag_ao.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_aq.png b/app/assets/images/emoji/flag_aq.png
new file mode 100644
index 00000000000..b272021d375
--- /dev/null
+++ b/app/assets/images/emoji/flag_aq.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ar.png b/app/assets/images/emoji/flag_ar.png
new file mode 100644
index 00000000000..73136caf3b7
--- /dev/null
+++ b/app/assets/images/emoji/flag_ar.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_as.png b/app/assets/images/emoji/flag_as.png
new file mode 100644
index 00000000000..3db45a0d9f3
--- /dev/null
+++ b/app/assets/images/emoji/flag_as.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_at.png b/app/assets/images/emoji/flag_at.png
new file mode 100644
index 00000000000..c43769dcb19
--- /dev/null
+++ b/app/assets/images/emoji/flag_at.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_au.png b/app/assets/images/emoji/flag_au.png
new file mode 100644
index 00000000000..7794309c78c
--- /dev/null
+++ b/app/assets/images/emoji/flag_au.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_aw.png b/app/assets/images/emoji/flag_aw.png
new file mode 100644
index 00000000000..02c840d12c9
--- /dev/null
+++ b/app/assets/images/emoji/flag_aw.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ax.png b/app/assets/images/emoji/flag_ax.png
new file mode 100644
index 00000000000..fc5466174bb
--- /dev/null
+++ b/app/assets/images/emoji/flag_ax.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_az.png b/app/assets/images/emoji/flag_az.png
new file mode 100644
index 00000000000..89d3d15fd9f
--- /dev/null
+++ b/app/assets/images/emoji/flag_az.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ba.png b/app/assets/images/emoji/flag_ba.png
new file mode 100644
index 00000000000..25fe407e13c
--- /dev/null
+++ b/app/assets/images/emoji/flag_ba.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_bb.png b/app/assets/images/emoji/flag_bb.png
new file mode 100644
index 00000000000..bccd8c5c9b0
--- /dev/null
+++ b/app/assets/images/emoji/flag_bb.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_bd.png b/app/assets/images/emoji/flag_bd.png
new file mode 100644
index 00000000000..b0597a3149b
--- /dev/null
+++ b/app/assets/images/emoji/flag_bd.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_be.png b/app/assets/images/emoji/flag_be.png
new file mode 100644
index 00000000000..551f086e3c4
--- /dev/null
+++ b/app/assets/images/emoji/flag_be.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_bf.png b/app/assets/images/emoji/flag_bf.png
new file mode 100644
index 00000000000..444d4829f94
--- /dev/null
+++ b/app/assets/images/emoji/flag_bf.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_bg.png b/app/assets/images/emoji/flag_bg.png
new file mode 100644
index 00000000000..821eee5e170
--- /dev/null
+++ b/app/assets/images/emoji/flag_bg.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_bh.png b/app/assets/images/emoji/flag_bh.png
new file mode 100644
index 00000000000..f33724249f0
--- /dev/null
+++ b/app/assets/images/emoji/flag_bh.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_bi.png b/app/assets/images/emoji/flag_bi.png
new file mode 100644
index 00000000000..ea20ac93211
--- /dev/null
+++ b/app/assets/images/emoji/flag_bi.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_bj.png b/app/assets/images/emoji/flag_bj.png
new file mode 100644
index 00000000000..7cca4f80457
--- /dev/null
+++ b/app/assets/images/emoji/flag_bj.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_bl.png b/app/assets/images/emoji/flag_bl.png
new file mode 100644
index 00000000000..1082e78999f
--- /dev/null
+++ b/app/assets/images/emoji/flag_bl.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_black.png b/app/assets/images/emoji/flag_black.png
new file mode 100644
index 00000000000..0e28d05d5ac
--- /dev/null
+++ b/app/assets/images/emoji/flag_black.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_bm.png b/app/assets/images/emoji/flag_bm.png
new file mode 100644
index 00000000000..ab8cafdac63
--- /dev/null
+++ b/app/assets/images/emoji/flag_bm.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_bn.png b/app/assets/images/emoji/flag_bn.png
new file mode 100644
index 00000000000..caa9329a896
--- /dev/null
+++ b/app/assets/images/emoji/flag_bn.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_bo.png b/app/assets/images/emoji/flag_bo.png
new file mode 100644
index 00000000000..98af62b3da7
--- /dev/null
+++ b/app/assets/images/emoji/flag_bo.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_bq.png b/app/assets/images/emoji/flag_bq.png
new file mode 100644
index 00000000000..cb978ef9de9
--- /dev/null
+++ b/app/assets/images/emoji/flag_bq.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_br.png b/app/assets/images/emoji/flag_br.png
new file mode 100644
index 00000000000..b139366a42b
--- /dev/null
+++ b/app/assets/images/emoji/flag_br.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_bs.png b/app/assets/images/emoji/flag_bs.png
new file mode 100644
index 00000000000..d36bcd2fb52
--- /dev/null
+++ b/app/assets/images/emoji/flag_bs.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_bt.png b/app/assets/images/emoji/flag_bt.png
new file mode 100644
index 00000000000..ed57aa0360e
--- /dev/null
+++ b/app/assets/images/emoji/flag_bt.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_bv.png b/app/assets/images/emoji/flag_bv.png
new file mode 100644
index 00000000000..5884e648228
--- /dev/null
+++ b/app/assets/images/emoji/flag_bv.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_bw.png b/app/assets/images/emoji/flag_bw.png
new file mode 100644
index 00000000000..cb12f34739d
--- /dev/null
+++ b/app/assets/images/emoji/flag_bw.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_by.png b/app/assets/images/emoji/flag_by.png
new file mode 100644
index 00000000000..859c05beb13
--- /dev/null
+++ b/app/assets/images/emoji/flag_by.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_bz.png b/app/assets/images/emoji/flag_bz.png
new file mode 100644
index 00000000000..34761cd03d8
--- /dev/null
+++ b/app/assets/images/emoji/flag_bz.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ca.png b/app/assets/images/emoji/flag_ca.png
new file mode 100644
index 00000000000..7c5b390e85b
--- /dev/null
+++ b/app/assets/images/emoji/flag_ca.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_cc.png b/app/assets/images/emoji/flag_cc.png
new file mode 100644
index 00000000000..b6555a23d83
--- /dev/null
+++ b/app/assets/images/emoji/flag_cc.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_cd.png b/app/assets/images/emoji/flag_cd.png
new file mode 100644
index 00000000000..fa92009771d
--- /dev/null
+++ b/app/assets/images/emoji/flag_cd.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_cf.png b/app/assets/images/emoji/flag_cf.png
new file mode 100644
index 00000000000..b969ae29ea9
--- /dev/null
+++ b/app/assets/images/emoji/flag_cf.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_cg.png b/app/assets/images/emoji/flag_cg.png
new file mode 100644
index 00000000000..3a38a40a95e
--- /dev/null
+++ b/app/assets/images/emoji/flag_cg.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ch.png b/app/assets/images/emoji/flag_ch.png
new file mode 100644
index 00000000000..5ff86b8a3b7
--- /dev/null
+++ b/app/assets/images/emoji/flag_ch.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ci.png b/app/assets/images/emoji/flag_ci.png
new file mode 100644
index 00000000000..e3b4d15c7f1
--- /dev/null
+++ b/app/assets/images/emoji/flag_ci.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ck.png b/app/assets/images/emoji/flag_ck.png
new file mode 100644
index 00000000000..b6b53dbc1c4
--- /dev/null
+++ b/app/assets/images/emoji/flag_ck.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_cl.png b/app/assets/images/emoji/flag_cl.png
new file mode 100644
index 00000000000..c9390da5499
--- /dev/null
+++ b/app/assets/images/emoji/flag_cl.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_cm.png b/app/assets/images/emoji/flag_cm.png
new file mode 100644
index 00000000000..2d3f6ec4518
--- /dev/null
+++ b/app/assets/images/emoji/flag_cm.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_cn.png b/app/assets/images/emoji/flag_cn.png
new file mode 100644
index 00000000000..0a7f350a6d2
--- /dev/null
+++ b/app/assets/images/emoji/flag_cn.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_co.png b/app/assets/images/emoji/flag_co.png
new file mode 100644
index 00000000000..7e0f5e0dc3c
--- /dev/null
+++ b/app/assets/images/emoji/flag_co.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_cp.png b/app/assets/images/emoji/flag_cp.png
new file mode 100644
index 00000000000..70c761036bd
--- /dev/null
+++ b/app/assets/images/emoji/flag_cp.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_cr.png b/app/assets/images/emoji/flag_cr.png
new file mode 100644
index 00000000000..a5fce126515
--- /dev/null
+++ b/app/assets/images/emoji/flag_cr.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_cu.png b/app/assets/images/emoji/flag_cu.png
new file mode 100644
index 00000000000..447328f7dfd
--- /dev/null
+++ b/app/assets/images/emoji/flag_cu.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_cv.png b/app/assets/images/emoji/flag_cv.png
new file mode 100644
index 00000000000..43faf4d64d5
--- /dev/null
+++ b/app/assets/images/emoji/flag_cv.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_cw.png b/app/assets/images/emoji/flag_cw.png
new file mode 100644
index 00000000000..eb39e8d0078
--- /dev/null
+++ b/app/assets/images/emoji/flag_cw.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_cx.png b/app/assets/images/emoji/flag_cx.png
new file mode 100644
index 00000000000..09d21359f3a
--- /dev/null
+++ b/app/assets/images/emoji/flag_cx.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_cy.png b/app/assets/images/emoji/flag_cy.png
new file mode 100644
index 00000000000..154a7aa3176
--- /dev/null
+++ b/app/assets/images/emoji/flag_cy.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_cz.png b/app/assets/images/emoji/flag_cz.png
new file mode 100644
index 00000000000..9737ca223c7
--- /dev/null
+++ b/app/assets/images/emoji/flag_cz.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_de.png b/app/assets/images/emoji/flag_de.png
new file mode 100644
index 00000000000..98ed76b3bab
--- /dev/null
+++ b/app/assets/images/emoji/flag_de.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_dg.png b/app/assets/images/emoji/flag_dg.png
new file mode 100644
index 00000000000..aae927d14b8
--- /dev/null
+++ b/app/assets/images/emoji/flag_dg.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_dj.png b/app/assets/images/emoji/flag_dj.png
new file mode 100644
index 00000000000..73c2a2acbd9
--- /dev/null
+++ b/app/assets/images/emoji/flag_dj.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_dk.png b/app/assets/images/emoji/flag_dk.png
new file mode 100644
index 00000000000..e5a60b06256
--- /dev/null
+++ b/app/assets/images/emoji/flag_dk.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_dm.png b/app/assets/images/emoji/flag_dm.png
new file mode 100644
index 00000000000..50f8a53981d
--- /dev/null
+++ b/app/assets/images/emoji/flag_dm.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_do.png b/app/assets/images/emoji/flag_do.png
new file mode 100644
index 00000000000..037a45d7c26
--- /dev/null
+++ b/app/assets/images/emoji/flag_do.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_dz.png b/app/assets/images/emoji/flag_dz.png
new file mode 100644
index 00000000000..24945b10f2d
--- /dev/null
+++ b/app/assets/images/emoji/flag_dz.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ea.png b/app/assets/images/emoji/flag_ea.png
new file mode 100644
index 00000000000..356ff347838
--- /dev/null
+++ b/app/assets/images/emoji/flag_ea.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ec.png b/app/assets/images/emoji/flag_ec.png
new file mode 100644
index 00000000000..13814594619
--- /dev/null
+++ b/app/assets/images/emoji/flag_ec.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ee.png b/app/assets/images/emoji/flag_ee.png
new file mode 100644
index 00000000000..84f317e7747
--- /dev/null
+++ b/app/assets/images/emoji/flag_ee.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_eg.png b/app/assets/images/emoji/flag_eg.png
new file mode 100644
index 00000000000..57786064a95
--- /dev/null
+++ b/app/assets/images/emoji/flag_eg.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_eh.png b/app/assets/images/emoji/flag_eh.png
new file mode 100644
index 00000000000..4d7a76687f6
--- /dev/null
+++ b/app/assets/images/emoji/flag_eh.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_er.png b/app/assets/images/emoji/flag_er.png
new file mode 100644
index 00000000000..0c3c724c1fb
--- /dev/null
+++ b/app/assets/images/emoji/flag_er.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_es.png b/app/assets/images/emoji/flag_es.png
new file mode 100644
index 00000000000..3e73597a225
--- /dev/null
+++ b/app/assets/images/emoji/flag_es.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_et.png b/app/assets/images/emoji/flag_et.png
new file mode 100644
index 00000000000..9560a134c97
--- /dev/null
+++ b/app/assets/images/emoji/flag_et.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_eu.png b/app/assets/images/emoji/flag_eu.png
new file mode 100644
index 00000000000..0b456cf3330
--- /dev/null
+++ b/app/assets/images/emoji/flag_eu.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_fi.png b/app/assets/images/emoji/flag_fi.png
new file mode 100644
index 00000000000..ebcf58abfc5
--- /dev/null
+++ b/app/assets/images/emoji/flag_fi.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_fj.png b/app/assets/images/emoji/flag_fj.png
new file mode 100644
index 00000000000..9cc8c37fe37
--- /dev/null
+++ b/app/assets/images/emoji/flag_fj.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_fk.png b/app/assets/images/emoji/flag_fk.png
new file mode 100644
index 00000000000..61372fd2549
--- /dev/null
+++ b/app/assets/images/emoji/flag_fk.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_fm.png b/app/assets/images/emoji/flag_fm.png
new file mode 100644
index 00000000000..0889825c8e1
--- /dev/null
+++ b/app/assets/images/emoji/flag_fm.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_fo.png b/app/assets/images/emoji/flag_fo.png
new file mode 100644
index 00000000000..9a4431b0831
--- /dev/null
+++ b/app/assets/images/emoji/flag_fo.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_fr.png b/app/assets/images/emoji/flag_fr.png
new file mode 100644
index 00000000000..62ca19c3fcf
--- /dev/null
+++ b/app/assets/images/emoji/flag_fr.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ga.png b/app/assets/images/emoji/flag_ga.png
new file mode 100644
index 00000000000..2e68e527a3e
--- /dev/null
+++ b/app/assets/images/emoji/flag_ga.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_gb.png b/app/assets/images/emoji/flag_gb.png
new file mode 100644
index 00000000000..3ed10f62347
--- /dev/null
+++ b/app/assets/images/emoji/flag_gb.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_gd.png b/app/assets/images/emoji/flag_gd.png
new file mode 100644
index 00000000000..527aad33807
--- /dev/null
+++ b/app/assets/images/emoji/flag_gd.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ge.png b/app/assets/images/emoji/flag_ge.png
new file mode 100644
index 00000000000..a75d142480d
--- /dev/null
+++ b/app/assets/images/emoji/flag_ge.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_gf.png b/app/assets/images/emoji/flag_gf.png
new file mode 100644
index 00000000000..0cf96f327c0
--- /dev/null
+++ b/app/assets/images/emoji/flag_gf.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_gg.png b/app/assets/images/emoji/flag_gg.png
new file mode 100644
index 00000000000..970002c7f76
--- /dev/null
+++ b/app/assets/images/emoji/flag_gg.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_gh.png b/app/assets/images/emoji/flag_gh.png
new file mode 100644
index 00000000000..f31b5eb7b45
--- /dev/null
+++ b/app/assets/images/emoji/flag_gh.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_gi.png b/app/assets/images/emoji/flag_gi.png
new file mode 100644
index 00000000000..e554a2a1d0c
--- /dev/null
+++ b/app/assets/images/emoji/flag_gi.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_gl.png b/app/assets/images/emoji/flag_gl.png
new file mode 100644
index 00000000000..2e795dd4e33
--- /dev/null
+++ b/app/assets/images/emoji/flag_gl.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_gm.png b/app/assets/images/emoji/flag_gm.png
new file mode 100644
index 00000000000..bb69c0975a3
--- /dev/null
+++ b/app/assets/images/emoji/flag_gm.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_gn.png b/app/assets/images/emoji/flag_gn.png
new file mode 100644
index 00000000000..1981f61dbf5
--- /dev/null
+++ b/app/assets/images/emoji/flag_gn.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_gp.png b/app/assets/images/emoji/flag_gp.png
new file mode 100644
index 00000000000..10e42e672bd
--- /dev/null
+++ b/app/assets/images/emoji/flag_gp.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_gq.png b/app/assets/images/emoji/flag_gq.png
new file mode 100644
index 00000000000..11475e61eeb
--- /dev/null
+++ b/app/assets/images/emoji/flag_gq.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_gr.png b/app/assets/images/emoji/flag_gr.png
new file mode 100644
index 00000000000..0f6bb1b6b94
--- /dev/null
+++ b/app/assets/images/emoji/flag_gr.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_gs.png b/app/assets/images/emoji/flag_gs.png
new file mode 100644
index 00000000000..6fc92780453
--- /dev/null
+++ b/app/assets/images/emoji/flag_gs.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_gt.png b/app/assets/images/emoji/flag_gt.png
new file mode 100644
index 00000000000..7213d4139ed
--- /dev/null
+++ b/app/assets/images/emoji/flag_gt.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_gu.png b/app/assets/images/emoji/flag_gu.png
new file mode 100644
index 00000000000..4027549ca3c
--- /dev/null
+++ b/app/assets/images/emoji/flag_gu.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_gw.png b/app/assets/images/emoji/flag_gw.png
new file mode 100644
index 00000000000..6357f6225f4
--- /dev/null
+++ b/app/assets/images/emoji/flag_gw.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_gy.png b/app/assets/images/emoji/flag_gy.png
new file mode 100644
index 00000000000..746e2fb7e44
--- /dev/null
+++ b/app/assets/images/emoji/flag_gy.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_hk.png b/app/assets/images/emoji/flag_hk.png
new file mode 100644
index 00000000000..cf0c7151b56
--- /dev/null
+++ b/app/assets/images/emoji/flag_hk.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_hm.png b/app/assets/images/emoji/flag_hm.png
new file mode 100644
index 00000000000..b613509e466
--- /dev/null
+++ b/app/assets/images/emoji/flag_hm.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_hn.png b/app/assets/images/emoji/flag_hn.png
new file mode 100644
index 00000000000..402cdcefdf8
--- /dev/null
+++ b/app/assets/images/emoji/flag_hn.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_hr.png b/app/assets/images/emoji/flag_hr.png
new file mode 100644
index 00000000000..46f4f06b4f2
--- /dev/null
+++ b/app/assets/images/emoji/flag_hr.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ht.png b/app/assets/images/emoji/flag_ht.png
new file mode 100644
index 00000000000..d8d0c888498
--- /dev/null
+++ b/app/assets/images/emoji/flag_ht.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_hu.png b/app/assets/images/emoji/flag_hu.png
new file mode 100644
index 00000000000..a898de636a5
--- /dev/null
+++ b/app/assets/images/emoji/flag_hu.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ic.png b/app/assets/images/emoji/flag_ic.png
new file mode 100644
index 00000000000..69fd990aa95
--- /dev/null
+++ b/app/assets/images/emoji/flag_ic.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_id.png b/app/assets/images/emoji/flag_id.png
new file mode 100644
index 00000000000..85b4c063a45
--- /dev/null
+++ b/app/assets/images/emoji/flag_id.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ie.png b/app/assets/images/emoji/flag_ie.png
new file mode 100644
index 00000000000..a28295838cc
--- /dev/null
+++ b/app/assets/images/emoji/flag_ie.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_il.png b/app/assets/images/emoji/flag_il.png
new file mode 100644
index 00000000000..85c410d45fb
--- /dev/null
+++ b/app/assets/images/emoji/flag_il.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_im.png b/app/assets/images/emoji/flag_im.png
new file mode 100644
index 00000000000..60a2458e38e
--- /dev/null
+++ b/app/assets/images/emoji/flag_im.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_in.png b/app/assets/images/emoji/flag_in.png
new file mode 100644
index 00000000000..feccc8952ce
--- /dev/null
+++ b/app/assets/images/emoji/flag_in.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_io.png b/app/assets/images/emoji/flag_io.png
new file mode 100644
index 00000000000..aae927d14b8
--- /dev/null
+++ b/app/assets/images/emoji/flag_io.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_iq.png b/app/assets/images/emoji/flag_iq.png
new file mode 100644
index 00000000000..41fd1db6f86
--- /dev/null
+++ b/app/assets/images/emoji/flag_iq.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ir.png b/app/assets/images/emoji/flag_ir.png
new file mode 100644
index 00000000000..ff7aaf62ba6
--- /dev/null
+++ b/app/assets/images/emoji/flag_ir.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_is.png b/app/assets/images/emoji/flag_is.png
new file mode 100644
index 00000000000..ad8d4131dd2
--- /dev/null
+++ b/app/assets/images/emoji/flag_is.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_it.png b/app/assets/images/emoji/flag_it.png
new file mode 100644
index 00000000000..f21563ec533
--- /dev/null
+++ b/app/assets/images/emoji/flag_it.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_je.png b/app/assets/images/emoji/flag_je.png
new file mode 100644
index 00000000000..198a918f6a4
--- /dev/null
+++ b/app/assets/images/emoji/flag_je.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_jm.png b/app/assets/images/emoji/flag_jm.png
new file mode 100644
index 00000000000..f84e4f9e8db
--- /dev/null
+++ b/app/assets/images/emoji/flag_jm.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_jo.png b/app/assets/images/emoji/flag_jo.png
new file mode 100644
index 00000000000..20bfa147e3e
--- /dev/null
+++ b/app/assets/images/emoji/flag_jo.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_jp.png b/app/assets/images/emoji/flag_jp.png
new file mode 100644
index 00000000000..8d8838e4708
--- /dev/null
+++ b/app/assets/images/emoji/flag_jp.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ke.png b/app/assets/images/emoji/flag_ke.png
new file mode 100644
index 00000000000..9e417ab3009
--- /dev/null
+++ b/app/assets/images/emoji/flag_ke.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_kg.png b/app/assets/images/emoji/flag_kg.png
new file mode 100644
index 00000000000..2f2d848fe58
--- /dev/null
+++ b/app/assets/images/emoji/flag_kg.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_kh.png b/app/assets/images/emoji/flag_kh.png
new file mode 100644
index 00000000000..9a2877dd620
--- /dev/null
+++ b/app/assets/images/emoji/flag_kh.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ki.png b/app/assets/images/emoji/flag_ki.png
new file mode 100644
index 00000000000..10e507e3245
--- /dev/null
+++ b/app/assets/images/emoji/flag_ki.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_km.png b/app/assets/images/emoji/flag_km.png
new file mode 100644
index 00000000000..bd5a0588e03
--- /dev/null
+++ b/app/assets/images/emoji/flag_km.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_kn.png b/app/assets/images/emoji/flag_kn.png
new file mode 100644
index 00000000000..776207c9605
--- /dev/null
+++ b/app/assets/images/emoji/flag_kn.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_kp.png b/app/assets/images/emoji/flag_kp.png
new file mode 100644
index 00000000000..6b3fd89eaaa
--- /dev/null
+++ b/app/assets/images/emoji/flag_kp.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_kr.png b/app/assets/images/emoji/flag_kr.png
new file mode 100644
index 00000000000..833a88116e1
--- /dev/null
+++ b/app/assets/images/emoji/flag_kr.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_kw.png b/app/assets/images/emoji/flag_kw.png
new file mode 100644
index 00000000000..4d19bfa6ca7
--- /dev/null
+++ b/app/assets/images/emoji/flag_kw.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ky.png b/app/assets/images/emoji/flag_ky.png
new file mode 100644
index 00000000000..40daa4da597
--- /dev/null
+++ b/app/assets/images/emoji/flag_ky.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_kz.png b/app/assets/images/emoji/flag_kz.png
new file mode 100644
index 00000000000..2f97a8fd3c6
--- /dev/null
+++ b/app/assets/images/emoji/flag_kz.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_la.png b/app/assets/images/emoji/flag_la.png
new file mode 100644
index 00000000000..4d4179f34f6
--- /dev/null
+++ b/app/assets/images/emoji/flag_la.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_lb.png b/app/assets/images/emoji/flag_lb.png
new file mode 100644
index 00000000000..3d594467011
--- /dev/null
+++ b/app/assets/images/emoji/flag_lb.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_lc.png b/app/assets/images/emoji/flag_lc.png
new file mode 100644
index 00000000000..45547b1e439
--- /dev/null
+++ b/app/assets/images/emoji/flag_lc.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_li.png b/app/assets/images/emoji/flag_li.png
new file mode 100644
index 00000000000..0eafa6a2215
--- /dev/null
+++ b/app/assets/images/emoji/flag_li.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_lk.png b/app/assets/images/emoji/flag_lk.png
new file mode 100644
index 00000000000..ab4fe10c40c
--- /dev/null
+++ b/app/assets/images/emoji/flag_lk.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_lr.png b/app/assets/images/emoji/flag_lr.png
new file mode 100644
index 00000000000..f66f267fea2
--- /dev/null
+++ b/app/assets/images/emoji/flag_lr.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ls.png b/app/assets/images/emoji/flag_ls.png
new file mode 100644
index 00000000000..24745631e3c
--- /dev/null
+++ b/app/assets/images/emoji/flag_ls.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_lt.png b/app/assets/images/emoji/flag_lt.png
new file mode 100644
index 00000000000..d644b56d62a
--- /dev/null
+++ b/app/assets/images/emoji/flag_lt.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_lu.png b/app/assets/images/emoji/flag_lu.png
new file mode 100644
index 00000000000..a2df9c92994
--- /dev/null
+++ b/app/assets/images/emoji/flag_lu.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_lv.png b/app/assets/images/emoji/flag_lv.png
new file mode 100644
index 00000000000..ae680d5f0e3
--- /dev/null
+++ b/app/assets/images/emoji/flag_lv.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ly.png b/app/assets/images/emoji/flag_ly.png
new file mode 100644
index 00000000000..f6e77b0f3ba
--- /dev/null
+++ b/app/assets/images/emoji/flag_ly.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ma.png b/app/assets/images/emoji/flag_ma.png
new file mode 100644
index 00000000000..c4a056722cd
--- /dev/null
+++ b/app/assets/images/emoji/flag_ma.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_mc.png b/app/assets/images/emoji/flag_mc.png
new file mode 100644
index 00000000000..d479eab98cb
--- /dev/null
+++ b/app/assets/images/emoji/flag_mc.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_md.png b/app/assets/images/emoji/flag_md.png
new file mode 100644
index 00000000000..a7a72539872
--- /dev/null
+++ b/app/assets/images/emoji/flag_md.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_me.png b/app/assets/images/emoji/flag_me.png
new file mode 100644
index 00000000000..7c771e7e120
--- /dev/null
+++ b/app/assets/images/emoji/flag_me.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_mf.png b/app/assets/images/emoji/flag_mf.png
new file mode 100644
index 00000000000..70c761036bd
--- /dev/null
+++ b/app/assets/images/emoji/flag_mf.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_mg.png b/app/assets/images/emoji/flag_mg.png
new file mode 100644
index 00000000000..2f3ccdda76f
--- /dev/null
+++ b/app/assets/images/emoji/flag_mg.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_mh.png b/app/assets/images/emoji/flag_mh.png
new file mode 100644
index 00000000000..598016481c1
--- /dev/null
+++ b/app/assets/images/emoji/flag_mh.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_mk.png b/app/assets/images/emoji/flag_mk.png
new file mode 100644
index 00000000000..7ba775ee75c
--- /dev/null
+++ b/app/assets/images/emoji/flag_mk.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ml.png b/app/assets/images/emoji/flag_ml.png
new file mode 100644
index 00000000000..68343785468
--- /dev/null
+++ b/app/assets/images/emoji/flag_ml.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_mm.png b/app/assets/images/emoji/flag_mm.png
new file mode 100644
index 00000000000..37dc7d71591
--- /dev/null
+++ b/app/assets/images/emoji/flag_mm.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_mn.png b/app/assets/images/emoji/flag_mn.png
new file mode 100644
index 00000000000..1f146bbcd1a
--- /dev/null
+++ b/app/assets/images/emoji/flag_mn.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_mo.png b/app/assets/images/emoji/flag_mo.png
new file mode 100644
index 00000000000..7edde31f64b
--- /dev/null
+++ b/app/assets/images/emoji/flag_mo.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_mp.png b/app/assets/images/emoji/flag_mp.png
new file mode 100644
index 00000000000..17ec1c441ed
--- /dev/null
+++ b/app/assets/images/emoji/flag_mp.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_mq.png b/app/assets/images/emoji/flag_mq.png
new file mode 100644
index 00000000000..1e672dc9087
--- /dev/null
+++ b/app/assets/images/emoji/flag_mq.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_mr.png b/app/assets/images/emoji/flag_mr.png
new file mode 100644
index 00000000000..f87de46effe
--- /dev/null
+++ b/app/assets/images/emoji/flag_mr.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ms.png b/app/assets/images/emoji/flag_ms.png
new file mode 100644
index 00000000000..480b0d4ebda
--- /dev/null
+++ b/app/assets/images/emoji/flag_ms.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_mt.png b/app/assets/images/emoji/flag_mt.png
new file mode 100644
index 00000000000..c9e1dbdce82
--- /dev/null
+++ b/app/assets/images/emoji/flag_mt.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_mu.png b/app/assets/images/emoji/flag_mu.png
new file mode 100644
index 00000000000..55b33cb7c33
--- /dev/null
+++ b/app/assets/images/emoji/flag_mu.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_mv.png b/app/assets/images/emoji/flag_mv.png
new file mode 100644
index 00000000000..ce5867126ae
--- /dev/null
+++ b/app/assets/images/emoji/flag_mv.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_mw.png b/app/assets/images/emoji/flag_mw.png
new file mode 100644
index 00000000000..003d8548401
--- /dev/null
+++ b/app/assets/images/emoji/flag_mw.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_mx.png b/app/assets/images/emoji/flag_mx.png
new file mode 100644
index 00000000000..42572bcd0ba
--- /dev/null
+++ b/app/assets/images/emoji/flag_mx.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_my.png b/app/assets/images/emoji/flag_my.png
new file mode 100644
index 00000000000..17526c26742
--- /dev/null
+++ b/app/assets/images/emoji/flag_my.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_mz.png b/app/assets/images/emoji/flag_mz.png
new file mode 100644
index 00000000000..2352a78e786
--- /dev/null
+++ b/app/assets/images/emoji/flag_mz.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_na.png b/app/assets/images/emoji/flag_na.png
new file mode 100644
index 00000000000..ed31c3df04d
--- /dev/null
+++ b/app/assets/images/emoji/flag_na.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_nc.png b/app/assets/images/emoji/flag_nc.png
new file mode 100644
index 00000000000..90b3afebfa3
--- /dev/null
+++ b/app/assets/images/emoji/flag_nc.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ne.png b/app/assets/images/emoji/flag_ne.png
new file mode 100644
index 00000000000..f98a1173c2a
--- /dev/null
+++ b/app/assets/images/emoji/flag_ne.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_nf.png b/app/assets/images/emoji/flag_nf.png
new file mode 100644
index 00000000000..9099e767420
--- /dev/null
+++ b/app/assets/images/emoji/flag_nf.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ng.png b/app/assets/images/emoji/flag_ng.png
new file mode 100644
index 00000000000..ea0abeff1a1
--- /dev/null
+++ b/app/assets/images/emoji/flag_ng.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ni.png b/app/assets/images/emoji/flag_ni.png
new file mode 100644
index 00000000000..772920dfa10
--- /dev/null
+++ b/app/assets/images/emoji/flag_ni.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_nl.png b/app/assets/images/emoji/flag_nl.png
new file mode 100644
index 00000000000..83a0e817e41
--- /dev/null
+++ b/app/assets/images/emoji/flag_nl.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_no.png b/app/assets/images/emoji/flag_no.png
new file mode 100644
index 00000000000..99d3142eb7b
--- /dev/null
+++ b/app/assets/images/emoji/flag_no.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_np.png b/app/assets/images/emoji/flag_np.png
new file mode 100644
index 00000000000..87425a8dfef
--- /dev/null
+++ b/app/assets/images/emoji/flag_np.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_nr.png b/app/assets/images/emoji/flag_nr.png
new file mode 100644
index 00000000000..b3e3a5d5621
--- /dev/null
+++ b/app/assets/images/emoji/flag_nr.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_nu.png b/app/assets/images/emoji/flag_nu.png
new file mode 100644
index 00000000000..f03614443ee
--- /dev/null
+++ b/app/assets/images/emoji/flag_nu.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_nz.png b/app/assets/images/emoji/flag_nz.png
new file mode 100644
index 00000000000..a4eeeab9cd9
--- /dev/null
+++ b/app/assets/images/emoji/flag_nz.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_om.png b/app/assets/images/emoji/flag_om.png
new file mode 100644
index 00000000000..ea824ba31e7
--- /dev/null
+++ b/app/assets/images/emoji/flag_om.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_pa.png b/app/assets/images/emoji/flag_pa.png
new file mode 100644
index 00000000000..c3091d89889
--- /dev/null
+++ b/app/assets/images/emoji/flag_pa.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_pe.png b/app/assets/images/emoji/flag_pe.png
new file mode 100644
index 00000000000..39223aa9dbb
--- /dev/null
+++ b/app/assets/images/emoji/flag_pe.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_pf.png b/app/assets/images/emoji/flag_pf.png
new file mode 100644
index 00000000000..113445f8f6e
--- /dev/null
+++ b/app/assets/images/emoji/flag_pf.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_pg.png b/app/assets/images/emoji/flag_pg.png
new file mode 100644
index 00000000000..825e9dcb762
--- /dev/null
+++ b/app/assets/images/emoji/flag_pg.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ph.png b/app/assets/images/emoji/flag_ph.png
new file mode 100644
index 00000000000..8260e15bd2c
--- /dev/null
+++ b/app/assets/images/emoji/flag_ph.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_pk.png b/app/assets/images/emoji/flag_pk.png
new file mode 100644
index 00000000000..a7b6a1c5074
--- /dev/null
+++ b/app/assets/images/emoji/flag_pk.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_pl.png b/app/assets/images/emoji/flag_pl.png
new file mode 100644
index 00000000000..19de2edec11
--- /dev/null
+++ b/app/assets/images/emoji/flag_pl.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_pm.png b/app/assets/images/emoji/flag_pm.png
new file mode 100644
index 00000000000..2ca60554193
--- /dev/null
+++ b/app/assets/images/emoji/flag_pm.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_pn.png b/app/assets/images/emoji/flag_pn.png
new file mode 100644
index 00000000000..f2263b154bc
--- /dev/null
+++ b/app/assets/images/emoji/flag_pn.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_pr.png b/app/assets/images/emoji/flag_pr.png
new file mode 100644
index 00000000000..d0209cddb79
--- /dev/null
+++ b/app/assets/images/emoji/flag_pr.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ps.png b/app/assets/images/emoji/flag_ps.png
new file mode 100644
index 00000000000..7ccab09778b
--- /dev/null
+++ b/app/assets/images/emoji/flag_ps.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_pt.png b/app/assets/images/emoji/flag_pt.png
new file mode 100644
index 00000000000..cc93f27c64b
--- /dev/null
+++ b/app/assets/images/emoji/flag_pt.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_pw.png b/app/assets/images/emoji/flag_pw.png
new file mode 100644
index 00000000000..154b2f12d3c
--- /dev/null
+++ b/app/assets/images/emoji/flag_pw.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_py.png b/app/assets/images/emoji/flag_py.png
new file mode 100644
index 00000000000..662ad2f6ff1
--- /dev/null
+++ b/app/assets/images/emoji/flag_py.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_qa.png b/app/assets/images/emoji/flag_qa.png
new file mode 100644
index 00000000000..a01d8b05cc7
--- /dev/null
+++ b/app/assets/images/emoji/flag_qa.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_re.png b/app/assets/images/emoji/flag_re.png
new file mode 100644
index 00000000000..57f2bbe9df8
--- /dev/null
+++ b/app/assets/images/emoji/flag_re.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ro.png b/app/assets/images/emoji/flag_ro.png
new file mode 100644
index 00000000000..3e48c447706
--- /dev/null
+++ b/app/assets/images/emoji/flag_ro.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_rs.png b/app/assets/images/emoji/flag_rs.png
new file mode 100644
index 00000000000..9df6c9a5235
--- /dev/null
+++ b/app/assets/images/emoji/flag_rs.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ru.png b/app/assets/images/emoji/flag_ru.png
new file mode 100644
index 00000000000..e50c9db90e7
--- /dev/null
+++ b/app/assets/images/emoji/flag_ru.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_rw.png b/app/assets/images/emoji/flag_rw.png
new file mode 100644
index 00000000000..c238c874e1d
--- /dev/null
+++ b/app/assets/images/emoji/flag_rw.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_sa.png b/app/assets/images/emoji/flag_sa.png
new file mode 100644
index 00000000000..4941be7d198
--- /dev/null
+++ b/app/assets/images/emoji/flag_sa.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_sb.png b/app/assets/images/emoji/flag_sb.png
new file mode 100644
index 00000000000..7d8f1ac6130
--- /dev/null
+++ b/app/assets/images/emoji/flag_sb.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_sc.png b/app/assets/images/emoji/flag_sc.png
new file mode 100644
index 00000000000..6ae4d90765e
--- /dev/null
+++ b/app/assets/images/emoji/flag_sc.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_sd.png b/app/assets/images/emoji/flag_sd.png
new file mode 100644
index 00000000000..963be1b36fb
--- /dev/null
+++ b/app/assets/images/emoji/flag_sd.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_se.png b/app/assets/images/emoji/flag_se.png
new file mode 100644
index 00000000000..fc0d0e0ce89
--- /dev/null
+++ b/app/assets/images/emoji/flag_se.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_sg.png b/app/assets/images/emoji/flag_sg.png
new file mode 100644
index 00000000000..de3c7737c42
--- /dev/null
+++ b/app/assets/images/emoji/flag_sg.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_sh.png b/app/assets/images/emoji/flag_sh.png
new file mode 100644
index 00000000000..40cd9e44e96
--- /dev/null
+++ b/app/assets/images/emoji/flag_sh.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_si.png b/app/assets/images/emoji/flag_si.png
new file mode 100644
index 00000000000..e308999dba2
--- /dev/null
+++ b/app/assets/images/emoji/flag_si.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_sj.png b/app/assets/images/emoji/flag_sj.png
new file mode 100644
index 00000000000..5884e648228
--- /dev/null
+++ b/app/assets/images/emoji/flag_sj.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_sk.png b/app/assets/images/emoji/flag_sk.png
new file mode 100644
index 00000000000..4259d0e1418
--- /dev/null
+++ b/app/assets/images/emoji/flag_sk.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_sl.png b/app/assets/images/emoji/flag_sl.png
new file mode 100644
index 00000000000..d2cc68830ab
--- /dev/null
+++ b/app/assets/images/emoji/flag_sl.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_sm.png b/app/assets/images/emoji/flag_sm.png
new file mode 100644
index 00000000000..03b8708754e
--- /dev/null
+++ b/app/assets/images/emoji/flag_sm.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_sn.png b/app/assets/images/emoji/flag_sn.png
new file mode 100644
index 00000000000..5368bbe93df
--- /dev/null
+++ b/app/assets/images/emoji/flag_sn.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_so.png b/app/assets/images/emoji/flag_so.png
new file mode 100644
index 00000000000..68a0597365a
--- /dev/null
+++ b/app/assets/images/emoji/flag_so.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_sr.png b/app/assets/images/emoji/flag_sr.png
new file mode 100644
index 00000000000..d3251327035
--- /dev/null
+++ b/app/assets/images/emoji/flag_sr.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ss.png b/app/assets/images/emoji/flag_ss.png
new file mode 100644
index 00000000000..122977e798f
--- /dev/null
+++ b/app/assets/images/emoji/flag_ss.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_st.png b/app/assets/images/emoji/flag_st.png
new file mode 100644
index 00000000000..f83a863d612
--- /dev/null
+++ b/app/assets/images/emoji/flag_st.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_sv.png b/app/assets/images/emoji/flag_sv.png
new file mode 100644
index 00000000000..efb83e2f253
--- /dev/null
+++ b/app/assets/images/emoji/flag_sv.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_sx.png b/app/assets/images/emoji/flag_sx.png
new file mode 100644
index 00000000000..94b760fbedf
--- /dev/null
+++ b/app/assets/images/emoji/flag_sx.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_sy.png b/app/assets/images/emoji/flag_sy.png
new file mode 100644
index 00000000000..09a8ee8f78c
--- /dev/null
+++ b/app/assets/images/emoji/flag_sy.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_sz.png b/app/assets/images/emoji/flag_sz.png
new file mode 100644
index 00000000000..f74e82ea1fd
--- /dev/null
+++ b/app/assets/images/emoji/flag_sz.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ta.png b/app/assets/images/emoji/flag_ta.png
new file mode 100644
index 00000000000..b44283e90e2
--- /dev/null
+++ b/app/assets/images/emoji/flag_ta.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_tc.png b/app/assets/images/emoji/flag_tc.png
new file mode 100644
index 00000000000..156b33d1ba6
--- /dev/null
+++ b/app/assets/images/emoji/flag_tc.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_td.png b/app/assets/images/emoji/flag_td.png
new file mode 100644
index 00000000000..ebe7f592828
--- /dev/null
+++ b/app/assets/images/emoji/flag_td.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_tf.png b/app/assets/images/emoji/flag_tf.png
new file mode 100644
index 00000000000..a1a3ad68ee2
--- /dev/null
+++ b/app/assets/images/emoji/flag_tf.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_tg.png b/app/assets/images/emoji/flag_tg.png
new file mode 100644
index 00000000000..826b73c9ac5
--- /dev/null
+++ b/app/assets/images/emoji/flag_tg.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_th.png b/app/assets/images/emoji/flag_th.png
new file mode 100644
index 00000000000..93ff542c5a6
--- /dev/null
+++ b/app/assets/images/emoji/flag_th.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_tj.png b/app/assets/images/emoji/flag_tj.png
new file mode 100644
index 00000000000..7a8a0b6190a
--- /dev/null
+++ b/app/assets/images/emoji/flag_tj.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_tk.png b/app/assets/images/emoji/flag_tk.png
new file mode 100644
index 00000000000..2fa5a21b1bb
--- /dev/null
+++ b/app/assets/images/emoji/flag_tk.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_tl.png b/app/assets/images/emoji/flag_tl.png
new file mode 100644
index 00000000000..5b120eccc6f
--- /dev/null
+++ b/app/assets/images/emoji/flag_tl.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_tm.png b/app/assets/images/emoji/flag_tm.png
new file mode 100644
index 00000000000..c3c4f532302
--- /dev/null
+++ b/app/assets/images/emoji/flag_tm.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_tn.png b/app/assets/images/emoji/flag_tn.png
new file mode 100644
index 00000000000..58ef161229f
--- /dev/null
+++ b/app/assets/images/emoji/flag_tn.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_to.png b/app/assets/images/emoji/flag_to.png
new file mode 100644
index 00000000000..1ffa7bb9d19
--- /dev/null
+++ b/app/assets/images/emoji/flag_to.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_tr.png b/app/assets/images/emoji/flag_tr.png
new file mode 100644
index 00000000000..325251fae88
--- /dev/null
+++ b/app/assets/images/emoji/flag_tr.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_tt.png b/app/assets/images/emoji/flag_tt.png
new file mode 100644
index 00000000000..ed3bb39a300
--- /dev/null
+++ b/app/assets/images/emoji/flag_tt.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_tv.png b/app/assets/images/emoji/flag_tv.png
new file mode 100644
index 00000000000..e82c65c7bb9
--- /dev/null
+++ b/app/assets/images/emoji/flag_tv.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_tw.png b/app/assets/images/emoji/flag_tw.png
new file mode 100644
index 00000000000..3a8f00b5928
--- /dev/null
+++ b/app/assets/images/emoji/flag_tw.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_tz.png b/app/assets/images/emoji/flag_tz.png
new file mode 100644
index 00000000000..2a020853d4e
--- /dev/null
+++ b/app/assets/images/emoji/flag_tz.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ua.png b/app/assets/images/emoji/flag_ua.png
new file mode 100644
index 00000000000..cd84d1bbd36
--- /dev/null
+++ b/app/assets/images/emoji/flag_ua.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ug.png b/app/assets/images/emoji/flag_ug.png
new file mode 100644
index 00000000000..dc97690eb55
--- /dev/null
+++ b/app/assets/images/emoji/flag_ug.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_um.png b/app/assets/images/emoji/flag_um.png
new file mode 100644
index 00000000000..4a7ee3cdf13
--- /dev/null
+++ b/app/assets/images/emoji/flag_um.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_us.png b/app/assets/images/emoji/flag_us.png
new file mode 100644
index 00000000000..9f730305860
--- /dev/null
+++ b/app/assets/images/emoji/flag_us.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_uy.png b/app/assets/images/emoji/flag_uy.png
new file mode 100644
index 00000000000..b8002a697a6
--- /dev/null
+++ b/app/assets/images/emoji/flag_uy.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_uz.png b/app/assets/images/emoji/flag_uz.png
new file mode 100644
index 00000000000..d56ca9bc424
--- /dev/null
+++ b/app/assets/images/emoji/flag_uz.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_va.png b/app/assets/images/emoji/flag_va.png
new file mode 100644
index 00000000000..ddaf5e3141b
--- /dev/null
+++ b/app/assets/images/emoji/flag_va.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_vc.png b/app/assets/images/emoji/flag_vc.png
new file mode 100644
index 00000000000..43703c62a71
--- /dev/null
+++ b/app/assets/images/emoji/flag_vc.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ve.png b/app/assets/images/emoji/flag_ve.png
new file mode 100644
index 00000000000..1b62796824e
--- /dev/null
+++ b/app/assets/images/emoji/flag_ve.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_vg.png b/app/assets/images/emoji/flag_vg.png
new file mode 100644
index 00000000000..536f780f1c0
--- /dev/null
+++ b/app/assets/images/emoji/flag_vg.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_vi.png b/app/assets/images/emoji/flag_vi.png
new file mode 100644
index 00000000000..64102012cfe
--- /dev/null
+++ b/app/assets/images/emoji/flag_vi.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_vn.png b/app/assets/images/emoji/flag_vn.png
new file mode 100644
index 00000000000..427036046b6
--- /dev/null
+++ b/app/assets/images/emoji/flag_vn.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_vu.png b/app/assets/images/emoji/flag_vu.png
new file mode 100644
index 00000000000..706eba44070
--- /dev/null
+++ b/app/assets/images/emoji/flag_vu.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_wf.png b/app/assets/images/emoji/flag_wf.png
new file mode 100644
index 00000000000..70c761036bd
--- /dev/null
+++ b/app/assets/images/emoji/flag_wf.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_white.png b/app/assets/images/emoji/flag_white.png
new file mode 100644
index 00000000000..86d6e96d5e9
--- /dev/null
+++ b/app/assets/images/emoji/flag_white.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ws.png b/app/assets/images/emoji/flag_ws.png
new file mode 100644
index 00000000000..a1ea0703141
--- /dev/null
+++ b/app/assets/images/emoji/flag_ws.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_xk.png b/app/assets/images/emoji/flag_xk.png
new file mode 100644
index 00000000000..e587a446632
--- /dev/null
+++ b/app/assets/images/emoji/flag_xk.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_ye.png b/app/assets/images/emoji/flag_ye.png
new file mode 100644
index 00000000000..eadfebd5f67
--- /dev/null
+++ b/app/assets/images/emoji/flag_ye.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_yt.png b/app/assets/images/emoji/flag_yt.png
new file mode 100644
index 00000000000..c81fa6d886e
--- /dev/null
+++ b/app/assets/images/emoji/flag_yt.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_za.png b/app/assets/images/emoji/flag_za.png
new file mode 100644
index 00000000000..f397ef5072f
--- /dev/null
+++ b/app/assets/images/emoji/flag_za.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_zm.png b/app/assets/images/emoji/flag_zm.png
new file mode 100644
index 00000000000..2494a31f662
--- /dev/null
+++ b/app/assets/images/emoji/flag_zm.png
Binary files differ
diff --git a/app/assets/images/emoji/flag_zw.png b/app/assets/images/emoji/flag_zw.png
new file mode 100644
index 00000000000..e09b9652be6
--- /dev/null
+++ b/app/assets/images/emoji/flag_zw.png
Binary files differ
diff --git a/app/assets/images/emoji/flags.png b/app/assets/images/emoji/flags.png
new file mode 100644
index 00000000000..3b451035a3a
--- /dev/null
+++ b/app/assets/images/emoji/flags.png
Binary files differ
diff --git a/app/assets/images/emoji/flashlight.png b/app/assets/images/emoji/flashlight.png
new file mode 100644
index 00000000000..eee36c25067
--- /dev/null
+++ b/app/assets/images/emoji/flashlight.png
Binary files differ
diff --git a/app/assets/images/emoji/fleur-de-lis.png b/app/assets/images/emoji/fleur-de-lis.png
new file mode 100644
index 00000000000..c9250d27fa7
--- /dev/null
+++ b/app/assets/images/emoji/fleur-de-lis.png
Binary files differ
diff --git a/app/assets/images/emoji/floppy_disk.png b/app/assets/images/emoji/floppy_disk.png
new file mode 100644
index 00000000000..072a76d3c13
--- /dev/null
+++ b/app/assets/images/emoji/floppy_disk.png
Binary files differ
diff --git a/app/assets/images/emoji/flower_playing_cards.png b/app/assets/images/emoji/flower_playing_cards.png
new file mode 100644
index 00000000000..6766b044d95
--- /dev/null
+++ b/app/assets/images/emoji/flower_playing_cards.png
Binary files differ
diff --git a/app/assets/images/emoji/flushed.png b/app/assets/images/emoji/flushed.png
new file mode 100644
index 00000000000..829220bc470
--- /dev/null
+++ b/app/assets/images/emoji/flushed.png
Binary files differ
diff --git a/app/assets/images/emoji/fog.png b/app/assets/images/emoji/fog.png
new file mode 100644
index 00000000000..4e73c2de272
--- /dev/null
+++ b/app/assets/images/emoji/fog.png
Binary files differ
diff --git a/app/assets/images/emoji/foggy.png b/app/assets/images/emoji/foggy.png
new file mode 100644
index 00000000000..57702d8d3ac
--- /dev/null
+++ b/app/assets/images/emoji/foggy.png
Binary files differ
diff --git a/app/assets/images/emoji/football.png b/app/assets/images/emoji/football.png
new file mode 100644
index 00000000000..10366f41fce
--- /dev/null
+++ b/app/assets/images/emoji/football.png
Binary files differ
diff --git a/app/assets/images/emoji/footprints.png b/app/assets/images/emoji/footprints.png
new file mode 100644
index 00000000000..b2673c5a1a8
--- /dev/null
+++ b/app/assets/images/emoji/footprints.png
Binary files differ
diff --git a/app/assets/images/emoji/fork_and_knife.png b/app/assets/images/emoji/fork_and_knife.png
new file mode 100644
index 00000000000..09f1feaea1c
--- /dev/null
+++ b/app/assets/images/emoji/fork_and_knife.png
Binary files differ
diff --git a/app/assets/images/emoji/fork_knife_plate.png b/app/assets/images/emoji/fork_knife_plate.png
new file mode 100644
index 00000000000..7411755f708
--- /dev/null
+++ b/app/assets/images/emoji/fork_knife_plate.png
Binary files differ
diff --git a/app/assets/images/emoji/fountain.png b/app/assets/images/emoji/fountain.png
new file mode 100644
index 00000000000..293f5d91c0f
--- /dev/null
+++ b/app/assets/images/emoji/fountain.png
Binary files differ
diff --git a/app/assets/images/emoji/four.png b/app/assets/images/emoji/four.png
new file mode 100644
index 00000000000..b0e914aac45
--- /dev/null
+++ b/app/assets/images/emoji/four.png
Binary files differ
diff --git a/app/assets/images/emoji/four_leaf_clover.png b/app/assets/images/emoji/four_leaf_clover.png
new file mode 100644
index 00000000000..fdedfcc2b4e
--- /dev/null
+++ b/app/assets/images/emoji/four_leaf_clover.png
Binary files differ
diff --git a/app/assets/images/emoji/fox.png b/app/assets/images/emoji/fox.png
new file mode 100644
index 00000000000..1ab339bf054
--- /dev/null
+++ b/app/assets/images/emoji/fox.png
Binary files differ
diff --git a/app/assets/images/emoji/frame_photo.png b/app/assets/images/emoji/frame_photo.png
new file mode 100644
index 00000000000..9fe84607bfd
--- /dev/null
+++ b/app/assets/images/emoji/frame_photo.png
Binary files differ
diff --git a/app/assets/images/emoji/free.png b/app/assets/images/emoji/free.png
new file mode 100644
index 00000000000..b71956eb48a
--- /dev/null
+++ b/app/assets/images/emoji/free.png
Binary files differ
diff --git a/app/assets/images/emoji/french_bread.png b/app/assets/images/emoji/french_bread.png
new file mode 100644
index 00000000000..4c2c5639822
--- /dev/null
+++ b/app/assets/images/emoji/french_bread.png
Binary files differ
diff --git a/app/assets/images/emoji/fried_shrimp.png b/app/assets/images/emoji/fried_shrimp.png
new file mode 100644
index 00000000000..752ba7f1398
--- /dev/null
+++ b/app/assets/images/emoji/fried_shrimp.png
Binary files differ
diff --git a/app/assets/images/emoji/fries.png b/app/assets/images/emoji/fries.png
new file mode 100644
index 00000000000..4e2a4caacef
--- /dev/null
+++ b/app/assets/images/emoji/fries.png
Binary files differ
diff --git a/app/assets/images/emoji/frog.png b/app/assets/images/emoji/frog.png
new file mode 100644
index 00000000000..8825d1ad577
--- /dev/null
+++ b/app/assets/images/emoji/frog.png
Binary files differ
diff --git a/app/assets/images/emoji/frowning.png b/app/assets/images/emoji/frowning.png
new file mode 100644
index 00000000000..43ab6b0a1c1
--- /dev/null
+++ b/app/assets/images/emoji/frowning.png
Binary files differ
diff --git a/app/assets/images/emoji/frowning2.png b/app/assets/images/emoji/frowning2.png
new file mode 100644
index 00000000000..6ae71f233b9
--- /dev/null
+++ b/app/assets/images/emoji/frowning2.png
Binary files differ
diff --git a/app/assets/images/emoji/fuelpump.png b/app/assets/images/emoji/fuelpump.png
new file mode 100644
index 00000000000..05b18794474
--- /dev/null
+++ b/app/assets/images/emoji/fuelpump.png
Binary files differ
diff --git a/app/assets/images/emoji/full_moon.png b/app/assets/images/emoji/full_moon.png
new file mode 100644
index 00000000000..c9a2d6aa7c9
--- /dev/null
+++ b/app/assets/images/emoji/full_moon.png
Binary files differ
diff --git a/app/assets/images/emoji/full_moon_with_face.png b/app/assets/images/emoji/full_moon_with_face.png
new file mode 100644
index 00000000000..a5c25bbaf64
--- /dev/null
+++ b/app/assets/images/emoji/full_moon_with_face.png
Binary files differ
diff --git a/app/assets/images/emoji/game_die.png b/app/assets/images/emoji/game_die.png
new file mode 100644
index 00000000000..ad3626fe5e5
--- /dev/null
+++ b/app/assets/images/emoji/game_die.png
Binary files differ
diff --git a/app/assets/images/emoji/gear.png b/app/assets/images/emoji/gear.png
new file mode 100644
index 00000000000..2a1cc2c0ff4
--- /dev/null
+++ b/app/assets/images/emoji/gear.png
Binary files differ
diff --git a/app/assets/images/emoji/gem.png b/app/assets/images/emoji/gem.png
new file mode 100644
index 00000000000..db122d26a19
--- /dev/null
+++ b/app/assets/images/emoji/gem.png
Binary files differ
diff --git a/app/assets/images/emoji/gemini.png b/app/assets/images/emoji/gemini.png
new file mode 100644
index 00000000000..1a09698cf00
--- /dev/null
+++ b/app/assets/images/emoji/gemini.png
Binary files differ
diff --git a/app/assets/images/emoji/ghost.png b/app/assets/images/emoji/ghost.png
new file mode 100644
index 00000000000..5650bc0ed18
--- /dev/null
+++ b/app/assets/images/emoji/ghost.png
Binary files differ
diff --git a/app/assets/images/emoji/gift.png b/app/assets/images/emoji/gift.png
new file mode 100644
index 00000000000..844e2164560
--- /dev/null
+++ b/app/assets/images/emoji/gift.png
Binary files differ
diff --git a/app/assets/images/emoji/gift_heart.png b/app/assets/images/emoji/gift_heart.png
new file mode 100644
index 00000000000..902ceafe4d1
--- /dev/null
+++ b/app/assets/images/emoji/gift_heart.png
Binary files differ
diff --git a/app/assets/images/emoji/girl.png b/app/assets/images/emoji/girl.png
new file mode 100644
index 00000000000..dc1d4d08b39
--- /dev/null
+++ b/app/assets/images/emoji/girl.png
Binary files differ
diff --git a/app/assets/images/emoji/girl_tone1.png b/app/assets/images/emoji/girl_tone1.png
new file mode 100644
index 00000000000..bb667e88651
--- /dev/null
+++ b/app/assets/images/emoji/girl_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/girl_tone2.png b/app/assets/images/emoji/girl_tone2.png
new file mode 100644
index 00000000000..a59ed4a3f0d
--- /dev/null
+++ b/app/assets/images/emoji/girl_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/girl_tone3.png b/app/assets/images/emoji/girl_tone3.png
new file mode 100644
index 00000000000..517e7f2a7b0
--- /dev/null
+++ b/app/assets/images/emoji/girl_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/girl_tone4.png b/app/assets/images/emoji/girl_tone4.png
new file mode 100644
index 00000000000..542d96c8487
--- /dev/null
+++ b/app/assets/images/emoji/girl_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/girl_tone5.png b/app/assets/images/emoji/girl_tone5.png
new file mode 100644
index 00000000000..66b7c28c2df
--- /dev/null
+++ b/app/assets/images/emoji/girl_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/globe_with_meridians.png b/app/assets/images/emoji/globe_with_meridians.png
new file mode 100644
index 00000000000..82450c1a4ba
--- /dev/null
+++ b/app/assets/images/emoji/globe_with_meridians.png
Binary files differ
diff --git a/app/assets/images/emoji/goal.png b/app/assets/images/emoji/goal.png
new file mode 100644
index 00000000000..df3a53da0fb
--- /dev/null
+++ b/app/assets/images/emoji/goal.png
Binary files differ
diff --git a/app/assets/images/emoji/goat.png b/app/assets/images/emoji/goat.png
new file mode 100644
index 00000000000..f9d9e38a128
--- /dev/null
+++ b/app/assets/images/emoji/goat.png
Binary files differ
diff --git a/app/assets/images/emoji/golf.png b/app/assets/images/emoji/golf.png
new file mode 100644
index 00000000000..f65a21d8a46
--- /dev/null
+++ b/app/assets/images/emoji/golf.png
Binary files differ
diff --git a/app/assets/images/emoji/golfer.png b/app/assets/images/emoji/golfer.png
new file mode 100644
index 00000000000..39c552de86d
--- /dev/null
+++ b/app/assets/images/emoji/golfer.png
Binary files differ
diff --git a/app/assets/images/emoji/gorilla.png b/app/assets/images/emoji/gorilla.png
new file mode 100644
index 00000000000..acc51e13622
--- /dev/null
+++ b/app/assets/images/emoji/gorilla.png
Binary files differ
diff --git a/app/assets/images/emoji/grapes.png b/app/assets/images/emoji/grapes.png
new file mode 100644
index 00000000000..30d22218896
--- /dev/null
+++ b/app/assets/images/emoji/grapes.png
Binary files differ
diff --git a/app/assets/images/emoji/green_apple.png b/app/assets/images/emoji/green_apple.png
new file mode 100644
index 00000000000..5fd51bd3915
--- /dev/null
+++ b/app/assets/images/emoji/green_apple.png
Binary files differ
diff --git a/app/assets/images/emoji/green_book.png b/app/assets/images/emoji/green_book.png
new file mode 100644
index 00000000000..e5e411cf3b5
--- /dev/null
+++ b/app/assets/images/emoji/green_book.png
Binary files differ
diff --git a/app/assets/images/emoji/green_heart.png b/app/assets/images/emoji/green_heart.png
new file mode 100644
index 00000000000..c52d60a58be
--- /dev/null
+++ b/app/assets/images/emoji/green_heart.png
Binary files differ
diff --git a/app/assets/images/emoji/grey_exclamation.png b/app/assets/images/emoji/grey_exclamation.png
new file mode 100644
index 00000000000..9b64da8bf7f
--- /dev/null
+++ b/app/assets/images/emoji/grey_exclamation.png
Binary files differ
diff --git a/app/assets/images/emoji/grey_question.png b/app/assets/images/emoji/grey_question.png
new file mode 100644
index 00000000000..6e7824c75f6
--- /dev/null
+++ b/app/assets/images/emoji/grey_question.png
Binary files differ
diff --git a/app/assets/images/emoji/grimacing.png b/app/assets/images/emoji/grimacing.png
new file mode 100644
index 00000000000..871b2f071c9
--- /dev/null
+++ b/app/assets/images/emoji/grimacing.png
Binary files differ
diff --git a/app/assets/images/emoji/grin.png b/app/assets/images/emoji/grin.png
new file mode 100644
index 00000000000..418d94c811b
--- /dev/null
+++ b/app/assets/images/emoji/grin.png
Binary files differ
diff --git a/app/assets/images/emoji/grinning.png b/app/assets/images/emoji/grinning.png
new file mode 100644
index 00000000000..3e8e0dab78c
--- /dev/null
+++ b/app/assets/images/emoji/grinning.png
Binary files differ
diff --git a/app/assets/images/emoji/guardsman.png b/app/assets/images/emoji/guardsman.png
new file mode 100644
index 00000000000..8d7ab3c473c
--- /dev/null
+++ b/app/assets/images/emoji/guardsman.png
Binary files differ
diff --git a/app/assets/images/emoji/guardsman_tone1.png b/app/assets/images/emoji/guardsman_tone1.png
new file mode 100644
index 00000000000..cea9ba27468
--- /dev/null
+++ b/app/assets/images/emoji/guardsman_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/guardsman_tone2.png b/app/assets/images/emoji/guardsman_tone2.png
new file mode 100644
index 00000000000..037464e4028
--- /dev/null
+++ b/app/assets/images/emoji/guardsman_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/guardsman_tone3.png b/app/assets/images/emoji/guardsman_tone3.png
new file mode 100644
index 00000000000..0f6726fbe87
--- /dev/null
+++ b/app/assets/images/emoji/guardsman_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/guardsman_tone4.png b/app/assets/images/emoji/guardsman_tone4.png
new file mode 100644
index 00000000000..85fcf9a3b97
--- /dev/null
+++ b/app/assets/images/emoji/guardsman_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/guardsman_tone5.png b/app/assets/images/emoji/guardsman_tone5.png
new file mode 100644
index 00000000000..e5f9ca7d5a2
--- /dev/null
+++ b/app/assets/images/emoji/guardsman_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/guitar.png b/app/assets/images/emoji/guitar.png
new file mode 100644
index 00000000000..43d752f1e3d
--- /dev/null
+++ b/app/assets/images/emoji/guitar.png
Binary files differ
diff --git a/app/assets/images/emoji/gun.png b/app/assets/images/emoji/gun.png
new file mode 100644
index 00000000000..89c5c244c7b
--- /dev/null
+++ b/app/assets/images/emoji/gun.png
Binary files differ
diff --git a/app/assets/images/emoji/haircut.png b/app/assets/images/emoji/haircut.png
new file mode 100644
index 00000000000..91266b12930
--- /dev/null
+++ b/app/assets/images/emoji/haircut.png
Binary files differ
diff --git a/app/assets/images/emoji/haircut_tone1.png b/app/assets/images/emoji/haircut_tone1.png
new file mode 100644
index 00000000000..c743b74abeb
--- /dev/null
+++ b/app/assets/images/emoji/haircut_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/haircut_tone2.png b/app/assets/images/emoji/haircut_tone2.png
new file mode 100644
index 00000000000..f144f8e55ce
--- /dev/null
+++ b/app/assets/images/emoji/haircut_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/haircut_tone3.png b/app/assets/images/emoji/haircut_tone3.png
new file mode 100644
index 00000000000..d5ad19563ac
--- /dev/null
+++ b/app/assets/images/emoji/haircut_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/haircut_tone4.png b/app/assets/images/emoji/haircut_tone4.png
new file mode 100644
index 00000000000..244fd3af008
--- /dev/null
+++ b/app/assets/images/emoji/haircut_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/haircut_tone5.png b/app/assets/images/emoji/haircut_tone5.png
new file mode 100644
index 00000000000..20a94a88623
--- /dev/null
+++ b/app/assets/images/emoji/haircut_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/hamburger.png b/app/assets/images/emoji/hamburger.png
new file mode 100644
index 00000000000..3573b28a1fd
--- /dev/null
+++ b/app/assets/images/emoji/hamburger.png
Binary files differ
diff --git a/app/assets/images/emoji/hammer.png b/app/assets/images/emoji/hammer.png
new file mode 100644
index 00000000000..00736cce47d
--- /dev/null
+++ b/app/assets/images/emoji/hammer.png
Binary files differ
diff --git a/app/assets/images/emoji/hammer_pick.png b/app/assets/images/emoji/hammer_pick.png
new file mode 100644
index 00000000000..3bee30ec588
--- /dev/null
+++ b/app/assets/images/emoji/hammer_pick.png
Binary files differ
diff --git a/app/assets/images/emoji/hamster.png b/app/assets/images/emoji/hamster.png
new file mode 100644
index 00000000000..9a04388e4e7
--- /dev/null
+++ b/app/assets/images/emoji/hamster.png
Binary files differ
diff --git a/app/assets/images/emoji/hand_splayed.png b/app/assets/images/emoji/hand_splayed.png
new file mode 100644
index 00000000000..fb5ae8ebb5a
--- /dev/null
+++ b/app/assets/images/emoji/hand_splayed.png
Binary files differ
diff --git a/app/assets/images/emoji/hand_splayed_tone1.png b/app/assets/images/emoji/hand_splayed_tone1.png
new file mode 100644
index 00000000000..a7888e6bd23
--- /dev/null
+++ b/app/assets/images/emoji/hand_splayed_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/hand_splayed_tone2.png b/app/assets/images/emoji/hand_splayed_tone2.png
new file mode 100644
index 00000000000..cc10fbc272d
--- /dev/null
+++ b/app/assets/images/emoji/hand_splayed_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/hand_splayed_tone3.png b/app/assets/images/emoji/hand_splayed_tone3.png
new file mode 100644
index 00000000000..707236ae8a4
--- /dev/null
+++ b/app/assets/images/emoji/hand_splayed_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/hand_splayed_tone4.png b/app/assets/images/emoji/hand_splayed_tone4.png
new file mode 100644
index 00000000000..1430df9c61f
--- /dev/null
+++ b/app/assets/images/emoji/hand_splayed_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/hand_splayed_tone5.png b/app/assets/images/emoji/hand_splayed_tone5.png
new file mode 100644
index 00000000000..80bec971b6b
--- /dev/null
+++ b/app/assets/images/emoji/hand_splayed_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/handbag.png b/app/assets/images/emoji/handbag.png
new file mode 100644
index 00000000000..cbf75c5d25e
--- /dev/null
+++ b/app/assets/images/emoji/handbag.png
Binary files differ
diff --git a/app/assets/images/emoji/handball.png b/app/assets/images/emoji/handball.png
new file mode 100644
index 00000000000..1152f1344c7
--- /dev/null
+++ b/app/assets/images/emoji/handball.png
Binary files differ
diff --git a/app/assets/images/emoji/handball_tone1.png b/app/assets/images/emoji/handball_tone1.png
new file mode 100644
index 00000000000..c26cac2df98
--- /dev/null
+++ b/app/assets/images/emoji/handball_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/handball_tone2.png b/app/assets/images/emoji/handball_tone2.png
new file mode 100644
index 00000000000..7baaf95a9a2
--- /dev/null
+++ b/app/assets/images/emoji/handball_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/handball_tone3.png b/app/assets/images/emoji/handball_tone3.png
new file mode 100644
index 00000000000..0e3a37c3d40
--- /dev/null
+++ b/app/assets/images/emoji/handball_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/handball_tone4.png b/app/assets/images/emoji/handball_tone4.png
new file mode 100644
index 00000000000..e1233f38266
--- /dev/null
+++ b/app/assets/images/emoji/handball_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/handball_tone5.png b/app/assets/images/emoji/handball_tone5.png
new file mode 100644
index 00000000000..6b1eb9b64b0
--- /dev/null
+++ b/app/assets/images/emoji/handball_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/handshake.png b/app/assets/images/emoji/handshake.png
new file mode 100644
index 00000000000..c5d35fd8138
--- /dev/null
+++ b/app/assets/images/emoji/handshake.png
Binary files differ
diff --git a/app/assets/images/emoji/handshake_tone1.png b/app/assets/images/emoji/handshake_tone1.png
new file mode 100644
index 00000000000..8f8fbb9bdca
--- /dev/null
+++ b/app/assets/images/emoji/handshake_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/handshake_tone2.png b/app/assets/images/emoji/handshake_tone2.png
new file mode 100644
index 00000000000..336a77a6d78
--- /dev/null
+++ b/app/assets/images/emoji/handshake_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/handshake_tone3.png b/app/assets/images/emoji/handshake_tone3.png
new file mode 100644
index 00000000000..95f62d4fecd
--- /dev/null
+++ b/app/assets/images/emoji/handshake_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/handshake_tone4.png b/app/assets/images/emoji/handshake_tone4.png
new file mode 100644
index 00000000000..2b0a6433886
--- /dev/null
+++ b/app/assets/images/emoji/handshake_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/handshake_tone5.png b/app/assets/images/emoji/handshake_tone5.png
new file mode 100644
index 00000000000..40189ee68e4
--- /dev/null
+++ b/app/assets/images/emoji/handshake_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/hash.png b/app/assets/images/emoji/hash.png
new file mode 100644
index 00000000000..6e26f0070b0
--- /dev/null
+++ b/app/assets/images/emoji/hash.png
Binary files differ
diff --git a/app/assets/images/emoji/hatched_chick.png b/app/assets/images/emoji/hatched_chick.png
new file mode 100644
index 00000000000..31dfb511e0e
--- /dev/null
+++ b/app/assets/images/emoji/hatched_chick.png
Binary files differ
diff --git a/app/assets/images/emoji/hatching_chick.png b/app/assets/images/emoji/hatching_chick.png
new file mode 100644
index 00000000000..c5b0e8f3bcc
--- /dev/null
+++ b/app/assets/images/emoji/hatching_chick.png
Binary files differ
diff --git a/app/assets/images/emoji/head_bandage.png b/app/assets/images/emoji/head_bandage.png
new file mode 100644
index 00000000000..0be723085e0
--- /dev/null
+++ b/app/assets/images/emoji/head_bandage.png
Binary files differ
diff --git a/app/assets/images/emoji/headphones.png b/app/assets/images/emoji/headphones.png
new file mode 100644
index 00000000000..e9fd34041d8
--- /dev/null
+++ b/app/assets/images/emoji/headphones.png
Binary files differ
diff --git a/app/assets/images/emoji/hear_no_evil.png b/app/assets/images/emoji/hear_no_evil.png
new file mode 100644
index 00000000000..74b6be0c6c5
--- /dev/null
+++ b/app/assets/images/emoji/hear_no_evil.png
Binary files differ
diff --git a/app/assets/images/emoji/heart.png b/app/assets/images/emoji/heart.png
new file mode 100644
index 00000000000..638cb72dc4e
--- /dev/null
+++ b/app/assets/images/emoji/heart.png
Binary files differ
diff --git a/app/assets/images/emoji/heart_decoration.png b/app/assets/images/emoji/heart_decoration.png
new file mode 100644
index 00000000000..5443f60bc63
--- /dev/null
+++ b/app/assets/images/emoji/heart_decoration.png
Binary files differ
diff --git a/app/assets/images/emoji/heart_exclamation.png b/app/assets/images/emoji/heart_exclamation.png
new file mode 100644
index 00000000000..91b520be40b
--- /dev/null
+++ b/app/assets/images/emoji/heart_exclamation.png
Binary files differ
diff --git a/app/assets/images/emoji/heart_eyes.png b/app/assets/images/emoji/heart_eyes.png
new file mode 100644
index 00000000000..73fbee29d4e
--- /dev/null
+++ b/app/assets/images/emoji/heart_eyes.png
Binary files differ
diff --git a/app/assets/images/emoji/heart_eyes_cat.png b/app/assets/images/emoji/heart_eyes_cat.png
new file mode 100644
index 00000000000..bc5a833f9a1
--- /dev/null
+++ b/app/assets/images/emoji/heart_eyes_cat.png
Binary files differ
diff --git a/app/assets/images/emoji/heartbeat.png b/app/assets/images/emoji/heartbeat.png
new file mode 100644
index 00000000000..0bcf2d1d567
--- /dev/null
+++ b/app/assets/images/emoji/heartbeat.png
Binary files differ
diff --git a/app/assets/images/emoji/heartpulse.png b/app/assets/images/emoji/heartpulse.png
new file mode 100644
index 00000000000..d6e694e972f
--- /dev/null
+++ b/app/assets/images/emoji/heartpulse.png
Binary files differ
diff --git a/app/assets/images/emoji/hearts.png b/app/assets/images/emoji/hearts.png
new file mode 100644
index 00000000000..393c3ed5267
--- /dev/null
+++ b/app/assets/images/emoji/hearts.png
Binary files differ
diff --git a/app/assets/images/emoji/heavy_check_mark.png b/app/assets/images/emoji/heavy_check_mark.png
new file mode 100644
index 00000000000..03bd695377e
--- /dev/null
+++ b/app/assets/images/emoji/heavy_check_mark.png
Binary files differ
diff --git a/app/assets/images/emoji/heavy_division_sign.png b/app/assets/images/emoji/heavy_division_sign.png
new file mode 100644
index 00000000000..df32ab21bea
--- /dev/null
+++ b/app/assets/images/emoji/heavy_division_sign.png
Binary files differ
diff --git a/app/assets/images/emoji/heavy_dollar_sign.png b/app/assets/images/emoji/heavy_dollar_sign.png
new file mode 100644
index 00000000000..ef2c2e20590
--- /dev/null
+++ b/app/assets/images/emoji/heavy_dollar_sign.png
Binary files differ
diff --git a/app/assets/images/emoji/heavy_minus_sign.png b/app/assets/images/emoji/heavy_minus_sign.png
new file mode 100644
index 00000000000..054211caf12
--- /dev/null
+++ b/app/assets/images/emoji/heavy_minus_sign.png
Binary files differ
diff --git a/app/assets/images/emoji/heavy_multiplication_x.png b/app/assets/images/emoji/heavy_multiplication_x.png
new file mode 100644
index 00000000000..e47cc1b685d
--- /dev/null
+++ b/app/assets/images/emoji/heavy_multiplication_x.png
Binary files differ
diff --git a/app/assets/images/emoji/heavy_plus_sign.png b/app/assets/images/emoji/heavy_plus_sign.png
new file mode 100644
index 00000000000..40799798aaf
--- /dev/null
+++ b/app/assets/images/emoji/heavy_plus_sign.png
Binary files differ
diff --git a/app/assets/images/emoji/helicopter.png b/app/assets/images/emoji/helicopter.png
new file mode 100644
index 00000000000..7ec5f39a51a
--- /dev/null
+++ b/app/assets/images/emoji/helicopter.png
Binary files differ
diff --git a/app/assets/images/emoji/helmet_with_cross.png b/app/assets/images/emoji/helmet_with_cross.png
new file mode 100644
index 00000000000..7140a676038
--- /dev/null
+++ b/app/assets/images/emoji/helmet_with_cross.png
Binary files differ
diff --git a/app/assets/images/emoji/herb.png b/app/assets/images/emoji/herb.png
new file mode 100644
index 00000000000..d984d1562bb
--- /dev/null
+++ b/app/assets/images/emoji/herb.png
Binary files differ
diff --git a/app/assets/images/emoji/hibiscus.png b/app/assets/images/emoji/hibiscus.png
new file mode 100644
index 00000000000..39dd3524233
--- /dev/null
+++ b/app/assets/images/emoji/hibiscus.png
Binary files differ
diff --git a/app/assets/images/emoji/high_brightness.png b/app/assets/images/emoji/high_brightness.png
new file mode 100644
index 00000000000..c41f2d5fd50
--- /dev/null
+++ b/app/assets/images/emoji/high_brightness.png
Binary files differ
diff --git a/app/assets/images/emoji/high_heel.png b/app/assets/images/emoji/high_heel.png
new file mode 100644
index 00000000000..b331cbccc9d
--- /dev/null
+++ b/app/assets/images/emoji/high_heel.png
Binary files differ
diff --git a/app/assets/images/emoji/hockey.png b/app/assets/images/emoji/hockey.png
new file mode 100644
index 00000000000..be94e9cbf73
--- /dev/null
+++ b/app/assets/images/emoji/hockey.png
Binary files differ
diff --git a/app/assets/images/emoji/hole.png b/app/assets/images/emoji/hole.png
new file mode 100644
index 00000000000..517d2ae0deb
--- /dev/null
+++ b/app/assets/images/emoji/hole.png
Binary files differ
diff --git a/app/assets/images/emoji/homes.png b/app/assets/images/emoji/homes.png
new file mode 100644
index 00000000000..6ab4a2a2651
--- /dev/null
+++ b/app/assets/images/emoji/homes.png
Binary files differ
diff --git a/app/assets/images/emoji/honey_pot.png b/app/assets/images/emoji/honey_pot.png
new file mode 100644
index 00000000000..9d8f592955e
--- /dev/null
+++ b/app/assets/images/emoji/honey_pot.png
Binary files differ
diff --git a/app/assets/images/emoji/horse.png b/app/assets/images/emoji/horse.png
new file mode 100644
index 00000000000..7cb1172f4e4
--- /dev/null
+++ b/app/assets/images/emoji/horse.png
Binary files differ
diff --git a/app/assets/images/emoji/horse_racing.png b/app/assets/images/emoji/horse_racing.png
new file mode 100644
index 00000000000..addf9edac56
--- /dev/null
+++ b/app/assets/images/emoji/horse_racing.png
Binary files differ
diff --git a/app/assets/images/emoji/horse_racing_tone1.png b/app/assets/images/emoji/horse_racing_tone1.png
new file mode 100644
index 00000000000..e9bf4092e98
--- /dev/null
+++ b/app/assets/images/emoji/horse_racing_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/horse_racing_tone2.png b/app/assets/images/emoji/horse_racing_tone2.png
new file mode 100644
index 00000000000..031bbc3d867
--- /dev/null
+++ b/app/assets/images/emoji/horse_racing_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/horse_racing_tone3.png b/app/assets/images/emoji/horse_racing_tone3.png
new file mode 100644
index 00000000000..b40ef891f9b
--- /dev/null
+++ b/app/assets/images/emoji/horse_racing_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/horse_racing_tone4.png b/app/assets/images/emoji/horse_racing_tone4.png
new file mode 100644
index 00000000000..e286cb85065
--- /dev/null
+++ b/app/assets/images/emoji/horse_racing_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/horse_racing_tone5.png b/app/assets/images/emoji/horse_racing_tone5.png
new file mode 100644
index 00000000000..453c51c6007
--- /dev/null
+++ b/app/assets/images/emoji/horse_racing_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/hospital.png b/app/assets/images/emoji/hospital.png
new file mode 100644
index 00000000000..1cbce4ae767
--- /dev/null
+++ b/app/assets/images/emoji/hospital.png
Binary files differ
diff --git a/app/assets/images/emoji/hot_pepper.png b/app/assets/images/emoji/hot_pepper.png
new file mode 100644
index 00000000000..266675bd577
--- /dev/null
+++ b/app/assets/images/emoji/hot_pepper.png
Binary files differ
diff --git a/app/assets/images/emoji/hotdog.png b/app/assets/images/emoji/hotdog.png
new file mode 100644
index 00000000000..3c3354d94cb
--- /dev/null
+++ b/app/assets/images/emoji/hotdog.png
Binary files differ
diff --git a/app/assets/images/emoji/hotel.png b/app/assets/images/emoji/hotel.png
new file mode 100644
index 00000000000..ea8f4c4979a
--- /dev/null
+++ b/app/assets/images/emoji/hotel.png
Binary files differ
diff --git a/app/assets/images/emoji/hotsprings.png b/app/assets/images/emoji/hotsprings.png
new file mode 100644
index 00000000000..3d9df2d9475
--- /dev/null
+++ b/app/assets/images/emoji/hotsprings.png
Binary files differ
diff --git a/app/assets/images/emoji/hourglass.png b/app/assets/images/emoji/hourglass.png
new file mode 100644
index 00000000000..a5db2d1d3f4
--- /dev/null
+++ b/app/assets/images/emoji/hourglass.png
Binary files differ
diff --git a/app/assets/images/emoji/hourglass_flowing_sand.png b/app/assets/images/emoji/hourglass_flowing_sand.png
new file mode 100644
index 00000000000..b93b15ed6d8
--- /dev/null
+++ b/app/assets/images/emoji/hourglass_flowing_sand.png
Binary files differ
diff --git a/app/assets/images/emoji/house.png b/app/assets/images/emoji/house.png
new file mode 100644
index 00000000000..01c98a0ba92
--- /dev/null
+++ b/app/assets/images/emoji/house.png
Binary files differ
diff --git a/app/assets/images/emoji/house_abandoned.png b/app/assets/images/emoji/house_abandoned.png
new file mode 100644
index 00000000000..c55e81de990
--- /dev/null
+++ b/app/assets/images/emoji/house_abandoned.png
Binary files differ
diff --git a/app/assets/images/emoji/house_with_garden.png b/app/assets/images/emoji/house_with_garden.png
new file mode 100644
index 00000000000..0aae41598ef
--- /dev/null
+++ b/app/assets/images/emoji/house_with_garden.png
Binary files differ
diff --git a/app/assets/images/emoji/hugging.png b/app/assets/images/emoji/hugging.png
new file mode 100644
index 00000000000..5bba6dc6d51
--- /dev/null
+++ b/app/assets/images/emoji/hugging.png
Binary files differ
diff --git a/app/assets/images/emoji/hushed.png b/app/assets/images/emoji/hushed.png
new file mode 100644
index 00000000000..cad0e23132e
--- /dev/null
+++ b/app/assets/images/emoji/hushed.png
Binary files differ
diff --git a/app/assets/images/emoji/ice_cream.png b/app/assets/images/emoji/ice_cream.png
new file mode 100644
index 00000000000..94267b9c434
--- /dev/null
+++ b/app/assets/images/emoji/ice_cream.png
Binary files differ
diff --git a/app/assets/images/emoji/ice_skate.png b/app/assets/images/emoji/ice_skate.png
new file mode 100644
index 00000000000..8c449b0c039
--- /dev/null
+++ b/app/assets/images/emoji/ice_skate.png
Binary files differ
diff --git a/app/assets/images/emoji/icecream.png b/app/assets/images/emoji/icecream.png
new file mode 100644
index 00000000000..8f6546e31a5
--- /dev/null
+++ b/app/assets/images/emoji/icecream.png
Binary files differ
diff --git a/app/assets/images/emoji/id.png b/app/assets/images/emoji/id.png
new file mode 100644
index 00000000000..5bf69bf7ba8
--- /dev/null
+++ b/app/assets/images/emoji/id.png
Binary files differ
diff --git a/app/assets/images/emoji/ideograph_advantage.png b/app/assets/images/emoji/ideograph_advantage.png
new file mode 100644
index 00000000000..0c0d589caf0
--- /dev/null
+++ b/app/assets/images/emoji/ideograph_advantage.png
Binary files differ
diff --git a/app/assets/images/emoji/imp.png b/app/assets/images/emoji/imp.png
new file mode 100644
index 00000000000..9f9a9605539
--- /dev/null
+++ b/app/assets/images/emoji/imp.png
Binary files differ
diff --git a/app/assets/images/emoji/inbox_tray.png b/app/assets/images/emoji/inbox_tray.png
new file mode 100644
index 00000000000..41a6be2b0ee
--- /dev/null
+++ b/app/assets/images/emoji/inbox_tray.png
Binary files differ
diff --git a/app/assets/images/emoji/incoming_envelope.png b/app/assets/images/emoji/incoming_envelope.png
new file mode 100644
index 00000000000..fd22e88182e
--- /dev/null
+++ b/app/assets/images/emoji/incoming_envelope.png
Binary files differ
diff --git a/app/assets/images/emoji/information_desk_person.png b/app/assets/images/emoji/information_desk_person.png
new file mode 100644
index 00000000000..55fc6294d25
--- /dev/null
+++ b/app/assets/images/emoji/information_desk_person.png
Binary files differ
diff --git a/app/assets/images/emoji/information_desk_person_tone1.png b/app/assets/images/emoji/information_desk_person_tone1.png
new file mode 100644
index 00000000000..3d9e2247940
--- /dev/null
+++ b/app/assets/images/emoji/information_desk_person_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/information_desk_person_tone2.png b/app/assets/images/emoji/information_desk_person_tone2.png
new file mode 100644
index 00000000000..879e8b7966d
--- /dev/null
+++ b/app/assets/images/emoji/information_desk_person_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/information_desk_person_tone3.png b/app/assets/images/emoji/information_desk_person_tone3.png
new file mode 100644
index 00000000000..307514eab67
--- /dev/null
+++ b/app/assets/images/emoji/information_desk_person_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/information_desk_person_tone4.png b/app/assets/images/emoji/information_desk_person_tone4.png
new file mode 100644
index 00000000000..297395dcb3f
--- /dev/null
+++ b/app/assets/images/emoji/information_desk_person_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/information_desk_person_tone5.png b/app/assets/images/emoji/information_desk_person_tone5.png
new file mode 100644
index 00000000000..26f8f22b28b
--- /dev/null
+++ b/app/assets/images/emoji/information_desk_person_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/information_source.png b/app/assets/images/emoji/information_source.png
new file mode 100644
index 00000000000..871f2db9314
--- /dev/null
+++ b/app/assets/images/emoji/information_source.png
Binary files differ
diff --git a/app/assets/images/emoji/innocent.png b/app/assets/images/emoji/innocent.png
new file mode 100644
index 00000000000..57f5151124f
--- /dev/null
+++ b/app/assets/images/emoji/innocent.png
Binary files differ
diff --git a/app/assets/images/emoji/interrobang.png b/app/assets/images/emoji/interrobang.png
new file mode 100644
index 00000000000..509813e9bb2
--- /dev/null
+++ b/app/assets/images/emoji/interrobang.png
Binary files differ
diff --git a/app/assets/images/emoji/iphone.png b/app/assets/images/emoji/iphone.png
new file mode 100644
index 00000000000..fd377acf872
--- /dev/null
+++ b/app/assets/images/emoji/iphone.png
Binary files differ
diff --git a/app/assets/images/emoji/island.png b/app/assets/images/emoji/island.png
new file mode 100644
index 00000000000..7fd834389b7
--- /dev/null
+++ b/app/assets/images/emoji/island.png
Binary files differ
diff --git a/app/assets/images/emoji/izakaya_lantern.png b/app/assets/images/emoji/izakaya_lantern.png
new file mode 100644
index 00000000000..dfd933f6f36
--- /dev/null
+++ b/app/assets/images/emoji/izakaya_lantern.png
Binary files differ
diff --git a/app/assets/images/emoji/jack_o_lantern.png b/app/assets/images/emoji/jack_o_lantern.png
new file mode 100644
index 00000000000..44c3fc0aec9
--- /dev/null
+++ b/app/assets/images/emoji/jack_o_lantern.png
Binary files differ
diff --git a/app/assets/images/emoji/japan.png b/app/assets/images/emoji/japan.png
new file mode 100644
index 00000000000..d86d0a59e12
--- /dev/null
+++ b/app/assets/images/emoji/japan.png
Binary files differ
diff --git a/app/assets/images/emoji/japanese_castle.png b/app/assets/images/emoji/japanese_castle.png
new file mode 100644
index 00000000000..64b4e33a1ae
--- /dev/null
+++ b/app/assets/images/emoji/japanese_castle.png
Binary files differ
diff --git a/app/assets/images/emoji/japanese_goblin.png b/app/assets/images/emoji/japanese_goblin.png
new file mode 100644
index 00000000000..515c6a2250e
--- /dev/null
+++ b/app/assets/images/emoji/japanese_goblin.png
Binary files differ
diff --git a/app/assets/images/emoji/japanese_ogre.png b/app/assets/images/emoji/japanese_ogre.png
new file mode 100644
index 00000000000..fe8670fdaf1
--- /dev/null
+++ b/app/assets/images/emoji/japanese_ogre.png
Binary files differ
diff --git a/app/assets/images/emoji/jeans.png b/app/assets/images/emoji/jeans.png
new file mode 100644
index 00000000000..2a6869d674c
--- /dev/null
+++ b/app/assets/images/emoji/jeans.png
Binary files differ
diff --git a/app/assets/images/emoji/joy.png b/app/assets/images/emoji/joy.png
new file mode 100644
index 00000000000..0ba3b1859d8
--- /dev/null
+++ b/app/assets/images/emoji/joy.png
Binary files differ
diff --git a/app/assets/images/emoji/joy_cat.png b/app/assets/images/emoji/joy_cat.png
new file mode 100644
index 00000000000..aac353179aa
--- /dev/null
+++ b/app/assets/images/emoji/joy_cat.png
Binary files differ
diff --git a/app/assets/images/emoji/joystick.png b/app/assets/images/emoji/joystick.png
new file mode 100644
index 00000000000..1ee1905434e
--- /dev/null
+++ b/app/assets/images/emoji/joystick.png
Binary files differ
diff --git a/app/assets/images/emoji/juggling.png b/app/assets/images/emoji/juggling.png
new file mode 100644
index 00000000000..a37f6224a42
--- /dev/null
+++ b/app/assets/images/emoji/juggling.png
Binary files differ
diff --git a/app/assets/images/emoji/juggling_tone1.png b/app/assets/images/emoji/juggling_tone1.png
new file mode 100644
index 00000000000..c18eda40031
--- /dev/null
+++ b/app/assets/images/emoji/juggling_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/juggling_tone2.png b/app/assets/images/emoji/juggling_tone2.png
new file mode 100644
index 00000000000..de3b7a555b6
--- /dev/null
+++ b/app/assets/images/emoji/juggling_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/juggling_tone3.png b/app/assets/images/emoji/juggling_tone3.png
new file mode 100644
index 00000000000..74ab6d85458
--- /dev/null
+++ b/app/assets/images/emoji/juggling_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/juggling_tone4.png b/app/assets/images/emoji/juggling_tone4.png
new file mode 100644
index 00000000000..1c57823203f
--- /dev/null
+++ b/app/assets/images/emoji/juggling_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/juggling_tone5.png b/app/assets/images/emoji/juggling_tone5.png
new file mode 100644
index 00000000000..c343d6ee98a
--- /dev/null
+++ b/app/assets/images/emoji/juggling_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/kaaba.png b/app/assets/images/emoji/kaaba.png
new file mode 100644
index 00000000000..1778c1138e4
--- /dev/null
+++ b/app/assets/images/emoji/kaaba.png
Binary files differ
diff --git a/app/assets/images/emoji/key.png b/app/assets/images/emoji/key.png
new file mode 100644
index 00000000000..319cd1b884c
--- /dev/null
+++ b/app/assets/images/emoji/key.png
Binary files differ
diff --git a/app/assets/images/emoji/key2.png b/app/assets/images/emoji/key2.png
new file mode 100644
index 00000000000..e11d706c6c8
--- /dev/null
+++ b/app/assets/images/emoji/key2.png
Binary files differ
diff --git a/app/assets/images/emoji/keyboard.png b/app/assets/images/emoji/keyboard.png
new file mode 100644
index 00000000000..75027cb9af7
--- /dev/null
+++ b/app/assets/images/emoji/keyboard.png
Binary files differ
diff --git a/app/assets/images/emoji/kimono.png b/app/assets/images/emoji/kimono.png
new file mode 100644
index 00000000000..abe851115d1
--- /dev/null
+++ b/app/assets/images/emoji/kimono.png
Binary files differ
diff --git a/app/assets/images/emoji/kiss.png b/app/assets/images/emoji/kiss.png
new file mode 100644
index 00000000000..85e6dcfc4e8
--- /dev/null
+++ b/app/assets/images/emoji/kiss.png
Binary files differ
diff --git a/app/assets/images/emoji/kiss_mm.png b/app/assets/images/emoji/kiss_mm.png
new file mode 100644
index 00000000000..a9a0edae17c
--- /dev/null
+++ b/app/assets/images/emoji/kiss_mm.png
Binary files differ
diff --git a/app/assets/images/emoji/kiss_ww.png b/app/assets/images/emoji/kiss_ww.png
new file mode 100644
index 00000000000..fdac73cbb1d
--- /dev/null
+++ b/app/assets/images/emoji/kiss_ww.png
Binary files differ
diff --git a/app/assets/images/emoji/kissing.png b/app/assets/images/emoji/kissing.png
new file mode 100644
index 00000000000..39d325fd8e3
--- /dev/null
+++ b/app/assets/images/emoji/kissing.png
Binary files differ
diff --git a/app/assets/images/emoji/kissing_cat.png b/app/assets/images/emoji/kissing_cat.png
new file mode 100644
index 00000000000..6e0bcc77540
--- /dev/null
+++ b/app/assets/images/emoji/kissing_cat.png
Binary files differ
diff --git a/app/assets/images/emoji/kissing_closed_eyes.png b/app/assets/images/emoji/kissing_closed_eyes.png
new file mode 100644
index 00000000000..b684d7d4d6c
--- /dev/null
+++ b/app/assets/images/emoji/kissing_closed_eyes.png
Binary files differ
diff --git a/app/assets/images/emoji/kissing_heart.png b/app/assets/images/emoji/kissing_heart.png
new file mode 100644
index 00000000000..0ff808fd614
--- /dev/null
+++ b/app/assets/images/emoji/kissing_heart.png
Binary files differ
diff --git a/app/assets/images/emoji/kissing_smiling_eyes.png b/app/assets/images/emoji/kissing_smiling_eyes.png
new file mode 100644
index 00000000000..e181f17099d
--- /dev/null
+++ b/app/assets/images/emoji/kissing_smiling_eyes.png
Binary files differ
diff --git a/app/assets/images/emoji/kiwi.png b/app/assets/images/emoji/kiwi.png
new file mode 100644
index 00000000000..dfbd8258074
--- /dev/null
+++ b/app/assets/images/emoji/kiwi.png
Binary files differ
diff --git a/app/assets/images/emoji/knife.png b/app/assets/images/emoji/knife.png
new file mode 100644
index 00000000000..1acb9f3077b
--- /dev/null
+++ b/app/assets/images/emoji/knife.png
Binary files differ
diff --git a/app/assets/images/emoji/koala.png b/app/assets/images/emoji/koala.png
new file mode 100644
index 00000000000..a0aa437a98c
--- /dev/null
+++ b/app/assets/images/emoji/koala.png
Binary files differ
diff --git a/app/assets/images/emoji/koko.png b/app/assets/images/emoji/koko.png
new file mode 100644
index 00000000000..6450eb44d90
--- /dev/null
+++ b/app/assets/images/emoji/koko.png
Binary files differ
diff --git a/app/assets/images/emoji/label.png b/app/assets/images/emoji/label.png
new file mode 100644
index 00000000000..d41c9b4f1e1
--- /dev/null
+++ b/app/assets/images/emoji/label.png
Binary files differ
diff --git a/app/assets/images/emoji/large_blue_circle.png b/app/assets/images/emoji/large_blue_circle.png
new file mode 100644
index 00000000000..84078ef3127
--- /dev/null
+++ b/app/assets/images/emoji/large_blue_circle.png
Binary files differ
diff --git a/app/assets/images/emoji/large_blue_diamond.png b/app/assets/images/emoji/large_blue_diamond.png
new file mode 100644
index 00000000000..416a58bd5a8
--- /dev/null
+++ b/app/assets/images/emoji/large_blue_diamond.png
Binary files differ
diff --git a/app/assets/images/emoji/large_orange_diamond.png b/app/assets/images/emoji/large_orange_diamond.png
new file mode 100644
index 00000000000..73ff0ac36c8
--- /dev/null
+++ b/app/assets/images/emoji/large_orange_diamond.png
Binary files differ
diff --git a/app/assets/images/emoji/last_quarter_moon.png b/app/assets/images/emoji/last_quarter_moon.png
new file mode 100644
index 00000000000..0842a0dd408
--- /dev/null
+++ b/app/assets/images/emoji/last_quarter_moon.png
Binary files differ
diff --git a/app/assets/images/emoji/last_quarter_moon_with_face.png b/app/assets/images/emoji/last_quarter_moon_with_face.png
new file mode 100644
index 00000000000..94099343c5d
--- /dev/null
+++ b/app/assets/images/emoji/last_quarter_moon_with_face.png
Binary files differ
diff --git a/app/assets/images/emoji/laughing.png b/app/assets/images/emoji/laughing.png
new file mode 100644
index 00000000000..d94e9505ba1
--- /dev/null
+++ b/app/assets/images/emoji/laughing.png
Binary files differ
diff --git a/app/assets/images/emoji/leaves.png b/app/assets/images/emoji/leaves.png
new file mode 100644
index 00000000000..1e43e1af820
--- /dev/null
+++ b/app/assets/images/emoji/leaves.png
Binary files differ
diff --git a/app/assets/images/emoji/ledger.png b/app/assets/images/emoji/ledger.png
new file mode 100644
index 00000000000..13e7561a4bd
--- /dev/null
+++ b/app/assets/images/emoji/ledger.png
Binary files differ
diff --git a/app/assets/images/emoji/left_facing_fist.png b/app/assets/images/emoji/left_facing_fist.png
new file mode 100644
index 00000000000..a9d9fd8d59c
--- /dev/null
+++ b/app/assets/images/emoji/left_facing_fist.png
Binary files differ
diff --git a/app/assets/images/emoji/left_facing_fist_tone1.png b/app/assets/images/emoji/left_facing_fist_tone1.png
new file mode 100644
index 00000000000..1262a6b4b69
--- /dev/null
+++ b/app/assets/images/emoji/left_facing_fist_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/left_facing_fist_tone2.png b/app/assets/images/emoji/left_facing_fist_tone2.png
new file mode 100644
index 00000000000..40bf70b82b2
--- /dev/null
+++ b/app/assets/images/emoji/left_facing_fist_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/left_facing_fist_tone3.png b/app/assets/images/emoji/left_facing_fist_tone3.png
new file mode 100644
index 00000000000..93f58145111
--- /dev/null
+++ b/app/assets/images/emoji/left_facing_fist_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/left_facing_fist_tone4.png b/app/assets/images/emoji/left_facing_fist_tone4.png
new file mode 100644
index 00000000000..d82b5ec91f0
--- /dev/null
+++ b/app/assets/images/emoji/left_facing_fist_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/left_facing_fist_tone5.png b/app/assets/images/emoji/left_facing_fist_tone5.png
new file mode 100644
index 00000000000..09ae4cd492b
--- /dev/null
+++ b/app/assets/images/emoji/left_facing_fist_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/left_luggage.png b/app/assets/images/emoji/left_luggage.png
new file mode 100644
index 00000000000..887b23f3f25
--- /dev/null
+++ b/app/assets/images/emoji/left_luggage.png
Binary files differ
diff --git a/app/assets/images/emoji/left_right_arrow.png b/app/assets/images/emoji/left_right_arrow.png
new file mode 100644
index 00000000000..7937f24f2ac
--- /dev/null
+++ b/app/assets/images/emoji/left_right_arrow.png
Binary files differ
diff --git a/app/assets/images/emoji/leftwards_arrow_with_hook.png b/app/assets/images/emoji/leftwards_arrow_with_hook.png
new file mode 100644
index 00000000000..ba45c2ad9e9
--- /dev/null
+++ b/app/assets/images/emoji/leftwards_arrow_with_hook.png
Binary files differ
diff --git a/app/assets/images/emoji/lemon.png b/app/assets/images/emoji/lemon.png
new file mode 100644
index 00000000000..9a7d95ca220
--- /dev/null
+++ b/app/assets/images/emoji/lemon.png
Binary files differ
diff --git a/app/assets/images/emoji/leo.png b/app/assets/images/emoji/leo.png
new file mode 100644
index 00000000000..30158d34de9
--- /dev/null
+++ b/app/assets/images/emoji/leo.png
Binary files differ
diff --git a/app/assets/images/emoji/leopard.png b/app/assets/images/emoji/leopard.png
new file mode 100644
index 00000000000..8aac3d49448
--- /dev/null
+++ b/app/assets/images/emoji/leopard.png
Binary files differ
diff --git a/app/assets/images/emoji/level_slider.png b/app/assets/images/emoji/level_slider.png
new file mode 100644
index 00000000000..720a3b34119
--- /dev/null
+++ b/app/assets/images/emoji/level_slider.png
Binary files differ
diff --git a/app/assets/images/emoji/levitate.png b/app/assets/images/emoji/levitate.png
new file mode 100644
index 00000000000..3dc315a3d91
--- /dev/null
+++ b/app/assets/images/emoji/levitate.png
Binary files differ
diff --git a/app/assets/images/emoji/libra.png b/app/assets/images/emoji/libra.png
new file mode 100644
index 00000000000..8fd133a357c
--- /dev/null
+++ b/app/assets/images/emoji/libra.png
Binary files differ
diff --git a/app/assets/images/emoji/lifter.png b/app/assets/images/emoji/lifter.png
new file mode 100644
index 00000000000..afdeaa476af
--- /dev/null
+++ b/app/assets/images/emoji/lifter.png
Binary files differ
diff --git a/app/assets/images/emoji/lifter_tone1.png b/app/assets/images/emoji/lifter_tone1.png
new file mode 100644
index 00000000000..febaad123ec
--- /dev/null
+++ b/app/assets/images/emoji/lifter_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/lifter_tone2.png b/app/assets/images/emoji/lifter_tone2.png
new file mode 100644
index 00000000000..27ae794a18e
--- /dev/null
+++ b/app/assets/images/emoji/lifter_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/lifter_tone3.png b/app/assets/images/emoji/lifter_tone3.png
new file mode 100644
index 00000000000..45c4c22c709
--- /dev/null
+++ b/app/assets/images/emoji/lifter_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/lifter_tone4.png b/app/assets/images/emoji/lifter_tone4.png
new file mode 100644
index 00000000000..67dd21d2464
--- /dev/null
+++ b/app/assets/images/emoji/lifter_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/lifter_tone5.png b/app/assets/images/emoji/lifter_tone5.png
new file mode 100644
index 00000000000..fa0152038b6
--- /dev/null
+++ b/app/assets/images/emoji/lifter_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/light_rail.png b/app/assets/images/emoji/light_rail.png
new file mode 100644
index 00000000000..a64829f5078
--- /dev/null
+++ b/app/assets/images/emoji/light_rail.png
Binary files differ
diff --git a/app/assets/images/emoji/link.png b/app/assets/images/emoji/link.png
new file mode 100644
index 00000000000..ae20f0f8eec
--- /dev/null
+++ b/app/assets/images/emoji/link.png
Binary files differ
diff --git a/app/assets/images/emoji/lion_face.png b/app/assets/images/emoji/lion_face.png
new file mode 100644
index 00000000000..5062ab47ecf
--- /dev/null
+++ b/app/assets/images/emoji/lion_face.png
Binary files differ
diff --git a/app/assets/images/emoji/lips.png b/app/assets/images/emoji/lips.png
new file mode 100644
index 00000000000..35f3cc2006f
--- /dev/null
+++ b/app/assets/images/emoji/lips.png
Binary files differ
diff --git a/app/assets/images/emoji/lipstick.png b/app/assets/images/emoji/lipstick.png
new file mode 100644
index 00000000000..61a0c084c99
--- /dev/null
+++ b/app/assets/images/emoji/lipstick.png
Binary files differ
diff --git a/app/assets/images/emoji/lizard.png b/app/assets/images/emoji/lizard.png
new file mode 100644
index 00000000000..8363876050e
--- /dev/null
+++ b/app/assets/images/emoji/lizard.png
Binary files differ
diff --git a/app/assets/images/emoji/lock.png b/app/assets/images/emoji/lock.png
new file mode 100644
index 00000000000..5a739c46644
--- /dev/null
+++ b/app/assets/images/emoji/lock.png
Binary files differ
diff --git a/app/assets/images/emoji/lock_with_ink_pen.png b/app/assets/images/emoji/lock_with_ink_pen.png
new file mode 100644
index 00000000000..19a07d162fb
--- /dev/null
+++ b/app/assets/images/emoji/lock_with_ink_pen.png
Binary files differ
diff --git a/app/assets/images/emoji/lollipop.png b/app/assets/images/emoji/lollipop.png
new file mode 100644
index 00000000000..ad76d7bf916
--- /dev/null
+++ b/app/assets/images/emoji/lollipop.png
Binary files differ
diff --git a/app/assets/images/emoji/loop.png b/app/assets/images/emoji/loop.png
new file mode 100644
index 00000000000..0b82c8fe315
--- /dev/null
+++ b/app/assets/images/emoji/loop.png
Binary files differ
diff --git a/app/assets/images/emoji/loud_sound.png b/app/assets/images/emoji/loud_sound.png
new file mode 100644
index 00000000000..8370033a539
--- /dev/null
+++ b/app/assets/images/emoji/loud_sound.png
Binary files differ
diff --git a/app/assets/images/emoji/loudspeaker.png b/app/assets/images/emoji/loudspeaker.png
new file mode 100644
index 00000000000..5fd76a95b82
--- /dev/null
+++ b/app/assets/images/emoji/loudspeaker.png
Binary files differ
diff --git a/app/assets/images/emoji/love_hotel.png b/app/assets/images/emoji/love_hotel.png
new file mode 100644
index 00000000000..5e136be6f8b
--- /dev/null
+++ b/app/assets/images/emoji/love_hotel.png
Binary files differ
diff --git a/app/assets/images/emoji/love_letter.png b/app/assets/images/emoji/love_letter.png
new file mode 100644
index 00000000000..3c3c767e784
--- /dev/null
+++ b/app/assets/images/emoji/love_letter.png
Binary files differ
diff --git a/app/assets/images/emoji/low_brightness.png b/app/assets/images/emoji/low_brightness.png
new file mode 100644
index 00000000000..543011d3961
--- /dev/null
+++ b/app/assets/images/emoji/low_brightness.png
Binary files differ
diff --git a/app/assets/images/emoji/lying_face.png b/app/assets/images/emoji/lying_face.png
new file mode 100644
index 00000000000..02827e2628b
--- /dev/null
+++ b/app/assets/images/emoji/lying_face.png
Binary files differ
diff --git a/app/assets/images/emoji/m.png b/app/assets/images/emoji/m.png
new file mode 100644
index 00000000000..8a3506fc1d7
--- /dev/null
+++ b/app/assets/images/emoji/m.png
Binary files differ
diff --git a/app/assets/images/emoji/mag.png b/app/assets/images/emoji/mag.png
new file mode 100644
index 00000000000..55487156ac6
--- /dev/null
+++ b/app/assets/images/emoji/mag.png
Binary files differ
diff --git a/app/assets/images/emoji/mag_right.png b/app/assets/images/emoji/mag_right.png
new file mode 100644
index 00000000000..0f4b1bca876
--- /dev/null
+++ b/app/assets/images/emoji/mag_right.png
Binary files differ
diff --git a/app/assets/images/emoji/mahjong.png b/app/assets/images/emoji/mahjong.png
new file mode 100644
index 00000000000..66fd32025b2
--- /dev/null
+++ b/app/assets/images/emoji/mahjong.png
Binary files differ
diff --git a/app/assets/images/emoji/mailbox.png b/app/assets/images/emoji/mailbox.png
new file mode 100644
index 00000000000..ef5174e40dd
--- /dev/null
+++ b/app/assets/images/emoji/mailbox.png
Binary files differ
diff --git a/app/assets/images/emoji/mailbox_closed.png b/app/assets/images/emoji/mailbox_closed.png
new file mode 100644
index 00000000000..ddc705db0d8
--- /dev/null
+++ b/app/assets/images/emoji/mailbox_closed.png
Binary files differ
diff --git a/app/assets/images/emoji/mailbox_with_mail.png b/app/assets/images/emoji/mailbox_with_mail.png
new file mode 100644
index 00000000000..5460616a5b1
--- /dev/null
+++ b/app/assets/images/emoji/mailbox_with_mail.png
Binary files differ
diff --git a/app/assets/images/emoji/mailbox_with_no_mail.png b/app/assets/images/emoji/mailbox_with_no_mail.png
new file mode 100644
index 00000000000..f9aeee6b15a
--- /dev/null
+++ b/app/assets/images/emoji/mailbox_with_no_mail.png
Binary files differ
diff --git a/app/assets/images/emoji/man.png b/app/assets/images/emoji/man.png
new file mode 100644
index 00000000000..857a02e5146
--- /dev/null
+++ b/app/assets/images/emoji/man.png
Binary files differ
diff --git a/app/assets/images/emoji/man_dancing.png b/app/assets/images/emoji/man_dancing.png
new file mode 100644
index 00000000000..ccff3bede5a
--- /dev/null
+++ b/app/assets/images/emoji/man_dancing.png
Binary files differ
diff --git a/app/assets/images/emoji/man_dancing_tone1.png b/app/assets/images/emoji/man_dancing_tone1.png
new file mode 100644
index 00000000000..e0b9f82d905
--- /dev/null
+++ b/app/assets/images/emoji/man_dancing_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/man_dancing_tone2.png b/app/assets/images/emoji/man_dancing_tone2.png
new file mode 100644
index 00000000000..a5beed56e2e
--- /dev/null
+++ b/app/assets/images/emoji/man_dancing_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/man_dancing_tone3.png b/app/assets/images/emoji/man_dancing_tone3.png
new file mode 100644
index 00000000000..2fa20180a6e
--- /dev/null
+++ b/app/assets/images/emoji/man_dancing_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/man_dancing_tone4.png b/app/assets/images/emoji/man_dancing_tone4.png
new file mode 100644
index 00000000000..bd3528c83ba
--- /dev/null
+++ b/app/assets/images/emoji/man_dancing_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/man_dancing_tone5.png b/app/assets/images/emoji/man_dancing_tone5.png
new file mode 100644
index 00000000000..41fd4f880c9
--- /dev/null
+++ b/app/assets/images/emoji/man_dancing_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/man_in_tuxedo.png b/app/assets/images/emoji/man_in_tuxedo.png
new file mode 100644
index 00000000000..5f7e9303f89
--- /dev/null
+++ b/app/assets/images/emoji/man_in_tuxedo.png
Binary files differ
diff --git a/app/assets/images/emoji/man_in_tuxedo_tone1.png b/app/assets/images/emoji/man_in_tuxedo_tone1.png
new file mode 100644
index 00000000000..7b6b3acd99b
--- /dev/null
+++ b/app/assets/images/emoji/man_in_tuxedo_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/man_in_tuxedo_tone2.png b/app/assets/images/emoji/man_in_tuxedo_tone2.png
new file mode 100644
index 00000000000..7975191b360
--- /dev/null
+++ b/app/assets/images/emoji/man_in_tuxedo_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/man_in_tuxedo_tone3.png b/app/assets/images/emoji/man_in_tuxedo_tone3.png
new file mode 100644
index 00000000000..a2816f600ae
--- /dev/null
+++ b/app/assets/images/emoji/man_in_tuxedo_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/man_in_tuxedo_tone4.png b/app/assets/images/emoji/man_in_tuxedo_tone4.png
new file mode 100644
index 00000000000..ea8291760f9
--- /dev/null
+++ b/app/assets/images/emoji/man_in_tuxedo_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/man_in_tuxedo_tone5.png b/app/assets/images/emoji/man_in_tuxedo_tone5.png
new file mode 100644
index 00000000000..c743e05fc5e
--- /dev/null
+++ b/app/assets/images/emoji/man_in_tuxedo_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/man_tone1.png b/app/assets/images/emoji/man_tone1.png
new file mode 100644
index 00000000000..bb86e963a80
--- /dev/null
+++ b/app/assets/images/emoji/man_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/man_tone2.png b/app/assets/images/emoji/man_tone2.png
new file mode 100644
index 00000000000..fdeeaff46f5
--- /dev/null
+++ b/app/assets/images/emoji/man_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/man_tone3.png b/app/assets/images/emoji/man_tone3.png
new file mode 100644
index 00000000000..7ae0b5df9cf
--- /dev/null
+++ b/app/assets/images/emoji/man_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/man_tone4.png b/app/assets/images/emoji/man_tone4.png
new file mode 100644
index 00000000000..db14cde99b8
--- /dev/null
+++ b/app/assets/images/emoji/man_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/man_tone5.png b/app/assets/images/emoji/man_tone5.png
new file mode 100644
index 00000000000..7c67a70529c
--- /dev/null
+++ b/app/assets/images/emoji/man_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/man_with_gua_pi_mao.png b/app/assets/images/emoji/man_with_gua_pi_mao.png
new file mode 100644
index 00000000000..7841e13608d
--- /dev/null
+++ b/app/assets/images/emoji/man_with_gua_pi_mao.png
Binary files differ
diff --git a/app/assets/images/emoji/man_with_gua_pi_mao_tone1.png b/app/assets/images/emoji/man_with_gua_pi_mao_tone1.png
new file mode 100644
index 00000000000..5b7b3def19c
--- /dev/null
+++ b/app/assets/images/emoji/man_with_gua_pi_mao_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/man_with_gua_pi_mao_tone2.png b/app/assets/images/emoji/man_with_gua_pi_mao_tone2.png
new file mode 100644
index 00000000000..c8b9cf87f4b
--- /dev/null
+++ b/app/assets/images/emoji/man_with_gua_pi_mao_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/man_with_gua_pi_mao_tone3.png b/app/assets/images/emoji/man_with_gua_pi_mao_tone3.png
new file mode 100644
index 00000000000..effdd0c4c84
--- /dev/null
+++ b/app/assets/images/emoji/man_with_gua_pi_mao_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/man_with_gua_pi_mao_tone4.png b/app/assets/images/emoji/man_with_gua_pi_mao_tone4.png
new file mode 100644
index 00000000000..f885ff46fa1
--- /dev/null
+++ b/app/assets/images/emoji/man_with_gua_pi_mao_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/man_with_gua_pi_mao_tone5.png b/app/assets/images/emoji/man_with_gua_pi_mao_tone5.png
new file mode 100644
index 00000000000..a6d55ca1380
--- /dev/null
+++ b/app/assets/images/emoji/man_with_gua_pi_mao_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/man_with_turban.png b/app/assets/images/emoji/man_with_turban.png
new file mode 100644
index 00000000000..51cf047f966
--- /dev/null
+++ b/app/assets/images/emoji/man_with_turban.png
Binary files differ
diff --git a/app/assets/images/emoji/man_with_turban_tone1.png b/app/assets/images/emoji/man_with_turban_tone1.png
new file mode 100644
index 00000000000..1e12ee4b231
--- /dev/null
+++ b/app/assets/images/emoji/man_with_turban_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/man_with_turban_tone2.png b/app/assets/images/emoji/man_with_turban_tone2.png
new file mode 100644
index 00000000000..37de4cceb23
--- /dev/null
+++ b/app/assets/images/emoji/man_with_turban_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/man_with_turban_tone3.png b/app/assets/images/emoji/man_with_turban_tone3.png
new file mode 100644
index 00000000000..f607afd3450
--- /dev/null
+++ b/app/assets/images/emoji/man_with_turban_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/man_with_turban_tone4.png b/app/assets/images/emoji/man_with_turban_tone4.png
new file mode 100644
index 00000000000..c05695888af
--- /dev/null
+++ b/app/assets/images/emoji/man_with_turban_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/man_with_turban_tone5.png b/app/assets/images/emoji/man_with_turban_tone5.png
new file mode 100644
index 00000000000..4b4ff64720b
--- /dev/null
+++ b/app/assets/images/emoji/man_with_turban_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/mans_shoe.png b/app/assets/images/emoji/mans_shoe.png
new file mode 100644
index 00000000000..4bf7541032c
--- /dev/null
+++ b/app/assets/images/emoji/mans_shoe.png
Binary files differ
diff --git a/app/assets/images/emoji/map.png b/app/assets/images/emoji/map.png
new file mode 100644
index 00000000000..15efe32c798
--- /dev/null
+++ b/app/assets/images/emoji/map.png
Binary files differ
diff --git a/app/assets/images/emoji/maple_leaf.png b/app/assets/images/emoji/maple_leaf.png
new file mode 100644
index 00000000000..c49acea67f7
--- /dev/null
+++ b/app/assets/images/emoji/maple_leaf.png
Binary files differ
diff --git a/app/assets/images/emoji/martial_arts_uniform.png b/app/assets/images/emoji/martial_arts_uniform.png
new file mode 100644
index 00000000000..8d6114761f6
--- /dev/null
+++ b/app/assets/images/emoji/martial_arts_uniform.png
Binary files differ
diff --git a/app/assets/images/emoji/mask.png b/app/assets/images/emoji/mask.png
new file mode 100644
index 00000000000..1e800acd1c0
--- /dev/null
+++ b/app/assets/images/emoji/mask.png
Binary files differ
diff --git a/app/assets/images/emoji/massage.png b/app/assets/images/emoji/massage.png
new file mode 100644
index 00000000000..b91d845e374
--- /dev/null
+++ b/app/assets/images/emoji/massage.png
Binary files differ
diff --git a/app/assets/images/emoji/massage_tone1.png b/app/assets/images/emoji/massage_tone1.png
new file mode 100644
index 00000000000..e0f415d3186
--- /dev/null
+++ b/app/assets/images/emoji/massage_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/massage_tone2.png b/app/assets/images/emoji/massage_tone2.png
new file mode 100644
index 00000000000..0bb244a270b
--- /dev/null
+++ b/app/assets/images/emoji/massage_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/massage_tone3.png b/app/assets/images/emoji/massage_tone3.png
new file mode 100644
index 00000000000..a117ee81a22
--- /dev/null
+++ b/app/assets/images/emoji/massage_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/massage_tone4.png b/app/assets/images/emoji/massage_tone4.png
new file mode 100644
index 00000000000..6f42ab017f4
--- /dev/null
+++ b/app/assets/images/emoji/massage_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/massage_tone5.png b/app/assets/images/emoji/massage_tone5.png
new file mode 100644
index 00000000000..6a388c0d0b5
--- /dev/null
+++ b/app/assets/images/emoji/massage_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/meat_on_bone.png b/app/assets/images/emoji/meat_on_bone.png
new file mode 100644
index 00000000000..b20a59d1690
--- /dev/null
+++ b/app/assets/images/emoji/meat_on_bone.png
Binary files differ
diff --git a/app/assets/images/emoji/medal.png b/app/assets/images/emoji/medal.png
new file mode 100644
index 00000000000..b85896b14da
--- /dev/null
+++ b/app/assets/images/emoji/medal.png
Binary files differ
diff --git a/app/assets/images/emoji/mega.png b/app/assets/images/emoji/mega.png
new file mode 100644
index 00000000000..4e6735188e3
--- /dev/null
+++ b/app/assets/images/emoji/mega.png
Binary files differ
diff --git a/app/assets/images/emoji/melon.png b/app/assets/images/emoji/melon.png
new file mode 100644
index 00000000000..c01232d419d
--- /dev/null
+++ b/app/assets/images/emoji/melon.png
Binary files differ
diff --git a/app/assets/images/emoji/menorah.png b/app/assets/images/emoji/menorah.png
new file mode 100644
index 00000000000..b4297362869
--- /dev/null
+++ b/app/assets/images/emoji/menorah.png
Binary files differ
diff --git a/app/assets/images/emoji/mens.png b/app/assets/images/emoji/mens.png
new file mode 100644
index 00000000000..f5a1e1ba0cd
--- /dev/null
+++ b/app/assets/images/emoji/mens.png
Binary files differ
diff --git a/app/assets/images/emoji/metal.png b/app/assets/images/emoji/metal.png
new file mode 100644
index 00000000000..4aa6e7e0a44
--- /dev/null
+++ b/app/assets/images/emoji/metal.png
Binary files differ
diff --git a/app/assets/images/emoji/metal_tone1.png b/app/assets/images/emoji/metal_tone1.png
new file mode 100644
index 00000000000..c080d2addbd
--- /dev/null
+++ b/app/assets/images/emoji/metal_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/metal_tone2.png b/app/assets/images/emoji/metal_tone2.png
new file mode 100644
index 00000000000..12313529bcf
--- /dev/null
+++ b/app/assets/images/emoji/metal_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/metal_tone3.png b/app/assets/images/emoji/metal_tone3.png
new file mode 100644
index 00000000000..ca9be6ae67b
--- /dev/null
+++ b/app/assets/images/emoji/metal_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/metal_tone4.png b/app/assets/images/emoji/metal_tone4.png
new file mode 100644
index 00000000000..abe28cbf890
--- /dev/null
+++ b/app/assets/images/emoji/metal_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/metal_tone5.png b/app/assets/images/emoji/metal_tone5.png
new file mode 100644
index 00000000000..0c6b5dd34ed
--- /dev/null
+++ b/app/assets/images/emoji/metal_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/metro.png b/app/assets/images/emoji/metro.png
new file mode 100644
index 00000000000..1de8f0551f3
--- /dev/null
+++ b/app/assets/images/emoji/metro.png
Binary files differ
diff --git a/app/assets/images/emoji/microphone.png b/app/assets/images/emoji/microphone.png
new file mode 100644
index 00000000000..d4e6b0def25
--- /dev/null
+++ b/app/assets/images/emoji/microphone.png
Binary files differ
diff --git a/app/assets/images/emoji/microphone2.png b/app/assets/images/emoji/microphone2.png
new file mode 100644
index 00000000000..cd9167654ff
--- /dev/null
+++ b/app/assets/images/emoji/microphone2.png
Binary files differ
diff --git a/app/assets/images/emoji/microscope.png b/app/assets/images/emoji/microscope.png
new file mode 100644
index 00000000000..90f5acf6a78
--- /dev/null
+++ b/app/assets/images/emoji/microscope.png
Binary files differ
diff --git a/app/assets/images/emoji/middle_finger.png b/app/assets/images/emoji/middle_finger.png
new file mode 100644
index 00000000000..697f7a25eb2
--- /dev/null
+++ b/app/assets/images/emoji/middle_finger.png
Binary files differ
diff --git a/app/assets/images/emoji/middle_finger_tone1.png b/app/assets/images/emoji/middle_finger_tone1.png
new file mode 100644
index 00000000000..61ef12a1548
--- /dev/null
+++ b/app/assets/images/emoji/middle_finger_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/middle_finger_tone2.png b/app/assets/images/emoji/middle_finger_tone2.png
new file mode 100644
index 00000000000..c31a69be9af
--- /dev/null
+++ b/app/assets/images/emoji/middle_finger_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/middle_finger_tone3.png b/app/assets/images/emoji/middle_finger_tone3.png
new file mode 100644
index 00000000000..73ac216ce63
--- /dev/null
+++ b/app/assets/images/emoji/middle_finger_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/middle_finger_tone4.png b/app/assets/images/emoji/middle_finger_tone4.png
new file mode 100644
index 00000000000..80b8ab7706d
--- /dev/null
+++ b/app/assets/images/emoji/middle_finger_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/middle_finger_tone5.png b/app/assets/images/emoji/middle_finger_tone5.png
new file mode 100644
index 00000000000..a8826b196e8
--- /dev/null
+++ b/app/assets/images/emoji/middle_finger_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/military_medal.png b/app/assets/images/emoji/military_medal.png
new file mode 100644
index 00000000000..ecd3fb03584
--- /dev/null
+++ b/app/assets/images/emoji/military_medal.png
Binary files differ
diff --git a/app/assets/images/emoji/milk.png b/app/assets/images/emoji/milk.png
new file mode 100644
index 00000000000..e4fcf2e64f3
--- /dev/null
+++ b/app/assets/images/emoji/milk.png
Binary files differ
diff --git a/app/assets/images/emoji/milky_way.png b/app/assets/images/emoji/milky_way.png
new file mode 100644
index 00000000000..b2b8ac59c5e
--- /dev/null
+++ b/app/assets/images/emoji/milky_way.png
Binary files differ
diff --git a/app/assets/images/emoji/minibus.png b/app/assets/images/emoji/minibus.png
new file mode 100644
index 00000000000..c60dd8f47ab
--- /dev/null
+++ b/app/assets/images/emoji/minibus.png
Binary files differ
diff --git a/app/assets/images/emoji/minidisc.png b/app/assets/images/emoji/minidisc.png
new file mode 100644
index 00000000000..9fa94cfbe74
--- /dev/null
+++ b/app/assets/images/emoji/minidisc.png
Binary files differ
diff --git a/app/assets/images/emoji/mobile_phone_off.png b/app/assets/images/emoji/mobile_phone_off.png
new file mode 100644
index 00000000000..8b661ec1c94
--- /dev/null
+++ b/app/assets/images/emoji/mobile_phone_off.png
Binary files differ
diff --git a/app/assets/images/emoji/money_mouth.png b/app/assets/images/emoji/money_mouth.png
new file mode 100644
index 00000000000..75fd1e90cb0
--- /dev/null
+++ b/app/assets/images/emoji/money_mouth.png
Binary files differ
diff --git a/app/assets/images/emoji/money_with_wings.png b/app/assets/images/emoji/money_with_wings.png
new file mode 100644
index 00000000000..f022b04b3c2
--- /dev/null
+++ b/app/assets/images/emoji/money_with_wings.png
Binary files differ
diff --git a/app/assets/images/emoji/moneybag.png b/app/assets/images/emoji/moneybag.png
new file mode 100644
index 00000000000..b9296be0902
--- /dev/null
+++ b/app/assets/images/emoji/moneybag.png
Binary files differ
diff --git a/app/assets/images/emoji/monkey.png b/app/assets/images/emoji/monkey.png
new file mode 100644
index 00000000000..9fae29448e3
--- /dev/null
+++ b/app/assets/images/emoji/monkey.png
Binary files differ
diff --git a/app/assets/images/emoji/monkey_face.png b/app/assets/images/emoji/monkey_face.png
new file mode 100644
index 00000000000..7cab9b91a82
--- /dev/null
+++ b/app/assets/images/emoji/monkey_face.png
Binary files differ
diff --git a/app/assets/images/emoji/monorail.png b/app/assets/images/emoji/monorail.png
new file mode 100644
index 00000000000..11eb1f574bf
--- /dev/null
+++ b/app/assets/images/emoji/monorail.png
Binary files differ
diff --git a/app/assets/images/emoji/mortar_board.png b/app/assets/images/emoji/mortar_board.png
new file mode 100644
index 00000000000..8b17ddd9d00
--- /dev/null
+++ b/app/assets/images/emoji/mortar_board.png
Binary files differ
diff --git a/app/assets/images/emoji/mosque.png b/app/assets/images/emoji/mosque.png
new file mode 100644
index 00000000000..ef770b26d96
--- /dev/null
+++ b/app/assets/images/emoji/mosque.png
Binary files differ
diff --git a/app/assets/images/emoji/motor_scooter.png b/app/assets/images/emoji/motor_scooter.png
new file mode 100644
index 00000000000..c5afa72d807
--- /dev/null
+++ b/app/assets/images/emoji/motor_scooter.png
Binary files differ
diff --git a/app/assets/images/emoji/motorboat.png b/app/assets/images/emoji/motorboat.png
new file mode 100644
index 00000000000..0506db1a40f
--- /dev/null
+++ b/app/assets/images/emoji/motorboat.png
Binary files differ
diff --git a/app/assets/images/emoji/motorcycle.png b/app/assets/images/emoji/motorcycle.png
new file mode 100644
index 00000000000..3d1d567e8ec
--- /dev/null
+++ b/app/assets/images/emoji/motorcycle.png
Binary files differ
diff --git a/app/assets/images/emoji/motorway.png b/app/assets/images/emoji/motorway.png
new file mode 100644
index 00000000000..8c3d3d03e3f
--- /dev/null
+++ b/app/assets/images/emoji/motorway.png
Binary files differ
diff --git a/app/assets/images/emoji/mount_fuji.png b/app/assets/images/emoji/mount_fuji.png
new file mode 100644
index 00000000000..88a54752458
--- /dev/null
+++ b/app/assets/images/emoji/mount_fuji.png
Binary files differ
diff --git a/app/assets/images/emoji/mountain.png b/app/assets/images/emoji/mountain.png
new file mode 100644
index 00000000000..6722ebdd294
--- /dev/null
+++ b/app/assets/images/emoji/mountain.png
Binary files differ
diff --git a/app/assets/images/emoji/mountain_bicyclist.png b/app/assets/images/emoji/mountain_bicyclist.png
new file mode 100644
index 00000000000..41d3dc3ac6f
--- /dev/null
+++ b/app/assets/images/emoji/mountain_bicyclist.png
Binary files differ
diff --git a/app/assets/images/emoji/mountain_bicyclist_tone1.png b/app/assets/images/emoji/mountain_bicyclist_tone1.png
new file mode 100644
index 00000000000..e9f1daf5e40
--- /dev/null
+++ b/app/assets/images/emoji/mountain_bicyclist_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/mountain_bicyclist_tone2.png b/app/assets/images/emoji/mountain_bicyclist_tone2.png
new file mode 100644
index 00000000000..555b9e29d4d
--- /dev/null
+++ b/app/assets/images/emoji/mountain_bicyclist_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/mountain_bicyclist_tone3.png b/app/assets/images/emoji/mountain_bicyclist_tone3.png
new file mode 100644
index 00000000000..7df5508ec8c
--- /dev/null
+++ b/app/assets/images/emoji/mountain_bicyclist_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/mountain_bicyclist_tone4.png b/app/assets/images/emoji/mountain_bicyclist_tone4.png
new file mode 100644
index 00000000000..f94b3450697
--- /dev/null
+++ b/app/assets/images/emoji/mountain_bicyclist_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/mountain_bicyclist_tone5.png b/app/assets/images/emoji/mountain_bicyclist_tone5.png
new file mode 100644
index 00000000000..16a45861e1f
--- /dev/null
+++ b/app/assets/images/emoji/mountain_bicyclist_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/mountain_cableway.png b/app/assets/images/emoji/mountain_cableway.png
new file mode 100644
index 00000000000..1dea73ca53b
--- /dev/null
+++ b/app/assets/images/emoji/mountain_cableway.png
Binary files differ
diff --git a/app/assets/images/emoji/mountain_railway.png b/app/assets/images/emoji/mountain_railway.png
new file mode 100644
index 00000000000..ade2218e469
--- /dev/null
+++ b/app/assets/images/emoji/mountain_railway.png
Binary files differ
diff --git a/app/assets/images/emoji/mountain_snow.png b/app/assets/images/emoji/mountain_snow.png
new file mode 100644
index 00000000000..76e1cfd8313
--- /dev/null
+++ b/app/assets/images/emoji/mountain_snow.png
Binary files differ
diff --git a/app/assets/images/emoji/mouse.png b/app/assets/images/emoji/mouse.png
new file mode 100644
index 00000000000..50afcd3262e
--- /dev/null
+++ b/app/assets/images/emoji/mouse.png
Binary files differ
diff --git a/app/assets/images/emoji/mouse2.png b/app/assets/images/emoji/mouse2.png
new file mode 100644
index 00000000000..20fb041f09f
--- /dev/null
+++ b/app/assets/images/emoji/mouse2.png
Binary files differ
diff --git a/app/assets/images/emoji/mouse_three_button.png b/app/assets/images/emoji/mouse_three_button.png
new file mode 100644
index 00000000000..e84e96ff6e8
--- /dev/null
+++ b/app/assets/images/emoji/mouse_three_button.png
Binary files differ
diff --git a/app/assets/images/emoji/movie_camera.png b/app/assets/images/emoji/movie_camera.png
new file mode 100644
index 00000000000..4e73b130155
--- /dev/null
+++ b/app/assets/images/emoji/movie_camera.png
Binary files differ
diff --git a/app/assets/images/emoji/moyai.png b/app/assets/images/emoji/moyai.png
new file mode 100644
index 00000000000..e6a7779c45b
--- /dev/null
+++ b/app/assets/images/emoji/moyai.png
Binary files differ
diff --git a/app/assets/images/emoji/mrs_claus.png b/app/assets/images/emoji/mrs_claus.png
new file mode 100644
index 00000000000..078f0657f95
--- /dev/null
+++ b/app/assets/images/emoji/mrs_claus.png
Binary files differ
diff --git a/app/assets/images/emoji/mrs_claus_tone1.png b/app/assets/images/emoji/mrs_claus_tone1.png
new file mode 100644
index 00000000000..d8a695d7035
--- /dev/null
+++ b/app/assets/images/emoji/mrs_claus_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/mrs_claus_tone2.png b/app/assets/images/emoji/mrs_claus_tone2.png
new file mode 100644
index 00000000000..0e17e8c51f3
--- /dev/null
+++ b/app/assets/images/emoji/mrs_claus_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/mrs_claus_tone3.png b/app/assets/images/emoji/mrs_claus_tone3.png
new file mode 100644
index 00000000000..c3ee4d1dfae
--- /dev/null
+++ b/app/assets/images/emoji/mrs_claus_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/mrs_claus_tone4.png b/app/assets/images/emoji/mrs_claus_tone4.png
new file mode 100644
index 00000000000..68a556da2fe
--- /dev/null
+++ b/app/assets/images/emoji/mrs_claus_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/mrs_claus_tone5.png b/app/assets/images/emoji/mrs_claus_tone5.png
new file mode 100644
index 00000000000..ccab3c40ff2
--- /dev/null
+++ b/app/assets/images/emoji/mrs_claus_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/muscle.png b/app/assets/images/emoji/muscle.png
new file mode 100644
index 00000000000..7e67c1880f7
--- /dev/null
+++ b/app/assets/images/emoji/muscle.png
Binary files differ
diff --git a/app/assets/images/emoji/muscle_tone1.png b/app/assets/images/emoji/muscle_tone1.png
new file mode 100644
index 00000000000..1522942ce51
--- /dev/null
+++ b/app/assets/images/emoji/muscle_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/muscle_tone2.png b/app/assets/images/emoji/muscle_tone2.png
new file mode 100644
index 00000000000..569c6e832ca
--- /dev/null
+++ b/app/assets/images/emoji/muscle_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/muscle_tone3.png b/app/assets/images/emoji/muscle_tone3.png
new file mode 100644
index 00000000000..0a76b00fa89
--- /dev/null
+++ b/app/assets/images/emoji/muscle_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/muscle_tone4.png b/app/assets/images/emoji/muscle_tone4.png
new file mode 100644
index 00000000000..f0cf31328e0
--- /dev/null
+++ b/app/assets/images/emoji/muscle_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/muscle_tone5.png b/app/assets/images/emoji/muscle_tone5.png
new file mode 100644
index 00000000000..4fda92460e8
--- /dev/null
+++ b/app/assets/images/emoji/muscle_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/mushroom.png b/app/assets/images/emoji/mushroom.png
new file mode 100644
index 00000000000..dd85742ba2c
--- /dev/null
+++ b/app/assets/images/emoji/mushroom.png
Binary files differ
diff --git a/app/assets/images/emoji/musical_keyboard.png b/app/assets/images/emoji/musical_keyboard.png
new file mode 100644
index 00000000000..442b7456842
--- /dev/null
+++ b/app/assets/images/emoji/musical_keyboard.png
Binary files differ
diff --git a/app/assets/images/emoji/musical_note.png b/app/assets/images/emoji/musical_note.png
new file mode 100644
index 00000000000..06691ef61bb
--- /dev/null
+++ b/app/assets/images/emoji/musical_note.png
Binary files differ
diff --git a/app/assets/images/emoji/musical_score.png b/app/assets/images/emoji/musical_score.png
new file mode 100644
index 00000000000..47dc05a8ef5
--- /dev/null
+++ b/app/assets/images/emoji/musical_score.png
Binary files differ
diff --git a/app/assets/images/emoji/mute.png b/app/assets/images/emoji/mute.png
new file mode 100644
index 00000000000..7c1788e5075
--- /dev/null
+++ b/app/assets/images/emoji/mute.png
Binary files differ
diff --git a/app/assets/images/emoji/nail_care.png b/app/assets/images/emoji/nail_care.png
new file mode 100644
index 00000000000..aa52af7050d
--- /dev/null
+++ b/app/assets/images/emoji/nail_care.png
Binary files differ
diff --git a/app/assets/images/emoji/nail_care_tone1.png b/app/assets/images/emoji/nail_care_tone1.png
new file mode 100644
index 00000000000..26e883dd244
--- /dev/null
+++ b/app/assets/images/emoji/nail_care_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/nail_care_tone2.png b/app/assets/images/emoji/nail_care_tone2.png
new file mode 100644
index 00000000000..61257b47ea3
--- /dev/null
+++ b/app/assets/images/emoji/nail_care_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/nail_care_tone3.png b/app/assets/images/emoji/nail_care_tone3.png
new file mode 100644
index 00000000000..29871b05f62
--- /dev/null
+++ b/app/assets/images/emoji/nail_care_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/nail_care_tone4.png b/app/assets/images/emoji/nail_care_tone4.png
new file mode 100644
index 00000000000..2881de0b17d
--- /dev/null
+++ b/app/assets/images/emoji/nail_care_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/nail_care_tone5.png b/app/assets/images/emoji/nail_care_tone5.png
new file mode 100644
index 00000000000..a0b7c0a45a6
--- /dev/null
+++ b/app/assets/images/emoji/nail_care_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/name_badge.png b/app/assets/images/emoji/name_badge.png
new file mode 100644
index 00000000000..ec5ee213e20
--- /dev/null
+++ b/app/assets/images/emoji/name_badge.png
Binary files differ
diff --git a/app/assets/images/emoji/nauseated_face.png b/app/assets/images/emoji/nauseated_face.png
new file mode 100644
index 00000000000..a566c109c28
--- /dev/null
+++ b/app/assets/images/emoji/nauseated_face.png
Binary files differ
diff --git a/app/assets/images/emoji/necktie.png b/app/assets/images/emoji/necktie.png
new file mode 100644
index 00000000000..1804e7f3ff3
--- /dev/null
+++ b/app/assets/images/emoji/necktie.png
Binary files differ
diff --git a/app/assets/images/emoji/negative_squared_cross_mark.png b/app/assets/images/emoji/negative_squared_cross_mark.png
new file mode 100644
index 00000000000..dae487f1f98
--- /dev/null
+++ b/app/assets/images/emoji/negative_squared_cross_mark.png
Binary files differ
diff --git a/app/assets/images/emoji/nerd.png b/app/assets/images/emoji/nerd.png
new file mode 100644
index 00000000000..7820bd581dc
--- /dev/null
+++ b/app/assets/images/emoji/nerd.png
Binary files differ
diff --git a/app/assets/images/emoji/neutral_face.png b/app/assets/images/emoji/neutral_face.png
new file mode 100644
index 00000000000..065d193afe4
--- /dev/null
+++ b/app/assets/images/emoji/neutral_face.png
Binary files differ
diff --git a/app/assets/images/emoji/new.png b/app/assets/images/emoji/new.png
new file mode 100644
index 00000000000..b4f85488d1a
--- /dev/null
+++ b/app/assets/images/emoji/new.png
Binary files differ
diff --git a/app/assets/images/emoji/new_moon.png b/app/assets/images/emoji/new_moon.png
new file mode 100644
index 00000000000..ecff72caa42
--- /dev/null
+++ b/app/assets/images/emoji/new_moon.png
Binary files differ
diff --git a/app/assets/images/emoji/new_moon_with_face.png b/app/assets/images/emoji/new_moon_with_face.png
new file mode 100644
index 00000000000..150dd12400c
--- /dev/null
+++ b/app/assets/images/emoji/new_moon_with_face.png
Binary files differ
diff --git a/app/assets/images/emoji/newspaper.png b/app/assets/images/emoji/newspaper.png
new file mode 100644
index 00000000000..2aa8f060bde
--- /dev/null
+++ b/app/assets/images/emoji/newspaper.png
Binary files differ
diff --git a/app/assets/images/emoji/newspaper2.png b/app/assets/images/emoji/newspaper2.png
new file mode 100644
index 00000000000..f64748df2b2
--- /dev/null
+++ b/app/assets/images/emoji/newspaper2.png
Binary files differ
diff --git a/app/assets/images/emoji/ng.png b/app/assets/images/emoji/ng.png
new file mode 100644
index 00000000000..ee8d20f5ebc
--- /dev/null
+++ b/app/assets/images/emoji/ng.png
Binary files differ
diff --git a/app/assets/images/emoji/night_with_stars.png b/app/assets/images/emoji/night_with_stars.png
new file mode 100644
index 00000000000..ca2018f456d
--- /dev/null
+++ b/app/assets/images/emoji/night_with_stars.png
Binary files differ
diff --git a/app/assets/images/emoji/nine.png b/app/assets/images/emoji/nine.png
new file mode 100644
index 00000000000..9fce3d1eca9
--- /dev/null
+++ b/app/assets/images/emoji/nine.png
Binary files differ
diff --git a/app/assets/images/emoji/no_bell.png b/app/assets/images/emoji/no_bell.png
new file mode 100644
index 00000000000..15cb38dd1e7
--- /dev/null
+++ b/app/assets/images/emoji/no_bell.png
Binary files differ
diff --git a/app/assets/images/emoji/no_bicycles.png b/app/assets/images/emoji/no_bicycles.png
new file mode 100644
index 00000000000..19c85421ce9
--- /dev/null
+++ b/app/assets/images/emoji/no_bicycles.png
Binary files differ
diff --git a/app/assets/images/emoji/no_entry.png b/app/assets/images/emoji/no_entry.png
new file mode 100644
index 00000000000..476800fc5c6
--- /dev/null
+++ b/app/assets/images/emoji/no_entry.png
Binary files differ
diff --git a/app/assets/images/emoji/no_entry_sign.png b/app/assets/images/emoji/no_entry_sign.png
new file mode 100644
index 00000000000..d2efd65e74b
--- /dev/null
+++ b/app/assets/images/emoji/no_entry_sign.png
Binary files differ
diff --git a/app/assets/images/emoji/no_good.png b/app/assets/images/emoji/no_good.png
new file mode 100644
index 00000000000..ed577100322
--- /dev/null
+++ b/app/assets/images/emoji/no_good.png
Binary files differ
diff --git a/app/assets/images/emoji/no_good_tone1.png b/app/assets/images/emoji/no_good_tone1.png
new file mode 100644
index 00000000000..5c1a3cbb884
--- /dev/null
+++ b/app/assets/images/emoji/no_good_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/no_good_tone2.png b/app/assets/images/emoji/no_good_tone2.png
new file mode 100644
index 00000000000..80d8021f8fe
--- /dev/null
+++ b/app/assets/images/emoji/no_good_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/no_good_tone3.png b/app/assets/images/emoji/no_good_tone3.png
new file mode 100644
index 00000000000..635e6a00815
--- /dev/null
+++ b/app/assets/images/emoji/no_good_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/no_good_tone4.png b/app/assets/images/emoji/no_good_tone4.png
new file mode 100644
index 00000000000..b96e412a374
--- /dev/null
+++ b/app/assets/images/emoji/no_good_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/no_good_tone5.png b/app/assets/images/emoji/no_good_tone5.png
new file mode 100644
index 00000000000..9a7084afa0a
--- /dev/null
+++ b/app/assets/images/emoji/no_good_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/no_mobile_phones.png b/app/assets/images/emoji/no_mobile_phones.png
new file mode 100644
index 00000000000..7b1ae6ea579
--- /dev/null
+++ b/app/assets/images/emoji/no_mobile_phones.png
Binary files differ
diff --git a/app/assets/images/emoji/no_mouth.png b/app/assets/images/emoji/no_mouth.png
new file mode 100644
index 00000000000..b642f6c1172
--- /dev/null
+++ b/app/assets/images/emoji/no_mouth.png
Binary files differ
diff --git a/app/assets/images/emoji/no_pedestrians.png b/app/assets/images/emoji/no_pedestrians.png
new file mode 100644
index 00000000000..286aa577a23
--- /dev/null
+++ b/app/assets/images/emoji/no_pedestrians.png
Binary files differ
diff --git a/app/assets/images/emoji/no_smoking.png b/app/assets/images/emoji/no_smoking.png
new file mode 100644
index 00000000000..586b8d29d05
--- /dev/null
+++ b/app/assets/images/emoji/no_smoking.png
Binary files differ
diff --git a/app/assets/images/emoji/non-potable_water.png b/app/assets/images/emoji/non-potable_water.png
new file mode 100644
index 00000000000..827d4193f4e
--- /dev/null
+++ b/app/assets/images/emoji/non-potable_water.png
Binary files differ
diff --git a/app/assets/images/emoji/nose.png b/app/assets/images/emoji/nose.png
new file mode 100644
index 00000000000..2f04ac5f98f
--- /dev/null
+++ b/app/assets/images/emoji/nose.png
Binary files differ
diff --git a/app/assets/images/emoji/nose_tone1.png b/app/assets/images/emoji/nose_tone1.png
new file mode 100644
index 00000000000..8008d17506e
--- /dev/null
+++ b/app/assets/images/emoji/nose_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/nose_tone2.png b/app/assets/images/emoji/nose_tone2.png
new file mode 100644
index 00000000000..ac17f26e827
--- /dev/null
+++ b/app/assets/images/emoji/nose_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/nose_tone3.png b/app/assets/images/emoji/nose_tone3.png
new file mode 100644
index 00000000000..d8b6cbe0f8e
--- /dev/null
+++ b/app/assets/images/emoji/nose_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/nose_tone4.png b/app/assets/images/emoji/nose_tone4.png
new file mode 100644
index 00000000000..004b2631e2e
--- /dev/null
+++ b/app/assets/images/emoji/nose_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/nose_tone5.png b/app/assets/images/emoji/nose_tone5.png
new file mode 100644
index 00000000000..7b33821f6c9
--- /dev/null
+++ b/app/assets/images/emoji/nose_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/notebook.png b/app/assets/images/emoji/notebook.png
new file mode 100644
index 00000000000..f6c28b4915d
--- /dev/null
+++ b/app/assets/images/emoji/notebook.png
Binary files differ
diff --git a/app/assets/images/emoji/notebook_with_decorative_cover.png b/app/assets/images/emoji/notebook_with_decorative_cover.png
new file mode 100644
index 00000000000..03f566b6d2c
--- /dev/null
+++ b/app/assets/images/emoji/notebook_with_decorative_cover.png
Binary files differ
diff --git a/app/assets/images/emoji/notepad_spiral.png b/app/assets/images/emoji/notepad_spiral.png
new file mode 100644
index 00000000000..85faa10d8ea
--- /dev/null
+++ b/app/assets/images/emoji/notepad_spiral.png
Binary files differ
diff --git a/app/assets/images/emoji/notes.png b/app/assets/images/emoji/notes.png
new file mode 100644
index 00000000000..57d499aa181
--- /dev/null
+++ b/app/assets/images/emoji/notes.png
Binary files differ
diff --git a/app/assets/images/emoji/nut_and_bolt.png b/app/assets/images/emoji/nut_and_bolt.png
new file mode 100644
index 00000000000..4b9ae155319
--- /dev/null
+++ b/app/assets/images/emoji/nut_and_bolt.png
Binary files differ
diff --git a/app/assets/images/emoji/o.png b/app/assets/images/emoji/o.png
new file mode 100644
index 00000000000..3fe75ce4675
--- /dev/null
+++ b/app/assets/images/emoji/o.png
Binary files differ
diff --git a/app/assets/images/emoji/o2.png b/app/assets/images/emoji/o2.png
new file mode 100644
index 00000000000..73278ba194a
--- /dev/null
+++ b/app/assets/images/emoji/o2.png
Binary files differ
diff --git a/app/assets/images/emoji/ocean.png b/app/assets/images/emoji/ocean.png
new file mode 100644
index 00000000000..45ff1e87703
--- /dev/null
+++ b/app/assets/images/emoji/ocean.png
Binary files differ
diff --git a/app/assets/images/emoji/octagonal_sign.png b/app/assets/images/emoji/octagonal_sign.png
new file mode 100644
index 00000000000..5ed61004045
--- /dev/null
+++ b/app/assets/images/emoji/octagonal_sign.png
Binary files differ
diff --git a/app/assets/images/emoji/octopus.png b/app/assets/images/emoji/octopus.png
new file mode 100644
index 00000000000..72c84074aac
--- /dev/null
+++ b/app/assets/images/emoji/octopus.png
Binary files differ
diff --git a/app/assets/images/emoji/oden.png b/app/assets/images/emoji/oden.png
new file mode 100644
index 00000000000..d38a849fece
--- /dev/null
+++ b/app/assets/images/emoji/oden.png
Binary files differ
diff --git a/app/assets/images/emoji/office.png b/app/assets/images/emoji/office.png
new file mode 100644
index 00000000000..7eee927d1b0
--- /dev/null
+++ b/app/assets/images/emoji/office.png
Binary files differ
diff --git a/app/assets/images/emoji/oil.png b/app/assets/images/emoji/oil.png
new file mode 100644
index 00000000000..c4c4d42da8b
--- /dev/null
+++ b/app/assets/images/emoji/oil.png
Binary files differ
diff --git a/app/assets/images/emoji/ok.png b/app/assets/images/emoji/ok.png
new file mode 100644
index 00000000000..d0d775532ff
--- /dev/null
+++ b/app/assets/images/emoji/ok.png
Binary files differ
diff --git a/app/assets/images/emoji/ok_hand.png b/app/assets/images/emoji/ok_hand.png
new file mode 100644
index 00000000000..028d69b0de3
--- /dev/null
+++ b/app/assets/images/emoji/ok_hand.png
Binary files differ
diff --git a/app/assets/images/emoji/ok_hand_tone1.png b/app/assets/images/emoji/ok_hand_tone1.png
new file mode 100644
index 00000000000..cecf7b2ab5a
--- /dev/null
+++ b/app/assets/images/emoji/ok_hand_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/ok_hand_tone2.png b/app/assets/images/emoji/ok_hand_tone2.png
new file mode 100644
index 00000000000..c19239bcd3d
--- /dev/null
+++ b/app/assets/images/emoji/ok_hand_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/ok_hand_tone3.png b/app/assets/images/emoji/ok_hand_tone3.png
new file mode 100644
index 00000000000..94b65b03ecd
--- /dev/null
+++ b/app/assets/images/emoji/ok_hand_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/ok_hand_tone4.png b/app/assets/images/emoji/ok_hand_tone4.png
new file mode 100644
index 00000000000..03d26f08e6a
--- /dev/null
+++ b/app/assets/images/emoji/ok_hand_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/ok_hand_tone5.png b/app/assets/images/emoji/ok_hand_tone5.png
new file mode 100644
index 00000000000..d4b24086364
--- /dev/null
+++ b/app/assets/images/emoji/ok_hand_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/ok_woman.png b/app/assets/images/emoji/ok_woman.png
new file mode 100644
index 00000000000..90a2c7469c4
--- /dev/null
+++ b/app/assets/images/emoji/ok_woman.png
Binary files differ
diff --git a/app/assets/images/emoji/ok_woman_tone1.png b/app/assets/images/emoji/ok_woman_tone1.png
new file mode 100644
index 00000000000..c99543e785b
--- /dev/null
+++ b/app/assets/images/emoji/ok_woman_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/ok_woman_tone2.png b/app/assets/images/emoji/ok_woman_tone2.png
new file mode 100644
index 00000000000..ad5fae813db
--- /dev/null
+++ b/app/assets/images/emoji/ok_woman_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/ok_woman_tone3.png b/app/assets/images/emoji/ok_woman_tone3.png
new file mode 100644
index 00000000000..51bf4fab406
--- /dev/null
+++ b/app/assets/images/emoji/ok_woman_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/ok_woman_tone4.png b/app/assets/images/emoji/ok_woman_tone4.png
new file mode 100644
index 00000000000..ee3f9dc640a
--- /dev/null
+++ b/app/assets/images/emoji/ok_woman_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/ok_woman_tone5.png b/app/assets/images/emoji/ok_woman_tone5.png
new file mode 100644
index 00000000000..62a9d9237f7
--- /dev/null
+++ b/app/assets/images/emoji/ok_woman_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/older_man.png b/app/assets/images/emoji/older_man.png
new file mode 100644
index 00000000000..4ace4e6f308
--- /dev/null
+++ b/app/assets/images/emoji/older_man.png
Binary files differ
diff --git a/app/assets/images/emoji/older_man_tone1.png b/app/assets/images/emoji/older_man_tone1.png
new file mode 100644
index 00000000000..ab459baace8
--- /dev/null
+++ b/app/assets/images/emoji/older_man_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/older_man_tone2.png b/app/assets/images/emoji/older_man_tone2.png
new file mode 100644
index 00000000000..f4dfc7694ea
--- /dev/null
+++ b/app/assets/images/emoji/older_man_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/older_man_tone3.png b/app/assets/images/emoji/older_man_tone3.png
new file mode 100644
index 00000000000..5ffd11792f4
--- /dev/null
+++ b/app/assets/images/emoji/older_man_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/older_man_tone4.png b/app/assets/images/emoji/older_man_tone4.png
new file mode 100644
index 00000000000..b350a764bfd
--- /dev/null
+++ b/app/assets/images/emoji/older_man_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/older_man_tone5.png b/app/assets/images/emoji/older_man_tone5.png
new file mode 100644
index 00000000000..05fe24a1708
--- /dev/null
+++ b/app/assets/images/emoji/older_man_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/older_woman.png b/app/assets/images/emoji/older_woman.png
new file mode 100644
index 00000000000..52dc4987143
--- /dev/null
+++ b/app/assets/images/emoji/older_woman.png
Binary files differ
diff --git a/app/assets/images/emoji/older_woman_tone1.png b/app/assets/images/emoji/older_woman_tone1.png
new file mode 100644
index 00000000000..b49e821402c
--- /dev/null
+++ b/app/assets/images/emoji/older_woman_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/older_woman_tone2.png b/app/assets/images/emoji/older_woman_tone2.png
new file mode 100644
index 00000000000..e86bf5ab3b7
--- /dev/null
+++ b/app/assets/images/emoji/older_woman_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/older_woman_tone3.png b/app/assets/images/emoji/older_woman_tone3.png
new file mode 100644
index 00000000000..83fc14b0874
--- /dev/null
+++ b/app/assets/images/emoji/older_woman_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/older_woman_tone4.png b/app/assets/images/emoji/older_woman_tone4.png
new file mode 100644
index 00000000000..e4aa8a424d4
--- /dev/null
+++ b/app/assets/images/emoji/older_woman_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/older_woman_tone5.png b/app/assets/images/emoji/older_woman_tone5.png
new file mode 100644
index 00000000000..4009012bb0a
--- /dev/null
+++ b/app/assets/images/emoji/older_woman_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/om_symbol.png b/app/assets/images/emoji/om_symbol.png
new file mode 100644
index 00000000000..a35c63c459c
--- /dev/null
+++ b/app/assets/images/emoji/om_symbol.png
Binary files differ
diff --git a/app/assets/images/emoji/on.png b/app/assets/images/emoji/on.png
new file mode 100644
index 00000000000..a0c371ae21e
--- /dev/null
+++ b/app/assets/images/emoji/on.png
Binary files differ
diff --git a/app/assets/images/emoji/oncoming_automobile.png b/app/assets/images/emoji/oncoming_automobile.png
new file mode 100644
index 00000000000..3c7e1d52e63
--- /dev/null
+++ b/app/assets/images/emoji/oncoming_automobile.png
Binary files differ
diff --git a/app/assets/images/emoji/oncoming_bus.png b/app/assets/images/emoji/oncoming_bus.png
new file mode 100644
index 00000000000..ad91e256c7f
--- /dev/null
+++ b/app/assets/images/emoji/oncoming_bus.png
Binary files differ
diff --git a/app/assets/images/emoji/oncoming_police_car.png b/app/assets/images/emoji/oncoming_police_car.png
new file mode 100644
index 00000000000..c9109c85b5d
--- /dev/null
+++ b/app/assets/images/emoji/oncoming_police_car.png
Binary files differ
diff --git a/app/assets/images/emoji/oncoming_taxi.png b/app/assets/images/emoji/oncoming_taxi.png
new file mode 100644
index 00000000000..fea14e45846
--- /dev/null
+++ b/app/assets/images/emoji/oncoming_taxi.png
Binary files differ
diff --git a/app/assets/images/emoji/one.png b/app/assets/images/emoji/one.png
new file mode 100644
index 00000000000..e6d84b80128
--- /dev/null
+++ b/app/assets/images/emoji/one.png
Binary files differ
diff --git a/app/assets/images/emoji/open_file_folder.png b/app/assets/images/emoji/open_file_folder.png
new file mode 100644
index 00000000000..3993b09222f
--- /dev/null
+++ b/app/assets/images/emoji/open_file_folder.png
Binary files differ
diff --git a/app/assets/images/emoji/open_hands.png b/app/assets/images/emoji/open_hands.png
new file mode 100644
index 00000000000..1cf75c9101e
--- /dev/null
+++ b/app/assets/images/emoji/open_hands.png
Binary files differ
diff --git a/app/assets/images/emoji/open_hands_tone1.png b/app/assets/images/emoji/open_hands_tone1.png
new file mode 100644
index 00000000000..352d2614f11
--- /dev/null
+++ b/app/assets/images/emoji/open_hands_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/open_hands_tone2.png b/app/assets/images/emoji/open_hands_tone2.png
new file mode 100644
index 00000000000..70824a50c73
--- /dev/null
+++ b/app/assets/images/emoji/open_hands_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/open_hands_tone3.png b/app/assets/images/emoji/open_hands_tone3.png
new file mode 100644
index 00000000000..d7d136bd3db
--- /dev/null
+++ b/app/assets/images/emoji/open_hands_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/open_hands_tone4.png b/app/assets/images/emoji/open_hands_tone4.png
new file mode 100644
index 00000000000..df4eaa711e7
--- /dev/null
+++ b/app/assets/images/emoji/open_hands_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/open_hands_tone5.png b/app/assets/images/emoji/open_hands_tone5.png
new file mode 100644
index 00000000000..7dc04eaebd8
--- /dev/null
+++ b/app/assets/images/emoji/open_hands_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/open_mouth.png b/app/assets/images/emoji/open_mouth.png
new file mode 100644
index 00000000000..a62cd27e148
--- /dev/null
+++ b/app/assets/images/emoji/open_mouth.png
Binary files differ
diff --git a/app/assets/images/emoji/ophiuchus.png b/app/assets/images/emoji/ophiuchus.png
new file mode 100644
index 00000000000..0a780a700da
--- /dev/null
+++ b/app/assets/images/emoji/ophiuchus.png
Binary files differ
diff --git a/app/assets/images/emoji/orange_book.png b/app/assets/images/emoji/orange_book.png
new file mode 100644
index 00000000000..ab40e6ae6a2
--- /dev/null
+++ b/app/assets/images/emoji/orange_book.png
Binary files differ
diff --git a/app/assets/images/emoji/orthodox_cross.png b/app/assets/images/emoji/orthodox_cross.png
new file mode 100644
index 00000000000..0530e33a4d4
--- /dev/null
+++ b/app/assets/images/emoji/orthodox_cross.png
Binary files differ
diff --git a/app/assets/images/emoji/outbox_tray.png b/app/assets/images/emoji/outbox_tray.png
new file mode 100644
index 00000000000..46493ed5b2c
--- /dev/null
+++ b/app/assets/images/emoji/outbox_tray.png
Binary files differ
diff --git a/app/assets/images/emoji/owl.png b/app/assets/images/emoji/owl.png
new file mode 100644
index 00000000000..fa6815480c3
--- /dev/null
+++ b/app/assets/images/emoji/owl.png
Binary files differ
diff --git a/app/assets/images/emoji/ox.png b/app/assets/images/emoji/ox.png
new file mode 100644
index 00000000000..badf5708f2f
--- /dev/null
+++ b/app/assets/images/emoji/ox.png
Binary files differ
diff --git a/app/assets/images/emoji/package.png b/app/assets/images/emoji/package.png
new file mode 100644
index 00000000000..85431756ad8
--- /dev/null
+++ b/app/assets/images/emoji/package.png
Binary files differ
diff --git a/app/assets/images/emoji/page_facing_up.png b/app/assets/images/emoji/page_facing_up.png
new file mode 100644
index 00000000000..ba4ed757e01
--- /dev/null
+++ b/app/assets/images/emoji/page_facing_up.png
Binary files differ
diff --git a/app/assets/images/emoji/page_with_curl.png b/app/assets/images/emoji/page_with_curl.png
new file mode 100644
index 00000000000..06355319c74
--- /dev/null
+++ b/app/assets/images/emoji/page_with_curl.png
Binary files differ
diff --git a/app/assets/images/emoji/pager.png b/app/assets/images/emoji/pager.png
new file mode 100644
index 00000000000..b24b99306a2
--- /dev/null
+++ b/app/assets/images/emoji/pager.png
Binary files differ
diff --git a/app/assets/images/emoji/paintbrush.png b/app/assets/images/emoji/paintbrush.png
new file mode 100644
index 00000000000..28bffbaa3c9
--- /dev/null
+++ b/app/assets/images/emoji/paintbrush.png
Binary files differ
diff --git a/app/assets/images/emoji/palm_tree.png b/app/assets/images/emoji/palm_tree.png
new file mode 100644
index 00000000000..4bbb10f4f19
--- /dev/null
+++ b/app/assets/images/emoji/palm_tree.png
Binary files differ
diff --git a/app/assets/images/emoji/pancakes.png b/app/assets/images/emoji/pancakes.png
new file mode 100644
index 00000000000..6223d1a28e9
--- /dev/null
+++ b/app/assets/images/emoji/pancakes.png
Binary files differ
diff --git a/app/assets/images/emoji/panda_face.png b/app/assets/images/emoji/panda_face.png
new file mode 100644
index 00000000000..978382775ce
--- /dev/null
+++ b/app/assets/images/emoji/panda_face.png
Binary files differ
diff --git a/app/assets/images/emoji/paperclip.png b/app/assets/images/emoji/paperclip.png
new file mode 100644
index 00000000000..8cd8d4f8750
--- /dev/null
+++ b/app/assets/images/emoji/paperclip.png
Binary files differ
diff --git a/app/assets/images/emoji/paperclips.png b/app/assets/images/emoji/paperclips.png
new file mode 100644
index 00000000000..76021e8c705
--- /dev/null
+++ b/app/assets/images/emoji/paperclips.png
Binary files differ
diff --git a/app/assets/images/emoji/park.png b/app/assets/images/emoji/park.png
new file mode 100644
index 00000000000..63ec7016301
--- /dev/null
+++ b/app/assets/images/emoji/park.png
Binary files differ
diff --git a/app/assets/images/emoji/parking.png b/app/assets/images/emoji/parking.png
new file mode 100644
index 00000000000..7be7dac27e8
--- /dev/null
+++ b/app/assets/images/emoji/parking.png
Binary files differ
diff --git a/app/assets/images/emoji/part_alternation_mark.png b/app/assets/images/emoji/part_alternation_mark.png
new file mode 100644
index 00000000000..70453d41528
--- /dev/null
+++ b/app/assets/images/emoji/part_alternation_mark.png
Binary files differ
diff --git a/app/assets/images/emoji/partly_sunny.png b/app/assets/images/emoji/partly_sunny.png
new file mode 100644
index 00000000000..a55e59c344c
--- /dev/null
+++ b/app/assets/images/emoji/partly_sunny.png
Binary files differ
diff --git a/app/assets/images/emoji/passport_control.png b/app/assets/images/emoji/passport_control.png
new file mode 100644
index 00000000000..079e34ee4d4
--- /dev/null
+++ b/app/assets/images/emoji/passport_control.png
Binary files differ
diff --git a/app/assets/images/emoji/pause_button.png b/app/assets/images/emoji/pause_button.png
new file mode 100644
index 00000000000..4f07e7ebfd7
--- /dev/null
+++ b/app/assets/images/emoji/pause_button.png
Binary files differ
diff --git a/app/assets/images/emoji/peace.png b/app/assets/images/emoji/peace.png
new file mode 100644
index 00000000000..86033faf477
--- /dev/null
+++ b/app/assets/images/emoji/peace.png
Binary files differ
diff --git a/app/assets/images/emoji/peach.png b/app/assets/images/emoji/peach.png
new file mode 100644
index 00000000000..9ab57cbb758
--- /dev/null
+++ b/app/assets/images/emoji/peach.png
Binary files differ
diff --git a/app/assets/images/emoji/peanuts.png b/app/assets/images/emoji/peanuts.png
new file mode 100644
index 00000000000..b64fadad010
--- /dev/null
+++ b/app/assets/images/emoji/peanuts.png
Binary files differ
diff --git a/app/assets/images/emoji/pear.png b/app/assets/images/emoji/pear.png
new file mode 100644
index 00000000000..3869f718bcf
--- /dev/null
+++ b/app/assets/images/emoji/pear.png
Binary files differ
diff --git a/app/assets/images/emoji/pen_ballpoint.png b/app/assets/images/emoji/pen_ballpoint.png
new file mode 100644
index 00000000000..6ef7a342433
--- /dev/null
+++ b/app/assets/images/emoji/pen_ballpoint.png
Binary files differ
diff --git a/app/assets/images/emoji/pen_fountain.png b/app/assets/images/emoji/pen_fountain.png
new file mode 100644
index 00000000000..3ca4bd2c231
--- /dev/null
+++ b/app/assets/images/emoji/pen_fountain.png
Binary files differ
diff --git a/app/assets/images/emoji/pencil.png b/app/assets/images/emoji/pencil.png
new file mode 100644
index 00000000000..edc6155e168
--- /dev/null
+++ b/app/assets/images/emoji/pencil.png
Binary files differ
diff --git a/app/assets/images/emoji/pencil2.png b/app/assets/images/emoji/pencil2.png
new file mode 100644
index 00000000000..3833d590fa2
--- /dev/null
+++ b/app/assets/images/emoji/pencil2.png
Binary files differ
diff --git a/app/assets/images/emoji/penguin.png b/app/assets/images/emoji/penguin.png
new file mode 100644
index 00000000000..c0064fb9734
--- /dev/null
+++ b/app/assets/images/emoji/penguin.png
Binary files differ
diff --git a/app/assets/images/emoji/pensive.png b/app/assets/images/emoji/pensive.png
new file mode 100644
index 00000000000..490fb566954
--- /dev/null
+++ b/app/assets/images/emoji/pensive.png
Binary files differ
diff --git a/app/assets/images/emoji/performing_arts.png b/app/assets/images/emoji/performing_arts.png
new file mode 100644
index 00000000000..685441fdaa1
--- /dev/null
+++ b/app/assets/images/emoji/performing_arts.png
Binary files differ
diff --git a/app/assets/images/emoji/persevere.png b/app/assets/images/emoji/persevere.png
new file mode 100644
index 00000000000..646a05fe908
--- /dev/null
+++ b/app/assets/images/emoji/persevere.png
Binary files differ
diff --git a/app/assets/images/emoji/person_frowning.png b/app/assets/images/emoji/person_frowning.png
new file mode 100644
index 00000000000..579324959a1
--- /dev/null
+++ b/app/assets/images/emoji/person_frowning.png
Binary files differ
diff --git a/app/assets/images/emoji/person_frowning_tone1.png b/app/assets/images/emoji/person_frowning_tone1.png
new file mode 100644
index 00000000000..21d3bb43923
--- /dev/null
+++ b/app/assets/images/emoji/person_frowning_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/person_frowning_tone2.png b/app/assets/images/emoji/person_frowning_tone2.png
new file mode 100644
index 00000000000..973f5fc8382
--- /dev/null
+++ b/app/assets/images/emoji/person_frowning_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/person_frowning_tone3.png b/app/assets/images/emoji/person_frowning_tone3.png
new file mode 100644
index 00000000000..41fbcc78816
--- /dev/null
+++ b/app/assets/images/emoji/person_frowning_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/person_frowning_tone4.png b/app/assets/images/emoji/person_frowning_tone4.png
new file mode 100644
index 00000000000..5a37c741030
--- /dev/null
+++ b/app/assets/images/emoji/person_frowning_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/person_frowning_tone5.png b/app/assets/images/emoji/person_frowning_tone5.png
new file mode 100644
index 00000000000..e08141f3efe
--- /dev/null
+++ b/app/assets/images/emoji/person_frowning_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/person_with_blond_hair.png b/app/assets/images/emoji/person_with_blond_hair.png
new file mode 100644
index 00000000000..ad6f01a7dda
--- /dev/null
+++ b/app/assets/images/emoji/person_with_blond_hair.png
Binary files differ
diff --git a/app/assets/images/emoji/person_with_blond_hair_tone1.png b/app/assets/images/emoji/person_with_blond_hair_tone1.png
new file mode 100644
index 00000000000..7d18ef24445
--- /dev/null
+++ b/app/assets/images/emoji/person_with_blond_hair_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/person_with_blond_hair_tone2.png b/app/assets/images/emoji/person_with_blond_hair_tone2.png
new file mode 100644
index 00000000000..dae1307315c
--- /dev/null
+++ b/app/assets/images/emoji/person_with_blond_hair_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/person_with_blond_hair_tone3.png b/app/assets/images/emoji/person_with_blond_hair_tone3.png
new file mode 100644
index 00000000000..684677e8e5a
--- /dev/null
+++ b/app/assets/images/emoji/person_with_blond_hair_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/person_with_blond_hair_tone4.png b/app/assets/images/emoji/person_with_blond_hair_tone4.png
new file mode 100644
index 00000000000..012be0b51f8
--- /dev/null
+++ b/app/assets/images/emoji/person_with_blond_hair_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/person_with_blond_hair_tone5.png b/app/assets/images/emoji/person_with_blond_hair_tone5.png
new file mode 100644
index 00000000000..d4ecc4cf44b
--- /dev/null
+++ b/app/assets/images/emoji/person_with_blond_hair_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/person_with_pouting_face.png b/app/assets/images/emoji/person_with_pouting_face.png
new file mode 100644
index 00000000000..10eb0571078
--- /dev/null
+++ b/app/assets/images/emoji/person_with_pouting_face.png
Binary files differ
diff --git a/app/assets/images/emoji/person_with_pouting_face_tone1.png b/app/assets/images/emoji/person_with_pouting_face_tone1.png
new file mode 100644
index 00000000000..57e826b75a4
--- /dev/null
+++ b/app/assets/images/emoji/person_with_pouting_face_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/person_with_pouting_face_tone2.png b/app/assets/images/emoji/person_with_pouting_face_tone2.png
new file mode 100644
index 00000000000..3f317c0c25f
--- /dev/null
+++ b/app/assets/images/emoji/person_with_pouting_face_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/person_with_pouting_face_tone3.png b/app/assets/images/emoji/person_with_pouting_face_tone3.png
new file mode 100644
index 00000000000..d2fbb6c20bf
--- /dev/null
+++ b/app/assets/images/emoji/person_with_pouting_face_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/person_with_pouting_face_tone4.png b/app/assets/images/emoji/person_with_pouting_face_tone4.png
new file mode 100644
index 00000000000..643ceb4a5c5
--- /dev/null
+++ b/app/assets/images/emoji/person_with_pouting_face_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/person_with_pouting_face_tone5.png b/app/assets/images/emoji/person_with_pouting_face_tone5.png
new file mode 100644
index 00000000000..b2eb6859c32
--- /dev/null
+++ b/app/assets/images/emoji/person_with_pouting_face_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/pick.png b/app/assets/images/emoji/pick.png
new file mode 100644
index 00000000000..6370fe6d791
--- /dev/null
+++ b/app/assets/images/emoji/pick.png
Binary files differ
diff --git a/app/assets/images/emoji/pig.png b/app/assets/images/emoji/pig.png
new file mode 100644
index 00000000000..afe05ca1676
--- /dev/null
+++ b/app/assets/images/emoji/pig.png
Binary files differ
diff --git a/app/assets/images/emoji/pig2.png b/app/assets/images/emoji/pig2.png
new file mode 100644
index 00000000000..5f31c1a2d75
--- /dev/null
+++ b/app/assets/images/emoji/pig2.png
Binary files differ
diff --git a/app/assets/images/emoji/pig_nose.png b/app/assets/images/emoji/pig_nose.png
new file mode 100644
index 00000000000..3610ae4a910
--- /dev/null
+++ b/app/assets/images/emoji/pig_nose.png
Binary files differ
diff --git a/app/assets/images/emoji/pill.png b/app/assets/images/emoji/pill.png
new file mode 100644
index 00000000000..1d4530e77a3
--- /dev/null
+++ b/app/assets/images/emoji/pill.png
Binary files differ
diff --git a/app/assets/images/emoji/pineapple.png b/app/assets/images/emoji/pineapple.png
new file mode 100644
index 00000000000..c89a1606462
--- /dev/null
+++ b/app/assets/images/emoji/pineapple.png
Binary files differ
diff --git a/app/assets/images/emoji/ping_pong.png b/app/assets/images/emoji/ping_pong.png
new file mode 100644
index 00000000000..ff3c51727d1
--- /dev/null
+++ b/app/assets/images/emoji/ping_pong.png
Binary files differ
diff --git a/app/assets/images/emoji/pisces.png b/app/assets/images/emoji/pisces.png
new file mode 100644
index 00000000000..7f6f646a95c
--- /dev/null
+++ b/app/assets/images/emoji/pisces.png
Binary files differ
diff --git a/app/assets/images/emoji/pizza.png b/app/assets/images/emoji/pizza.png
new file mode 100644
index 00000000000..e07365cb398
--- /dev/null
+++ b/app/assets/images/emoji/pizza.png
Binary files differ
diff --git a/app/assets/images/emoji/place_of_worship.png b/app/assets/images/emoji/place_of_worship.png
new file mode 100644
index 00000000000..207d59cce85
--- /dev/null
+++ b/app/assets/images/emoji/place_of_worship.png
Binary files differ
diff --git a/app/assets/images/emoji/play_pause.png b/app/assets/images/emoji/play_pause.png
new file mode 100644
index 00000000000..a9f857139ac
--- /dev/null
+++ b/app/assets/images/emoji/play_pause.png
Binary files differ
diff --git a/app/assets/images/emoji/point_down.png b/app/assets/images/emoji/point_down.png
new file mode 100644
index 00000000000..00d3d13ab5c
--- /dev/null
+++ b/app/assets/images/emoji/point_down.png
Binary files differ
diff --git a/app/assets/images/emoji/point_down_tone1.png b/app/assets/images/emoji/point_down_tone1.png
new file mode 100644
index 00000000000..140f157d8c7
--- /dev/null
+++ b/app/assets/images/emoji/point_down_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/point_down_tone2.png b/app/assets/images/emoji/point_down_tone2.png
new file mode 100644
index 00000000000..d518544f7fa
--- /dev/null
+++ b/app/assets/images/emoji/point_down_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/point_down_tone3.png b/app/assets/images/emoji/point_down_tone3.png
new file mode 100644
index 00000000000..018b688b8b7
--- /dev/null
+++ b/app/assets/images/emoji/point_down_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/point_down_tone4.png b/app/assets/images/emoji/point_down_tone4.png
new file mode 100644
index 00000000000..98845bf6f72
--- /dev/null
+++ b/app/assets/images/emoji/point_down_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/point_down_tone5.png b/app/assets/images/emoji/point_down_tone5.png
new file mode 100644
index 00000000000..9a9b039a9fc
--- /dev/null
+++ b/app/assets/images/emoji/point_down_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/point_left.png b/app/assets/images/emoji/point_left.png
new file mode 100644
index 00000000000..599fa2e3cf1
--- /dev/null
+++ b/app/assets/images/emoji/point_left.png
Binary files differ
diff --git a/app/assets/images/emoji/point_left_tone1.png b/app/assets/images/emoji/point_left_tone1.png
new file mode 100644
index 00000000000..88e2c306076
--- /dev/null
+++ b/app/assets/images/emoji/point_left_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/point_left_tone2.png b/app/assets/images/emoji/point_left_tone2.png
new file mode 100644
index 00000000000..d3c89d87c5f
--- /dev/null
+++ b/app/assets/images/emoji/point_left_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/point_left_tone3.png b/app/assets/images/emoji/point_left_tone3.png
new file mode 100644
index 00000000000..b23b9167358
--- /dev/null
+++ b/app/assets/images/emoji/point_left_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/point_left_tone4.png b/app/assets/images/emoji/point_left_tone4.png
new file mode 100644
index 00000000000..3093f325c27
--- /dev/null
+++ b/app/assets/images/emoji/point_left_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/point_left_tone5.png b/app/assets/images/emoji/point_left_tone5.png
new file mode 100644
index 00000000000..2b4cbfa120c
--- /dev/null
+++ b/app/assets/images/emoji/point_left_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/point_right.png b/app/assets/images/emoji/point_right.png
new file mode 100644
index 00000000000..93a3cd34aa5
--- /dev/null
+++ b/app/assets/images/emoji/point_right.png
Binary files differ
diff --git a/app/assets/images/emoji/point_right_tone1.png b/app/assets/images/emoji/point_right_tone1.png
new file mode 100644
index 00000000000..4a28c6bbc89
--- /dev/null
+++ b/app/assets/images/emoji/point_right_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/point_right_tone2.png b/app/assets/images/emoji/point_right_tone2.png
new file mode 100644
index 00000000000..7cb13231733
--- /dev/null
+++ b/app/assets/images/emoji/point_right_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/point_right_tone3.png b/app/assets/images/emoji/point_right_tone3.png
new file mode 100644
index 00000000000..5514807d71a
--- /dev/null
+++ b/app/assets/images/emoji/point_right_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/point_right_tone4.png b/app/assets/images/emoji/point_right_tone4.png
new file mode 100644
index 00000000000..b8541d6440d
--- /dev/null
+++ b/app/assets/images/emoji/point_right_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/point_right_tone5.png b/app/assets/images/emoji/point_right_tone5.png
new file mode 100644
index 00000000000..1b7aab07bb1
--- /dev/null
+++ b/app/assets/images/emoji/point_right_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/point_up.png b/app/assets/images/emoji/point_up.png
new file mode 100644
index 00000000000..f4978ff0f00
--- /dev/null
+++ b/app/assets/images/emoji/point_up.png
Binary files differ
diff --git a/app/assets/images/emoji/point_up_2.png b/app/assets/images/emoji/point_up_2.png
new file mode 100644
index 00000000000..bc496dfeae4
--- /dev/null
+++ b/app/assets/images/emoji/point_up_2.png
Binary files differ
diff --git a/app/assets/images/emoji/point_up_2_tone1.png b/app/assets/images/emoji/point_up_2_tone1.png
new file mode 100644
index 00000000000..a12a7e78430
--- /dev/null
+++ b/app/assets/images/emoji/point_up_2_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/point_up_2_tone2.png b/app/assets/images/emoji/point_up_2_tone2.png
new file mode 100644
index 00000000000..cdff40ceab0
--- /dev/null
+++ b/app/assets/images/emoji/point_up_2_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/point_up_2_tone3.png b/app/assets/images/emoji/point_up_2_tone3.png
new file mode 100644
index 00000000000..a07ce9e5ae8
--- /dev/null
+++ b/app/assets/images/emoji/point_up_2_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/point_up_2_tone4.png b/app/assets/images/emoji/point_up_2_tone4.png
new file mode 100644
index 00000000000..4f86c88ba42
--- /dev/null
+++ b/app/assets/images/emoji/point_up_2_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/point_up_2_tone5.png b/app/assets/images/emoji/point_up_2_tone5.png
new file mode 100644
index 00000000000..ed1b26c35d3
--- /dev/null
+++ b/app/assets/images/emoji/point_up_2_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/point_up_tone1.png b/app/assets/images/emoji/point_up_tone1.png
new file mode 100644
index 00000000000..6a9db21d64c
--- /dev/null
+++ b/app/assets/images/emoji/point_up_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/point_up_tone2.png b/app/assets/images/emoji/point_up_tone2.png
new file mode 100644
index 00000000000..15aa9ea0e05
--- /dev/null
+++ b/app/assets/images/emoji/point_up_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/point_up_tone3.png b/app/assets/images/emoji/point_up_tone3.png
new file mode 100644
index 00000000000..652b73a9c5d
--- /dev/null
+++ b/app/assets/images/emoji/point_up_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/point_up_tone4.png b/app/assets/images/emoji/point_up_tone4.png
new file mode 100644
index 00000000000..692bad926e9
--- /dev/null
+++ b/app/assets/images/emoji/point_up_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/point_up_tone5.png b/app/assets/images/emoji/point_up_tone5.png
new file mode 100644
index 00000000000..1e1b10fb71c
--- /dev/null
+++ b/app/assets/images/emoji/point_up_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/police_car.png b/app/assets/images/emoji/police_car.png
new file mode 100644
index 00000000000..3da4253de7e
--- /dev/null
+++ b/app/assets/images/emoji/police_car.png
Binary files differ
diff --git a/app/assets/images/emoji/poodle.png b/app/assets/images/emoji/poodle.png
new file mode 100644
index 00000000000..8ec39e396af
--- /dev/null
+++ b/app/assets/images/emoji/poodle.png
Binary files differ
diff --git a/app/assets/images/emoji/poop.png b/app/assets/images/emoji/poop.png
new file mode 100644
index 00000000000..10b15e72d56
--- /dev/null
+++ b/app/assets/images/emoji/poop.png
Binary files differ
diff --git a/app/assets/images/emoji/popcorn.png b/app/assets/images/emoji/popcorn.png
new file mode 100644
index 00000000000..36853e381d4
--- /dev/null
+++ b/app/assets/images/emoji/popcorn.png
Binary files differ
diff --git a/app/assets/images/emoji/post_office.png b/app/assets/images/emoji/post_office.png
new file mode 100644
index 00000000000..a23848f9aa0
--- /dev/null
+++ b/app/assets/images/emoji/post_office.png
Binary files differ
diff --git a/app/assets/images/emoji/postal_horn.png b/app/assets/images/emoji/postal_horn.png
new file mode 100644
index 00000000000..c173b8dbd67
--- /dev/null
+++ b/app/assets/images/emoji/postal_horn.png
Binary files differ
diff --git a/app/assets/images/emoji/postbox.png b/app/assets/images/emoji/postbox.png
new file mode 100644
index 00000000000..07c9c4ab3d6
--- /dev/null
+++ b/app/assets/images/emoji/postbox.png
Binary files differ
diff --git a/app/assets/images/emoji/potable_water.png b/app/assets/images/emoji/potable_water.png
new file mode 100644
index 00000000000..2c610049459
--- /dev/null
+++ b/app/assets/images/emoji/potable_water.png
Binary files differ
diff --git a/app/assets/images/emoji/potato.png b/app/assets/images/emoji/potato.png
new file mode 100644
index 00000000000..70350ca2c0a
--- /dev/null
+++ b/app/assets/images/emoji/potato.png
Binary files differ
diff --git a/app/assets/images/emoji/pouch.png b/app/assets/images/emoji/pouch.png
new file mode 100644
index 00000000000..8795c6c66ff
--- /dev/null
+++ b/app/assets/images/emoji/pouch.png
Binary files differ
diff --git a/app/assets/images/emoji/poultry_leg.png b/app/assets/images/emoji/poultry_leg.png
new file mode 100644
index 00000000000..eea4a53a2f9
--- /dev/null
+++ b/app/assets/images/emoji/poultry_leg.png
Binary files differ
diff --git a/app/assets/images/emoji/pound.png b/app/assets/images/emoji/pound.png
new file mode 100644
index 00000000000..a0d4c4099e9
--- /dev/null
+++ b/app/assets/images/emoji/pound.png
Binary files differ
diff --git a/app/assets/images/emoji/pouting_cat.png b/app/assets/images/emoji/pouting_cat.png
new file mode 100644
index 00000000000..41ddfeab42b
--- /dev/null
+++ b/app/assets/images/emoji/pouting_cat.png
Binary files differ
diff --git a/app/assets/images/emoji/pray.png b/app/assets/images/emoji/pray.png
new file mode 100644
index 00000000000..8347f2435be
--- /dev/null
+++ b/app/assets/images/emoji/pray.png
Binary files differ
diff --git a/app/assets/images/emoji/pray_tone1.png b/app/assets/images/emoji/pray_tone1.png
new file mode 100644
index 00000000000..060ef257172
--- /dev/null
+++ b/app/assets/images/emoji/pray_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/pray_tone2.png b/app/assets/images/emoji/pray_tone2.png
new file mode 100644
index 00000000000..56dc607c07a
--- /dev/null
+++ b/app/assets/images/emoji/pray_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/pray_tone3.png b/app/assets/images/emoji/pray_tone3.png
new file mode 100644
index 00000000000..0f33b862008
--- /dev/null
+++ b/app/assets/images/emoji/pray_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/pray_tone4.png b/app/assets/images/emoji/pray_tone4.png
new file mode 100644
index 00000000000..2ea8dc11657
--- /dev/null
+++ b/app/assets/images/emoji/pray_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/pray_tone5.png b/app/assets/images/emoji/pray_tone5.png
new file mode 100644
index 00000000000..2128a6c4703
--- /dev/null
+++ b/app/assets/images/emoji/pray_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/prayer_beads.png b/app/assets/images/emoji/prayer_beads.png
new file mode 100644
index 00000000000..a4b6dfcc62e
--- /dev/null
+++ b/app/assets/images/emoji/prayer_beads.png
Binary files differ
diff --git a/app/assets/images/emoji/pregnant_woman.png b/app/assets/images/emoji/pregnant_woman.png
new file mode 100644
index 00000000000..084e83a414a
--- /dev/null
+++ b/app/assets/images/emoji/pregnant_woman.png
Binary files differ
diff --git a/app/assets/images/emoji/pregnant_woman_tone1.png b/app/assets/images/emoji/pregnant_woman_tone1.png
new file mode 100644
index 00000000000..a78703b33aa
--- /dev/null
+++ b/app/assets/images/emoji/pregnant_woman_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/pregnant_woman_tone2.png b/app/assets/images/emoji/pregnant_woman_tone2.png
new file mode 100644
index 00000000000..0068c6c4a77
--- /dev/null
+++ b/app/assets/images/emoji/pregnant_woman_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/pregnant_woman_tone3.png b/app/assets/images/emoji/pregnant_woman_tone3.png
new file mode 100644
index 00000000000..3206296b684
--- /dev/null
+++ b/app/assets/images/emoji/pregnant_woman_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/pregnant_woman_tone4.png b/app/assets/images/emoji/pregnant_woman_tone4.png
new file mode 100644
index 00000000000..120fda5cd8c
--- /dev/null
+++ b/app/assets/images/emoji/pregnant_woman_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/pregnant_woman_tone5.png b/app/assets/images/emoji/pregnant_woman_tone5.png
new file mode 100644
index 00000000000..569bfdf05ce
--- /dev/null
+++ b/app/assets/images/emoji/pregnant_woman_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/prince.png b/app/assets/images/emoji/prince.png
new file mode 100644
index 00000000000..38d69344c84
--- /dev/null
+++ b/app/assets/images/emoji/prince.png
Binary files differ
diff --git a/app/assets/images/emoji/prince_tone1.png b/app/assets/images/emoji/prince_tone1.png
new file mode 100644
index 00000000000..849930c8887
--- /dev/null
+++ b/app/assets/images/emoji/prince_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/prince_tone2.png b/app/assets/images/emoji/prince_tone2.png
new file mode 100644
index 00000000000..23d8b3b1285
--- /dev/null
+++ b/app/assets/images/emoji/prince_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/prince_tone3.png b/app/assets/images/emoji/prince_tone3.png
new file mode 100644
index 00000000000..db6dfff0647
--- /dev/null
+++ b/app/assets/images/emoji/prince_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/prince_tone4.png b/app/assets/images/emoji/prince_tone4.png
new file mode 100644
index 00000000000..8e10f8be6a8
--- /dev/null
+++ b/app/assets/images/emoji/prince_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/prince_tone5.png b/app/assets/images/emoji/prince_tone5.png
new file mode 100644
index 00000000000..138d4ea7048
--- /dev/null
+++ b/app/assets/images/emoji/prince_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/princess.png b/app/assets/images/emoji/princess.png
new file mode 100644
index 00000000000..879e9fa8c5d
--- /dev/null
+++ b/app/assets/images/emoji/princess.png
Binary files differ
diff --git a/app/assets/images/emoji/princess_tone1.png b/app/assets/images/emoji/princess_tone1.png
new file mode 100644
index 00000000000..c28078cdc36
--- /dev/null
+++ b/app/assets/images/emoji/princess_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/princess_tone2.png b/app/assets/images/emoji/princess_tone2.png
new file mode 100644
index 00000000000..dcd20e6ecd4
--- /dev/null
+++ b/app/assets/images/emoji/princess_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/princess_tone3.png b/app/assets/images/emoji/princess_tone3.png
new file mode 100644
index 00000000000..cde6f315c56
--- /dev/null
+++ b/app/assets/images/emoji/princess_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/princess_tone4.png b/app/assets/images/emoji/princess_tone4.png
new file mode 100644
index 00000000000..c71e69caaef
--- /dev/null
+++ b/app/assets/images/emoji/princess_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/princess_tone5.png b/app/assets/images/emoji/princess_tone5.png
new file mode 100644
index 00000000000..063e2645910
--- /dev/null
+++ b/app/assets/images/emoji/princess_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/printer.png b/app/assets/images/emoji/printer.png
new file mode 100644
index 00000000000..027c830f0fe
--- /dev/null
+++ b/app/assets/images/emoji/printer.png
Binary files differ
diff --git a/app/assets/images/emoji/projector.png b/app/assets/images/emoji/projector.png
new file mode 100644
index 00000000000..ce9ab0daa28
--- /dev/null
+++ b/app/assets/images/emoji/projector.png
Binary files differ
diff --git a/app/assets/images/emoji/punch.png b/app/assets/images/emoji/punch.png
new file mode 100644
index 00000000000..b14ca5f5211
--- /dev/null
+++ b/app/assets/images/emoji/punch.png
Binary files differ
diff --git a/app/assets/images/emoji/punch_tone1.png b/app/assets/images/emoji/punch_tone1.png
new file mode 100644
index 00000000000..93c7d17fb47
--- /dev/null
+++ b/app/assets/images/emoji/punch_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/punch_tone2.png b/app/assets/images/emoji/punch_tone2.png
new file mode 100644
index 00000000000..c0a1af6e10a
--- /dev/null
+++ b/app/assets/images/emoji/punch_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/punch_tone3.png b/app/assets/images/emoji/punch_tone3.png
new file mode 100644
index 00000000000..1458b021201
--- /dev/null
+++ b/app/assets/images/emoji/punch_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/punch_tone4.png b/app/assets/images/emoji/punch_tone4.png
new file mode 100644
index 00000000000..c1466bfcdef
--- /dev/null
+++ b/app/assets/images/emoji/punch_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/punch_tone5.png b/app/assets/images/emoji/punch_tone5.png
new file mode 100644
index 00000000000..00b4ddb8953
--- /dev/null
+++ b/app/assets/images/emoji/punch_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/purple_heart.png b/app/assets/images/emoji/purple_heart.png
new file mode 100644
index 00000000000..95c53a9ade6
--- /dev/null
+++ b/app/assets/images/emoji/purple_heart.png
Binary files differ
diff --git a/app/assets/images/emoji/purse.png b/app/assets/images/emoji/purse.png
new file mode 100644
index 00000000000..981346193c5
--- /dev/null
+++ b/app/assets/images/emoji/purse.png
Binary files differ
diff --git a/app/assets/images/emoji/pushpin.png b/app/assets/images/emoji/pushpin.png
new file mode 100644
index 00000000000..57e07d7f4cc
--- /dev/null
+++ b/app/assets/images/emoji/pushpin.png
Binary files differ
diff --git a/app/assets/images/emoji/put_litter_in_its_place.png b/app/assets/images/emoji/put_litter_in_its_place.png
new file mode 100644
index 00000000000..82a84f9a375
--- /dev/null
+++ b/app/assets/images/emoji/put_litter_in_its_place.png
Binary files differ
diff --git a/app/assets/images/emoji/question.png b/app/assets/images/emoji/question.png
new file mode 100644
index 00000000000..5a58f3458aa
--- /dev/null
+++ b/app/assets/images/emoji/question.png
Binary files differ
diff --git a/app/assets/images/emoji/rabbit.png b/app/assets/images/emoji/rabbit.png
new file mode 100644
index 00000000000..ea75ab0426e
--- /dev/null
+++ b/app/assets/images/emoji/rabbit.png
Binary files differ
diff --git a/app/assets/images/emoji/rabbit2.png b/app/assets/images/emoji/rabbit2.png
new file mode 100644
index 00000000000..2c8a29c642f
--- /dev/null
+++ b/app/assets/images/emoji/rabbit2.png
Binary files differ
diff --git a/app/assets/images/emoji/race_car.png b/app/assets/images/emoji/race_car.png
new file mode 100644
index 00000000000..fe3f045f446
--- /dev/null
+++ b/app/assets/images/emoji/race_car.png
Binary files differ
diff --git a/app/assets/images/emoji/racehorse.png b/app/assets/images/emoji/racehorse.png
new file mode 100644
index 00000000000..b3e73cc8903
--- /dev/null
+++ b/app/assets/images/emoji/racehorse.png
Binary files differ
diff --git a/app/assets/images/emoji/radio.png b/app/assets/images/emoji/radio.png
new file mode 100644
index 00000000000..dec381fa242
--- /dev/null
+++ b/app/assets/images/emoji/radio.png
Binary files differ
diff --git a/app/assets/images/emoji/radio_button.png b/app/assets/images/emoji/radio_button.png
new file mode 100644
index 00000000000..3a23449d917
--- /dev/null
+++ b/app/assets/images/emoji/radio_button.png
Binary files differ
diff --git a/app/assets/images/emoji/radioactive.png b/app/assets/images/emoji/radioactive.png
new file mode 100644
index 00000000000..3b46199fe37
--- /dev/null
+++ b/app/assets/images/emoji/radioactive.png
Binary files differ
diff --git a/app/assets/images/emoji/rage.png b/app/assets/images/emoji/rage.png
new file mode 100644
index 00000000000..9d739bd40ad
--- /dev/null
+++ b/app/assets/images/emoji/rage.png
Binary files differ
diff --git a/app/assets/images/emoji/railway_car.png b/app/assets/images/emoji/railway_car.png
new file mode 100644
index 00000000000..a9acbf13008
--- /dev/null
+++ b/app/assets/images/emoji/railway_car.png
Binary files differ
diff --git a/app/assets/images/emoji/railway_track.png b/app/assets/images/emoji/railway_track.png
new file mode 100644
index 00000000000..e1a7a0d1430
--- /dev/null
+++ b/app/assets/images/emoji/railway_track.png
Binary files differ
diff --git a/app/assets/images/emoji/rainbow.png b/app/assets/images/emoji/rainbow.png
new file mode 100644
index 00000000000..154735d7147
--- /dev/null
+++ b/app/assets/images/emoji/rainbow.png
Binary files differ
diff --git a/app/assets/images/emoji/raised_back_of_hand.png b/app/assets/images/emoji/raised_back_of_hand.png
new file mode 100644
index 00000000000..479234294b4
--- /dev/null
+++ b/app/assets/images/emoji/raised_back_of_hand.png
Binary files differ
diff --git a/app/assets/images/emoji/raised_back_of_hand_tone1.png b/app/assets/images/emoji/raised_back_of_hand_tone1.png
new file mode 100644
index 00000000000..813d28499b5
--- /dev/null
+++ b/app/assets/images/emoji/raised_back_of_hand_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/raised_back_of_hand_tone2.png b/app/assets/images/emoji/raised_back_of_hand_tone2.png
new file mode 100644
index 00000000000..192ff795e37
--- /dev/null
+++ b/app/assets/images/emoji/raised_back_of_hand_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/raised_back_of_hand_tone3.png b/app/assets/images/emoji/raised_back_of_hand_tone3.png
new file mode 100644
index 00000000000..61a727abe6b
--- /dev/null
+++ b/app/assets/images/emoji/raised_back_of_hand_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/raised_back_of_hand_tone4.png b/app/assets/images/emoji/raised_back_of_hand_tone4.png
new file mode 100644
index 00000000000..2e83da511f5
--- /dev/null
+++ b/app/assets/images/emoji/raised_back_of_hand_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/raised_back_of_hand_tone5.png b/app/assets/images/emoji/raised_back_of_hand_tone5.png
new file mode 100644
index 00000000000..d7a5b95a02c
--- /dev/null
+++ b/app/assets/images/emoji/raised_back_of_hand_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/raised_hand.png b/app/assets/images/emoji/raised_hand.png
new file mode 100644
index 00000000000..6b2954315d1
--- /dev/null
+++ b/app/assets/images/emoji/raised_hand.png
Binary files differ
diff --git a/app/assets/images/emoji/raised_hand_tone1.png b/app/assets/images/emoji/raised_hand_tone1.png
new file mode 100644
index 00000000000..3b752902c07
--- /dev/null
+++ b/app/assets/images/emoji/raised_hand_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/raised_hand_tone2.png b/app/assets/images/emoji/raised_hand_tone2.png
new file mode 100644
index 00000000000..44e2a514c60
--- /dev/null
+++ b/app/assets/images/emoji/raised_hand_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/raised_hand_tone3.png b/app/assets/images/emoji/raised_hand_tone3.png
new file mode 100644
index 00000000000..5bb62a7528a
--- /dev/null
+++ b/app/assets/images/emoji/raised_hand_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/raised_hand_tone4.png b/app/assets/images/emoji/raised_hand_tone4.png
new file mode 100644
index 00000000000..c7f8c9ec270
--- /dev/null
+++ b/app/assets/images/emoji/raised_hand_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/raised_hand_tone5.png b/app/assets/images/emoji/raised_hand_tone5.png
new file mode 100644
index 00000000000..c601b58a73e
--- /dev/null
+++ b/app/assets/images/emoji/raised_hand_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/raised_hands.png b/app/assets/images/emoji/raised_hands.png
new file mode 100644
index 00000000000..c0155f728e7
--- /dev/null
+++ b/app/assets/images/emoji/raised_hands.png
Binary files differ
diff --git a/app/assets/images/emoji/raised_hands_tone1.png b/app/assets/images/emoji/raised_hands_tone1.png
new file mode 100644
index 00000000000..1168b8236b6
--- /dev/null
+++ b/app/assets/images/emoji/raised_hands_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/raised_hands_tone2.png b/app/assets/images/emoji/raised_hands_tone2.png
new file mode 100644
index 00000000000..322de622903
--- /dev/null
+++ b/app/assets/images/emoji/raised_hands_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/raised_hands_tone3.png b/app/assets/images/emoji/raised_hands_tone3.png
new file mode 100644
index 00000000000..2aa24e05ae1
--- /dev/null
+++ b/app/assets/images/emoji/raised_hands_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/raised_hands_tone4.png b/app/assets/images/emoji/raised_hands_tone4.png
new file mode 100644
index 00000000000..f31bf0db992
--- /dev/null
+++ b/app/assets/images/emoji/raised_hands_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/raised_hands_tone5.png b/app/assets/images/emoji/raised_hands_tone5.png
new file mode 100644
index 00000000000..5e95067f98b
--- /dev/null
+++ b/app/assets/images/emoji/raised_hands_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/raising_hand.png b/app/assets/images/emoji/raising_hand.png
new file mode 100644
index 00000000000..2880708c0cc
--- /dev/null
+++ b/app/assets/images/emoji/raising_hand.png
Binary files differ
diff --git a/app/assets/images/emoji/raising_hand_tone1.png b/app/assets/images/emoji/raising_hand_tone1.png
new file mode 100644
index 00000000000..1c90e3e2689
--- /dev/null
+++ b/app/assets/images/emoji/raising_hand_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/raising_hand_tone2.png b/app/assets/images/emoji/raising_hand_tone2.png
new file mode 100644
index 00000000000..82c3ef2bfc5
--- /dev/null
+++ b/app/assets/images/emoji/raising_hand_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/raising_hand_tone3.png b/app/assets/images/emoji/raising_hand_tone3.png
new file mode 100644
index 00000000000..1b1da2aa0ca
--- /dev/null
+++ b/app/assets/images/emoji/raising_hand_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/raising_hand_tone4.png b/app/assets/images/emoji/raising_hand_tone4.png
new file mode 100644
index 00000000000..e453855c01f
--- /dev/null
+++ b/app/assets/images/emoji/raising_hand_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/raising_hand_tone5.png b/app/assets/images/emoji/raising_hand_tone5.png
new file mode 100644
index 00000000000..b86200fd844
--- /dev/null
+++ b/app/assets/images/emoji/raising_hand_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/ram.png b/app/assets/images/emoji/ram.png
new file mode 100644
index 00000000000..52a44464c9b
--- /dev/null
+++ b/app/assets/images/emoji/ram.png
Binary files differ
diff --git a/app/assets/images/emoji/ramen.png b/app/assets/images/emoji/ramen.png
new file mode 100644
index 00000000000..c1cb7cd7384
--- /dev/null
+++ b/app/assets/images/emoji/ramen.png
Binary files differ
diff --git a/app/assets/images/emoji/rat.png b/app/assets/images/emoji/rat.png
new file mode 100644
index 00000000000..86219144f10
--- /dev/null
+++ b/app/assets/images/emoji/rat.png
Binary files differ
diff --git a/app/assets/images/emoji/record_button.png b/app/assets/images/emoji/record_button.png
new file mode 100644
index 00000000000..ada52830fce
--- /dev/null
+++ b/app/assets/images/emoji/record_button.png
Binary files differ
diff --git a/app/assets/images/emoji/recycle.png b/app/assets/images/emoji/recycle.png
new file mode 100644
index 00000000000..9221f095c37
--- /dev/null
+++ b/app/assets/images/emoji/recycle.png
Binary files differ
diff --git a/app/assets/images/emoji/red_car.png b/app/assets/images/emoji/red_car.png
new file mode 100644
index 00000000000..b3e6a774dea
--- /dev/null
+++ b/app/assets/images/emoji/red_car.png
Binary files differ
diff --git a/app/assets/images/emoji/red_circle.png b/app/assets/images/emoji/red_circle.png
new file mode 100644
index 00000000000..4bef930d92f
--- /dev/null
+++ b/app/assets/images/emoji/red_circle.png
Binary files differ
diff --git a/app/assets/images/emoji/registered.png b/app/assets/images/emoji/registered.png
new file mode 100644
index 00000000000..53ef9f2d4e6
--- /dev/null
+++ b/app/assets/images/emoji/registered.png
Binary files differ
diff --git a/app/assets/images/emoji/relaxed.png b/app/assets/images/emoji/relaxed.png
new file mode 100644
index 00000000000..e9e53c03d45
--- /dev/null
+++ b/app/assets/images/emoji/relaxed.png
Binary files differ
diff --git a/app/assets/images/emoji/relieved.png b/app/assets/images/emoji/relieved.png
new file mode 100644
index 00000000000..715ad0bf53f
--- /dev/null
+++ b/app/assets/images/emoji/relieved.png
Binary files differ
diff --git a/app/assets/images/emoji/reminder_ribbon.png b/app/assets/images/emoji/reminder_ribbon.png
new file mode 100644
index 00000000000..3988bbd094c
--- /dev/null
+++ b/app/assets/images/emoji/reminder_ribbon.png
Binary files differ
diff --git a/app/assets/images/emoji/repeat.png b/app/assets/images/emoji/repeat.png
new file mode 100644
index 00000000000..540ce4e0fba
--- /dev/null
+++ b/app/assets/images/emoji/repeat.png
Binary files differ
diff --git a/app/assets/images/emoji/repeat_one.png b/app/assets/images/emoji/repeat_one.png
new file mode 100644
index 00000000000..9567e83337f
--- /dev/null
+++ b/app/assets/images/emoji/repeat_one.png
Binary files differ
diff --git a/app/assets/images/emoji/restroom.png b/app/assets/images/emoji/restroom.png
new file mode 100644
index 00000000000..9588e0f0ef7
--- /dev/null
+++ b/app/assets/images/emoji/restroom.png
Binary files differ
diff --git a/app/assets/images/emoji/revolving_hearts.png b/app/assets/images/emoji/revolving_hearts.png
new file mode 100644
index 00000000000..7b9d1948f73
--- /dev/null
+++ b/app/assets/images/emoji/revolving_hearts.png
Binary files differ
diff --git a/app/assets/images/emoji/rewind.png b/app/assets/images/emoji/rewind.png
new file mode 100644
index 00000000000..e22e2bd3da5
--- /dev/null
+++ b/app/assets/images/emoji/rewind.png
Binary files differ
diff --git a/app/assets/images/emoji/rhino.png b/app/assets/images/emoji/rhino.png
new file mode 100644
index 00000000000..12f4e0d9d9b
--- /dev/null
+++ b/app/assets/images/emoji/rhino.png
Binary files differ
diff --git a/app/assets/images/emoji/ribbon.png b/app/assets/images/emoji/ribbon.png
new file mode 100644
index 00000000000..0f253c3d8c8
--- /dev/null
+++ b/app/assets/images/emoji/ribbon.png
Binary files differ
diff --git a/app/assets/images/emoji/rice.png b/app/assets/images/emoji/rice.png
new file mode 100644
index 00000000000..6e3ac7956b1
--- /dev/null
+++ b/app/assets/images/emoji/rice.png
Binary files differ
diff --git a/app/assets/images/emoji/rice_ball.png b/app/assets/images/emoji/rice_ball.png
new file mode 100644
index 00000000000..d3d8ee25cb8
--- /dev/null
+++ b/app/assets/images/emoji/rice_ball.png
Binary files differ
diff --git a/app/assets/images/emoji/rice_cracker.png b/app/assets/images/emoji/rice_cracker.png
new file mode 100644
index 00000000000..7fbd08e4ff9
--- /dev/null
+++ b/app/assets/images/emoji/rice_cracker.png
Binary files differ
diff --git a/app/assets/images/emoji/rice_scene.png b/app/assets/images/emoji/rice_scene.png
new file mode 100644
index 00000000000..1a28426592a
--- /dev/null
+++ b/app/assets/images/emoji/rice_scene.png
Binary files differ
diff --git a/app/assets/images/emoji/right_facing_fist.png b/app/assets/images/emoji/right_facing_fist.png
new file mode 100644
index 00000000000..754ed066d2c
--- /dev/null
+++ b/app/assets/images/emoji/right_facing_fist.png
Binary files differ
diff --git a/app/assets/images/emoji/right_facing_fist_tone1.png b/app/assets/images/emoji/right_facing_fist_tone1.png
new file mode 100644
index 00000000000..33ded2f61a6
--- /dev/null
+++ b/app/assets/images/emoji/right_facing_fist_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/right_facing_fist_tone2.png b/app/assets/images/emoji/right_facing_fist_tone2.png
new file mode 100644
index 00000000000..88054e335c7
--- /dev/null
+++ b/app/assets/images/emoji/right_facing_fist_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/right_facing_fist_tone3.png b/app/assets/images/emoji/right_facing_fist_tone3.png
new file mode 100644
index 00000000000..84b9f5da7f7
--- /dev/null
+++ b/app/assets/images/emoji/right_facing_fist_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/right_facing_fist_tone4.png b/app/assets/images/emoji/right_facing_fist_tone4.png
new file mode 100644
index 00000000000..e741cfea68b
--- /dev/null
+++ b/app/assets/images/emoji/right_facing_fist_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/right_facing_fist_tone5.png b/app/assets/images/emoji/right_facing_fist_tone5.png
new file mode 100644
index 00000000000..cf66d760c1f
--- /dev/null
+++ b/app/assets/images/emoji/right_facing_fist_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/ring.png b/app/assets/images/emoji/ring.png
new file mode 100644
index 00000000000..87d227adb74
--- /dev/null
+++ b/app/assets/images/emoji/ring.png
Binary files differ
diff --git a/app/assets/images/emoji/robot.png b/app/assets/images/emoji/robot.png
new file mode 100644
index 00000000000..7cc62612c6a
--- /dev/null
+++ b/app/assets/images/emoji/robot.png
Binary files differ
diff --git a/app/assets/images/emoji/rocket.png b/app/assets/images/emoji/rocket.png
new file mode 100644
index 00000000000..0d8da089a37
--- /dev/null
+++ b/app/assets/images/emoji/rocket.png
Binary files differ
diff --git a/app/assets/images/emoji/rofl.png b/app/assets/images/emoji/rofl.png
new file mode 100644
index 00000000000..b1736fedfeb
--- /dev/null
+++ b/app/assets/images/emoji/rofl.png
Binary files differ
diff --git a/app/assets/images/emoji/roller_coaster.png b/app/assets/images/emoji/roller_coaster.png
new file mode 100644
index 00000000000..5b849e071e8
--- /dev/null
+++ b/app/assets/images/emoji/roller_coaster.png
Binary files differ
diff --git a/app/assets/images/emoji/rolling_eyes.png b/app/assets/images/emoji/rolling_eyes.png
new file mode 100644
index 00000000000..2f77b9fc3b9
--- /dev/null
+++ b/app/assets/images/emoji/rolling_eyes.png
Binary files differ
diff --git a/app/assets/images/emoji/rooster.png b/app/assets/images/emoji/rooster.png
new file mode 100644
index 00000000000..bbf2bbff97a
--- /dev/null
+++ b/app/assets/images/emoji/rooster.png
Binary files differ
diff --git a/app/assets/images/emoji/rose.png b/app/assets/images/emoji/rose.png
new file mode 100644
index 00000000000..52c286d31ce
--- /dev/null
+++ b/app/assets/images/emoji/rose.png
Binary files differ
diff --git a/app/assets/images/emoji/rosette.png b/app/assets/images/emoji/rosette.png
new file mode 100644
index 00000000000..8030e494bcf
--- /dev/null
+++ b/app/assets/images/emoji/rosette.png
Binary files differ
diff --git a/app/assets/images/emoji/rotating_light.png b/app/assets/images/emoji/rotating_light.png
new file mode 100644
index 00000000000..cad66b0afef
--- /dev/null
+++ b/app/assets/images/emoji/rotating_light.png
Binary files differ
diff --git a/app/assets/images/emoji/round_pushpin.png b/app/assets/images/emoji/round_pushpin.png
new file mode 100644
index 00000000000..28b9d72866e
--- /dev/null
+++ b/app/assets/images/emoji/round_pushpin.png
Binary files differ
diff --git a/app/assets/images/emoji/rowboat.png b/app/assets/images/emoji/rowboat.png
new file mode 100644
index 00000000000..dd4dfc095d9
--- /dev/null
+++ b/app/assets/images/emoji/rowboat.png
Binary files differ
diff --git a/app/assets/images/emoji/rowboat_tone1.png b/app/assets/images/emoji/rowboat_tone1.png
new file mode 100644
index 00000000000..5e5d18548cb
--- /dev/null
+++ b/app/assets/images/emoji/rowboat_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/rowboat_tone2.png b/app/assets/images/emoji/rowboat_tone2.png
new file mode 100644
index 00000000000..9b123ef8871
--- /dev/null
+++ b/app/assets/images/emoji/rowboat_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/rowboat_tone3.png b/app/assets/images/emoji/rowboat_tone3.png
new file mode 100644
index 00000000000..8ebd89a55f5
--- /dev/null
+++ b/app/assets/images/emoji/rowboat_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/rowboat_tone4.png b/app/assets/images/emoji/rowboat_tone4.png
new file mode 100644
index 00000000000..2b0d04f8725
--- /dev/null
+++ b/app/assets/images/emoji/rowboat_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/rowboat_tone5.png b/app/assets/images/emoji/rowboat_tone5.png
new file mode 100644
index 00000000000..b346f2dfc84
--- /dev/null
+++ b/app/assets/images/emoji/rowboat_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/rugby_football.png b/app/assets/images/emoji/rugby_football.png
new file mode 100644
index 00000000000..b1872273436
--- /dev/null
+++ b/app/assets/images/emoji/rugby_football.png
Binary files differ
diff --git a/app/assets/images/emoji/runner.png b/app/assets/images/emoji/runner.png
new file mode 100644
index 00000000000..e914915976a
--- /dev/null
+++ b/app/assets/images/emoji/runner.png
Binary files differ
diff --git a/app/assets/images/emoji/runner_tone1.png b/app/assets/images/emoji/runner_tone1.png
new file mode 100644
index 00000000000..9355239a52d
--- /dev/null
+++ b/app/assets/images/emoji/runner_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/runner_tone2.png b/app/assets/images/emoji/runner_tone2.png
new file mode 100644
index 00000000000..6112fd5c376
--- /dev/null
+++ b/app/assets/images/emoji/runner_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/runner_tone3.png b/app/assets/images/emoji/runner_tone3.png
new file mode 100644
index 00000000000..625ec708f48
--- /dev/null
+++ b/app/assets/images/emoji/runner_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/runner_tone4.png b/app/assets/images/emoji/runner_tone4.png
new file mode 100644
index 00000000000..242f1b56337
--- /dev/null
+++ b/app/assets/images/emoji/runner_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/runner_tone5.png b/app/assets/images/emoji/runner_tone5.png
new file mode 100644
index 00000000000..2976c6f019f
--- /dev/null
+++ b/app/assets/images/emoji/runner_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/running_shirt_with_sash.png b/app/assets/images/emoji/running_shirt_with_sash.png
new file mode 100644
index 00000000000..6d83c06b803
--- /dev/null
+++ b/app/assets/images/emoji/running_shirt_with_sash.png
Binary files differ
diff --git a/app/assets/images/emoji/sa.png b/app/assets/images/emoji/sa.png
new file mode 100644
index 00000000000..900f9633247
--- /dev/null
+++ b/app/assets/images/emoji/sa.png
Binary files differ
diff --git a/app/assets/images/emoji/sagittarius.png b/app/assets/images/emoji/sagittarius.png
new file mode 100644
index 00000000000..f8d94ff2923
--- /dev/null
+++ b/app/assets/images/emoji/sagittarius.png
Binary files differ
diff --git a/app/assets/images/emoji/sailboat.png b/app/assets/images/emoji/sailboat.png
new file mode 100644
index 00000000000..772ef11da5d
--- /dev/null
+++ b/app/assets/images/emoji/sailboat.png
Binary files differ
diff --git a/app/assets/images/emoji/sake.png b/app/assets/images/emoji/sake.png
new file mode 100644
index 00000000000..2933f5672c4
--- /dev/null
+++ b/app/assets/images/emoji/sake.png
Binary files differ
diff --git a/app/assets/images/emoji/salad.png b/app/assets/images/emoji/salad.png
new file mode 100644
index 00000000000..c89f9341158
--- /dev/null
+++ b/app/assets/images/emoji/salad.png
Binary files differ
diff --git a/app/assets/images/emoji/sandal.png b/app/assets/images/emoji/sandal.png
new file mode 100644
index 00000000000..9d9f5122b7a
--- /dev/null
+++ b/app/assets/images/emoji/sandal.png
Binary files differ
diff --git a/app/assets/images/emoji/santa.png b/app/assets/images/emoji/santa.png
new file mode 100644
index 00000000000..bc83ab80d52
--- /dev/null
+++ b/app/assets/images/emoji/santa.png
Binary files differ
diff --git a/app/assets/images/emoji/santa_tone1.png b/app/assets/images/emoji/santa_tone1.png
new file mode 100644
index 00000000000..5233ffb7174
--- /dev/null
+++ b/app/assets/images/emoji/santa_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/santa_tone2.png b/app/assets/images/emoji/santa_tone2.png
new file mode 100644
index 00000000000..4e845438197
--- /dev/null
+++ b/app/assets/images/emoji/santa_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/santa_tone3.png b/app/assets/images/emoji/santa_tone3.png
new file mode 100644
index 00000000000..7fc4f33b60f
--- /dev/null
+++ b/app/assets/images/emoji/santa_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/santa_tone4.png b/app/assets/images/emoji/santa_tone4.png
new file mode 100644
index 00000000000..d1d5a15132d
--- /dev/null
+++ b/app/assets/images/emoji/santa_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/santa_tone5.png b/app/assets/images/emoji/santa_tone5.png
new file mode 100644
index 00000000000..4d697a01f24
--- /dev/null
+++ b/app/assets/images/emoji/santa_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/satellite.png b/app/assets/images/emoji/satellite.png
new file mode 100644
index 00000000000..db0372795f4
--- /dev/null
+++ b/app/assets/images/emoji/satellite.png
Binary files differ
diff --git a/app/assets/images/emoji/satellite_orbital.png b/app/assets/images/emoji/satellite_orbital.png
new file mode 100644
index 00000000000..4ba55d6e297
--- /dev/null
+++ b/app/assets/images/emoji/satellite_orbital.png
Binary files differ
diff --git a/app/assets/images/emoji/saxophone.png b/app/assets/images/emoji/saxophone.png
new file mode 100644
index 00000000000..a392faec291
--- /dev/null
+++ b/app/assets/images/emoji/saxophone.png
Binary files differ
diff --git a/app/assets/images/emoji/scales.png b/app/assets/images/emoji/scales.png
new file mode 100644
index 00000000000..0757eda1684
--- /dev/null
+++ b/app/assets/images/emoji/scales.png
Binary files differ
diff --git a/app/assets/images/emoji/school.png b/app/assets/images/emoji/school.png
new file mode 100644
index 00000000000..269759534f0
--- /dev/null
+++ b/app/assets/images/emoji/school.png
Binary files differ
diff --git a/app/assets/images/emoji/school_satchel.png b/app/assets/images/emoji/school_satchel.png
new file mode 100644
index 00000000000..9997c86e7dc
--- /dev/null
+++ b/app/assets/images/emoji/school_satchel.png
Binary files differ
diff --git a/app/assets/images/emoji/scissors.png b/app/assets/images/emoji/scissors.png
new file mode 100644
index 00000000000..270571c8cdd
--- /dev/null
+++ b/app/assets/images/emoji/scissors.png
Binary files differ
diff --git a/app/assets/images/emoji/scooter.png b/app/assets/images/emoji/scooter.png
new file mode 100644
index 00000000000..4ab7ef59cd2
--- /dev/null
+++ b/app/assets/images/emoji/scooter.png
Binary files differ
diff --git a/app/assets/images/emoji/scorpion.png b/app/assets/images/emoji/scorpion.png
new file mode 100644
index 00000000000..449a6b281c9
--- /dev/null
+++ b/app/assets/images/emoji/scorpion.png
Binary files differ
diff --git a/app/assets/images/emoji/scorpius.png b/app/assets/images/emoji/scorpius.png
new file mode 100644
index 00000000000..c31a9920455
--- /dev/null
+++ b/app/assets/images/emoji/scorpius.png
Binary files differ
diff --git a/app/assets/images/emoji/scream.png b/app/assets/images/emoji/scream.png
new file mode 100644
index 00000000000..c3bea9f2510
--- /dev/null
+++ b/app/assets/images/emoji/scream.png
Binary files differ
diff --git a/app/assets/images/emoji/scream_cat.png b/app/assets/images/emoji/scream_cat.png
new file mode 100644
index 00000000000..15803ad8e6e
--- /dev/null
+++ b/app/assets/images/emoji/scream_cat.png
Binary files differ
diff --git a/app/assets/images/emoji/scroll.png b/app/assets/images/emoji/scroll.png
new file mode 100644
index 00000000000..50ee5dcd4b9
--- /dev/null
+++ b/app/assets/images/emoji/scroll.png
Binary files differ
diff --git a/app/assets/images/emoji/seat.png b/app/assets/images/emoji/seat.png
new file mode 100644
index 00000000000..a6d72d95adb
--- /dev/null
+++ b/app/assets/images/emoji/seat.png
Binary files differ
diff --git a/app/assets/images/emoji/second_place.png b/app/assets/images/emoji/second_place.png
new file mode 100644
index 00000000000..17b011268b6
--- /dev/null
+++ b/app/assets/images/emoji/second_place.png
Binary files differ
diff --git a/app/assets/images/emoji/secret.png b/app/assets/images/emoji/secret.png
new file mode 100644
index 00000000000..5fd72608e60
--- /dev/null
+++ b/app/assets/images/emoji/secret.png
Binary files differ
diff --git a/app/assets/images/emoji/see_no_evil.png b/app/assets/images/emoji/see_no_evil.png
new file mode 100644
index 00000000000..5187e474531
--- /dev/null
+++ b/app/assets/images/emoji/see_no_evil.png
Binary files differ
diff --git a/app/assets/images/emoji/seedling.png b/app/assets/images/emoji/seedling.png
new file mode 100644
index 00000000000..ae0948bcfd6
--- /dev/null
+++ b/app/assets/images/emoji/seedling.png
Binary files differ
diff --git a/app/assets/images/emoji/selfie.png b/app/assets/images/emoji/selfie.png
new file mode 100644
index 00000000000..6a1ba75c7e3
--- /dev/null
+++ b/app/assets/images/emoji/selfie.png
Binary files differ
diff --git a/app/assets/images/emoji/selfie_tone1.png b/app/assets/images/emoji/selfie_tone1.png
new file mode 100644
index 00000000000..290e075b56f
--- /dev/null
+++ b/app/assets/images/emoji/selfie_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/selfie_tone2.png b/app/assets/images/emoji/selfie_tone2.png
new file mode 100644
index 00000000000..fcd9595b643
--- /dev/null
+++ b/app/assets/images/emoji/selfie_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/selfie_tone3.png b/app/assets/images/emoji/selfie_tone3.png
new file mode 100644
index 00000000000..f3a22fdf435
--- /dev/null
+++ b/app/assets/images/emoji/selfie_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/selfie_tone4.png b/app/assets/images/emoji/selfie_tone4.png
new file mode 100644
index 00000000000..cdecf6d9f4e
--- /dev/null
+++ b/app/assets/images/emoji/selfie_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/selfie_tone5.png b/app/assets/images/emoji/selfie_tone5.png
new file mode 100644
index 00000000000..86acbb6c202
--- /dev/null
+++ b/app/assets/images/emoji/selfie_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/seven.png b/app/assets/images/emoji/seven.png
new file mode 100644
index 00000000000..9b3476ae7c7
--- /dev/null
+++ b/app/assets/images/emoji/seven.png
Binary files differ
diff --git a/app/assets/images/emoji/shallow_pan_of_food.png b/app/assets/images/emoji/shallow_pan_of_food.png
new file mode 100644
index 00000000000..663a1006acd
--- /dev/null
+++ b/app/assets/images/emoji/shallow_pan_of_food.png
Binary files differ
diff --git a/app/assets/images/emoji/shamrock.png b/app/assets/images/emoji/shamrock.png
new file mode 100644
index 00000000000..f202aecfe6f
--- /dev/null
+++ b/app/assets/images/emoji/shamrock.png
Binary files differ
diff --git a/app/assets/images/emoji/shark.png b/app/assets/images/emoji/shark.png
new file mode 100644
index 00000000000..c75076d57d8
--- /dev/null
+++ b/app/assets/images/emoji/shark.png
Binary files differ
diff --git a/app/assets/images/emoji/shaved_ice.png b/app/assets/images/emoji/shaved_ice.png
new file mode 100644
index 00000000000..36dfb53ca93
--- /dev/null
+++ b/app/assets/images/emoji/shaved_ice.png
Binary files differ
diff --git a/app/assets/images/emoji/sheep.png b/app/assets/images/emoji/sheep.png
new file mode 100644
index 00000000000..102b8a52b28
--- /dev/null
+++ b/app/assets/images/emoji/sheep.png
Binary files differ
diff --git a/app/assets/images/emoji/shell.png b/app/assets/images/emoji/shell.png
new file mode 100644
index 00000000000..55721629f62
--- /dev/null
+++ b/app/assets/images/emoji/shell.png
Binary files differ
diff --git a/app/assets/images/emoji/shield.png b/app/assets/images/emoji/shield.png
new file mode 100644
index 00000000000..610bf033ce0
--- /dev/null
+++ b/app/assets/images/emoji/shield.png
Binary files differ
diff --git a/app/assets/images/emoji/shinto_shrine.png b/app/assets/images/emoji/shinto_shrine.png
new file mode 100644
index 00000000000..5a344975bf3
--- /dev/null
+++ b/app/assets/images/emoji/shinto_shrine.png
Binary files differ
diff --git a/app/assets/images/emoji/ship.png b/app/assets/images/emoji/ship.png
new file mode 100644
index 00000000000..62d54f7d6c9
--- /dev/null
+++ b/app/assets/images/emoji/ship.png
Binary files differ
diff --git a/app/assets/images/emoji/shirt.png b/app/assets/images/emoji/shirt.png
new file mode 100644
index 00000000000..af08dec8b59
--- /dev/null
+++ b/app/assets/images/emoji/shirt.png
Binary files differ
diff --git a/app/assets/images/emoji/shopping_bags.png b/app/assets/images/emoji/shopping_bags.png
new file mode 100644
index 00000000000..99f2a2b13ac
--- /dev/null
+++ b/app/assets/images/emoji/shopping_bags.png
Binary files differ
diff --git a/app/assets/images/emoji/shopping_cart.png b/app/assets/images/emoji/shopping_cart.png
new file mode 100644
index 00000000000..1086fe6e456
--- /dev/null
+++ b/app/assets/images/emoji/shopping_cart.png
Binary files differ
diff --git a/app/assets/images/emoji/shower.png b/app/assets/images/emoji/shower.png
new file mode 100644
index 00000000000..156776a2e52
--- /dev/null
+++ b/app/assets/images/emoji/shower.png
Binary files differ
diff --git a/app/assets/images/emoji/shrimp.png b/app/assets/images/emoji/shrimp.png
new file mode 100644
index 00000000000..49eff28a71e
--- /dev/null
+++ b/app/assets/images/emoji/shrimp.png
Binary files differ
diff --git a/app/assets/images/emoji/shrug.png b/app/assets/images/emoji/shrug.png
new file mode 100644
index 00000000000..76e63bfac77
--- /dev/null
+++ b/app/assets/images/emoji/shrug.png
Binary files differ
diff --git a/app/assets/images/emoji/shrug_tone1.png b/app/assets/images/emoji/shrug_tone1.png
new file mode 100644
index 00000000000..1c895e64468
--- /dev/null
+++ b/app/assets/images/emoji/shrug_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/shrug_tone2.png b/app/assets/images/emoji/shrug_tone2.png
new file mode 100644
index 00000000000..4e3ca8f8bac
--- /dev/null
+++ b/app/assets/images/emoji/shrug_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/shrug_tone3.png b/app/assets/images/emoji/shrug_tone3.png
new file mode 100644
index 00000000000..d1b16a19bb5
--- /dev/null
+++ b/app/assets/images/emoji/shrug_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/shrug_tone4.png b/app/assets/images/emoji/shrug_tone4.png
new file mode 100644
index 00000000000..5fbef3f2255
--- /dev/null
+++ b/app/assets/images/emoji/shrug_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/shrug_tone5.png b/app/assets/images/emoji/shrug_tone5.png
new file mode 100644
index 00000000000..4af2e28bc5c
--- /dev/null
+++ b/app/assets/images/emoji/shrug_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/signal_strength.png b/app/assets/images/emoji/signal_strength.png
new file mode 100644
index 00000000000..ee2b5a4b519
--- /dev/null
+++ b/app/assets/images/emoji/signal_strength.png
Binary files differ
diff --git a/app/assets/images/emoji/six.png b/app/assets/images/emoji/six.png
new file mode 100644
index 00000000000..371b3acef2c
--- /dev/null
+++ b/app/assets/images/emoji/six.png
Binary files differ
diff --git a/app/assets/images/emoji/six_pointed_star.png b/app/assets/images/emoji/six_pointed_star.png
new file mode 100644
index 00000000000..2eb1707458b
--- /dev/null
+++ b/app/assets/images/emoji/six_pointed_star.png
Binary files differ
diff --git a/app/assets/images/emoji/ski.png b/app/assets/images/emoji/ski.png
new file mode 100644
index 00000000000..4a2d2c12306
--- /dev/null
+++ b/app/assets/images/emoji/ski.png
Binary files differ
diff --git a/app/assets/images/emoji/skier.png b/app/assets/images/emoji/skier.png
new file mode 100644
index 00000000000..2eb3bdce2af
--- /dev/null
+++ b/app/assets/images/emoji/skier.png
Binary files differ
diff --git a/app/assets/images/emoji/skull.png b/app/assets/images/emoji/skull.png
new file mode 100644
index 00000000000..26abb17296a
--- /dev/null
+++ b/app/assets/images/emoji/skull.png
Binary files differ
diff --git a/app/assets/images/emoji/skull_crossbones.png b/app/assets/images/emoji/skull_crossbones.png
new file mode 100644
index 00000000000..b459df9227a
--- /dev/null
+++ b/app/assets/images/emoji/skull_crossbones.png
Binary files differ
diff --git a/app/assets/images/emoji/sleeping.png b/app/assets/images/emoji/sleeping.png
new file mode 100644
index 00000000000..9ecf600d6d8
--- /dev/null
+++ b/app/assets/images/emoji/sleeping.png
Binary files differ
diff --git a/app/assets/images/emoji/sleeping_accommodation.png b/app/assets/images/emoji/sleeping_accommodation.png
new file mode 100644
index 00000000000..c739e7fb69b
--- /dev/null
+++ b/app/assets/images/emoji/sleeping_accommodation.png
Binary files differ
diff --git a/app/assets/images/emoji/sleepy.png b/app/assets/images/emoji/sleepy.png
new file mode 100644
index 00000000000..836b4107717
--- /dev/null
+++ b/app/assets/images/emoji/sleepy.png
Binary files differ
diff --git a/app/assets/images/emoji/slight_frown.png b/app/assets/images/emoji/slight_frown.png
new file mode 100644
index 00000000000..b2f1d983d36
--- /dev/null
+++ b/app/assets/images/emoji/slight_frown.png
Binary files differ
diff --git a/app/assets/images/emoji/slight_smile.png b/app/assets/images/emoji/slight_smile.png
new file mode 100644
index 00000000000..ddd7d65dd3d
--- /dev/null
+++ b/app/assets/images/emoji/slight_smile.png
Binary files differ
diff --git a/app/assets/images/emoji/slot_machine.png b/app/assets/images/emoji/slot_machine.png
new file mode 100644
index 00000000000..ee71b6c268c
--- /dev/null
+++ b/app/assets/images/emoji/slot_machine.png
Binary files differ
diff --git a/app/assets/images/emoji/small_blue_diamond.png b/app/assets/images/emoji/small_blue_diamond.png
new file mode 100644
index 00000000000..b86b5bc4db3
--- /dev/null
+++ b/app/assets/images/emoji/small_blue_diamond.png
Binary files differ
diff --git a/app/assets/images/emoji/small_orange_diamond.png b/app/assets/images/emoji/small_orange_diamond.png
new file mode 100644
index 00000000000..e1c6ed9b2f8
--- /dev/null
+++ b/app/assets/images/emoji/small_orange_diamond.png
Binary files differ
diff --git a/app/assets/images/emoji/small_red_triangle.png b/app/assets/images/emoji/small_red_triangle.png
new file mode 100644
index 00000000000..785887c195a
--- /dev/null
+++ b/app/assets/images/emoji/small_red_triangle.png
Binary files differ
diff --git a/app/assets/images/emoji/small_red_triangle_down.png b/app/assets/images/emoji/small_red_triangle_down.png
new file mode 100644
index 00000000000..a83beff1914
--- /dev/null
+++ b/app/assets/images/emoji/small_red_triangle_down.png
Binary files differ
diff --git a/app/assets/images/emoji/smile.png b/app/assets/images/emoji/smile.png
new file mode 100644
index 00000000000..aa47ffe978c
--- /dev/null
+++ b/app/assets/images/emoji/smile.png
Binary files differ
diff --git a/app/assets/images/emoji/smile_cat.png b/app/assets/images/emoji/smile_cat.png
new file mode 100644
index 00000000000..6f25f11dd3a
--- /dev/null
+++ b/app/assets/images/emoji/smile_cat.png
Binary files differ
diff --git a/app/assets/images/emoji/smiley.png b/app/assets/images/emoji/smiley.png
new file mode 100644
index 00000000000..30957a65968
--- /dev/null
+++ b/app/assets/images/emoji/smiley.png
Binary files differ
diff --git a/app/assets/images/emoji/smiley_cat.png b/app/assets/images/emoji/smiley_cat.png
new file mode 100644
index 00000000000..163b57a3427
--- /dev/null
+++ b/app/assets/images/emoji/smiley_cat.png
Binary files differ
diff --git a/app/assets/images/emoji/smiling_imp.png b/app/assets/images/emoji/smiling_imp.png
new file mode 100644
index 00000000000..cc2c5f1ec72
--- /dev/null
+++ b/app/assets/images/emoji/smiling_imp.png
Binary files differ
diff --git a/app/assets/images/emoji/smirk.png b/app/assets/images/emoji/smirk.png
new file mode 100644
index 00000000000..87852109988
--- /dev/null
+++ b/app/assets/images/emoji/smirk.png
Binary files differ
diff --git a/app/assets/images/emoji/smirk_cat.png b/app/assets/images/emoji/smirk_cat.png
new file mode 100644
index 00000000000..9ac5954c199
--- /dev/null
+++ b/app/assets/images/emoji/smirk_cat.png
Binary files differ
diff --git a/app/assets/images/emoji/smoking.png b/app/assets/images/emoji/smoking.png
new file mode 100644
index 00000000000..910f648c8f9
--- /dev/null
+++ b/app/assets/images/emoji/smoking.png
Binary files differ
diff --git a/app/assets/images/emoji/snail.png b/app/assets/images/emoji/snail.png
new file mode 100644
index 00000000000..f4ea071e2d3
--- /dev/null
+++ b/app/assets/images/emoji/snail.png
Binary files differ
diff --git a/app/assets/images/emoji/snake.png b/app/assets/images/emoji/snake.png
new file mode 100644
index 00000000000..d0278a28d8c
--- /dev/null
+++ b/app/assets/images/emoji/snake.png
Binary files differ
diff --git a/app/assets/images/emoji/sneezing_face.png b/app/assets/images/emoji/sneezing_face.png
new file mode 100644
index 00000000000..ccf07d4b64d
--- /dev/null
+++ b/app/assets/images/emoji/sneezing_face.png
Binary files differ
diff --git a/app/assets/images/emoji/snowboarder.png b/app/assets/images/emoji/snowboarder.png
new file mode 100644
index 00000000000..6361c0f2c9d
--- /dev/null
+++ b/app/assets/images/emoji/snowboarder.png
Binary files differ
diff --git a/app/assets/images/emoji/snowflake.png b/app/assets/images/emoji/snowflake.png
new file mode 100644
index 00000000000..db319a77ec6
--- /dev/null
+++ b/app/assets/images/emoji/snowflake.png
Binary files differ
diff --git a/app/assets/images/emoji/snowman.png b/app/assets/images/emoji/snowman.png
new file mode 100644
index 00000000000..20c177c2aff
--- /dev/null
+++ b/app/assets/images/emoji/snowman.png
Binary files differ
diff --git a/app/assets/images/emoji/snowman2.png b/app/assets/images/emoji/snowman2.png
new file mode 100644
index 00000000000..896f28502af
--- /dev/null
+++ b/app/assets/images/emoji/snowman2.png
Binary files differ
diff --git a/app/assets/images/emoji/sob.png b/app/assets/images/emoji/sob.png
new file mode 100644
index 00000000000..52e3517a1ee
--- /dev/null
+++ b/app/assets/images/emoji/sob.png
Binary files differ
diff --git a/app/assets/images/emoji/soccer.png b/app/assets/images/emoji/soccer.png
new file mode 100644
index 00000000000..28cfa218d6d
--- /dev/null
+++ b/app/assets/images/emoji/soccer.png
Binary files differ
diff --git a/app/assets/images/emoji/soon.png b/app/assets/images/emoji/soon.png
new file mode 100644
index 00000000000..8cdfd86690d
--- /dev/null
+++ b/app/assets/images/emoji/soon.png
Binary files differ
diff --git a/app/assets/images/emoji/sos.png b/app/assets/images/emoji/sos.png
new file mode 100644
index 00000000000..d7d8c9953e4
--- /dev/null
+++ b/app/assets/images/emoji/sos.png
Binary files differ
diff --git a/app/assets/images/emoji/sound.png b/app/assets/images/emoji/sound.png
new file mode 100644
index 00000000000..e75ddca53ba
--- /dev/null
+++ b/app/assets/images/emoji/sound.png
Binary files differ
diff --git a/app/assets/images/emoji/space_invader.png b/app/assets/images/emoji/space_invader.png
new file mode 100644
index 00000000000..2e73f5f32e5
--- /dev/null
+++ b/app/assets/images/emoji/space_invader.png
Binary files differ
diff --git a/app/assets/images/emoji/spades.png b/app/assets/images/emoji/spades.png
new file mode 100644
index 00000000000..f822f184cb0
--- /dev/null
+++ b/app/assets/images/emoji/spades.png
Binary files differ
diff --git a/app/assets/images/emoji/spaghetti.png b/app/assets/images/emoji/spaghetti.png
new file mode 100644
index 00000000000..89c24a321f1
--- /dev/null
+++ b/app/assets/images/emoji/spaghetti.png
Binary files differ
diff --git a/app/assets/images/emoji/sparkle.png b/app/assets/images/emoji/sparkle.png
new file mode 100644
index 00000000000..6aa7b6ec9cf
--- /dev/null
+++ b/app/assets/images/emoji/sparkle.png
Binary files differ
diff --git a/app/assets/images/emoji/sparkler.png b/app/assets/images/emoji/sparkler.png
new file mode 100644
index 00000000000..30339cd6e09
--- /dev/null
+++ b/app/assets/images/emoji/sparkler.png
Binary files differ
diff --git a/app/assets/images/emoji/sparkles.png b/app/assets/images/emoji/sparkles.png
new file mode 100644
index 00000000000..169bc10b023
--- /dev/null
+++ b/app/assets/images/emoji/sparkles.png
Binary files differ
diff --git a/app/assets/images/emoji/sparkling_heart.png b/app/assets/images/emoji/sparkling_heart.png
new file mode 100644
index 00000000000..6709269454e
--- /dev/null
+++ b/app/assets/images/emoji/sparkling_heart.png
Binary files differ
diff --git a/app/assets/images/emoji/speak_no_evil.png b/app/assets/images/emoji/speak_no_evil.png
new file mode 100644
index 00000000000..9d9e07c974b
--- /dev/null
+++ b/app/assets/images/emoji/speak_no_evil.png
Binary files differ
diff --git a/app/assets/images/emoji/speaker.png b/app/assets/images/emoji/speaker.png
new file mode 100644
index 00000000000..7bcffb8fc43
--- /dev/null
+++ b/app/assets/images/emoji/speaker.png
Binary files differ
diff --git a/app/assets/images/emoji/speaking_head.png b/app/assets/images/emoji/speaking_head.png
new file mode 100644
index 00000000000..2df93aaae09
--- /dev/null
+++ b/app/assets/images/emoji/speaking_head.png
Binary files differ
diff --git a/app/assets/images/emoji/speech_balloon.png b/app/assets/images/emoji/speech_balloon.png
new file mode 100644
index 00000000000..a34ef741733
--- /dev/null
+++ b/app/assets/images/emoji/speech_balloon.png
Binary files differ
diff --git a/app/assets/images/emoji/speedboat.png b/app/assets/images/emoji/speedboat.png
new file mode 100644
index 00000000000..74059d12de1
--- /dev/null
+++ b/app/assets/images/emoji/speedboat.png
Binary files differ
diff --git a/app/assets/images/emoji/spider.png b/app/assets/images/emoji/spider.png
new file mode 100644
index 00000000000..3849fa90b94
--- /dev/null
+++ b/app/assets/images/emoji/spider.png
Binary files differ
diff --git a/app/assets/images/emoji/spider_web.png b/app/assets/images/emoji/spider_web.png
new file mode 100644
index 00000000000..ba448ee7fba
--- /dev/null
+++ b/app/assets/images/emoji/spider_web.png
Binary files differ
diff --git a/app/assets/images/emoji/spoon.png b/app/assets/images/emoji/spoon.png
new file mode 100644
index 00000000000..3c4da766aee
--- /dev/null
+++ b/app/assets/images/emoji/spoon.png
Binary files differ
diff --git a/app/assets/images/emoji/spy.png b/app/assets/images/emoji/spy.png
new file mode 100644
index 00000000000..a729e9584d6
--- /dev/null
+++ b/app/assets/images/emoji/spy.png
Binary files differ
diff --git a/app/assets/images/emoji/spy_tone1.png b/app/assets/images/emoji/spy_tone1.png
new file mode 100644
index 00000000000..2d1c022caee
--- /dev/null
+++ b/app/assets/images/emoji/spy_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/spy_tone2.png b/app/assets/images/emoji/spy_tone2.png
new file mode 100644
index 00000000000..548b9c26f5d
--- /dev/null
+++ b/app/assets/images/emoji/spy_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/spy_tone3.png b/app/assets/images/emoji/spy_tone3.png
new file mode 100644
index 00000000000..b023f4b18e1
--- /dev/null
+++ b/app/assets/images/emoji/spy_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/spy_tone4.png b/app/assets/images/emoji/spy_tone4.png
new file mode 100644
index 00000000000..d8300af492d
--- /dev/null
+++ b/app/assets/images/emoji/spy_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/spy_tone5.png b/app/assets/images/emoji/spy_tone5.png
new file mode 100644
index 00000000000..ca1462595fa
--- /dev/null
+++ b/app/assets/images/emoji/spy_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/squid.png b/app/assets/images/emoji/squid.png
new file mode 100644
index 00000000000..d2af223f0cb
--- /dev/null
+++ b/app/assets/images/emoji/squid.png
Binary files differ
diff --git a/app/assets/images/emoji/stadium.png b/app/assets/images/emoji/stadium.png
new file mode 100644
index 00000000000..00cd6db5e29
--- /dev/null
+++ b/app/assets/images/emoji/stadium.png
Binary files differ
diff --git a/app/assets/images/emoji/star.png b/app/assets/images/emoji/star.png
new file mode 100644
index 00000000000..c930947076e
--- /dev/null
+++ b/app/assets/images/emoji/star.png
Binary files differ
diff --git a/app/assets/images/emoji/star2.png b/app/assets/images/emoji/star2.png
new file mode 100644
index 00000000000..2f5cba592db
--- /dev/null
+++ b/app/assets/images/emoji/star2.png
Binary files differ
diff --git a/app/assets/images/emoji/star_and_crescent.png b/app/assets/images/emoji/star_and_crescent.png
new file mode 100644
index 00000000000..e182636457d
--- /dev/null
+++ b/app/assets/images/emoji/star_and_crescent.png
Binary files differ
diff --git a/app/assets/images/emoji/star_of_david.png b/app/assets/images/emoji/star_of_david.png
new file mode 100644
index 00000000000..fc59d0dde24
--- /dev/null
+++ b/app/assets/images/emoji/star_of_david.png
Binary files differ
diff --git a/app/assets/images/emoji/stars.png b/app/assets/images/emoji/stars.png
new file mode 100644
index 00000000000..aa45384d1c6
--- /dev/null
+++ b/app/assets/images/emoji/stars.png
Binary files differ
diff --git a/app/assets/images/emoji/station.png b/app/assets/images/emoji/station.png
new file mode 100644
index 00000000000..5c26fee529c
--- /dev/null
+++ b/app/assets/images/emoji/station.png
Binary files differ
diff --git a/app/assets/images/emoji/statue_of_liberty.png b/app/assets/images/emoji/statue_of_liberty.png
new file mode 100644
index 00000000000..05df8289b59
--- /dev/null
+++ b/app/assets/images/emoji/statue_of_liberty.png
Binary files differ
diff --git a/app/assets/images/emoji/steam_locomotive.png b/app/assets/images/emoji/steam_locomotive.png
new file mode 100644
index 00000000000..9ac0d999c4c
--- /dev/null
+++ b/app/assets/images/emoji/steam_locomotive.png
Binary files differ
diff --git a/app/assets/images/emoji/stew.png b/app/assets/images/emoji/stew.png
new file mode 100644
index 00000000000..6b3f010c17a
--- /dev/null
+++ b/app/assets/images/emoji/stew.png
Binary files differ
diff --git a/app/assets/images/emoji/stop_button.png b/app/assets/images/emoji/stop_button.png
new file mode 100644
index 00000000000..cfa99988ac2
--- /dev/null
+++ b/app/assets/images/emoji/stop_button.png
Binary files differ
diff --git a/app/assets/images/emoji/stopwatch.png b/app/assets/images/emoji/stopwatch.png
new file mode 100644
index 00000000000..8fae1c9a898
--- /dev/null
+++ b/app/assets/images/emoji/stopwatch.png
Binary files differ
diff --git a/app/assets/images/emoji/straight_ruler.png b/app/assets/images/emoji/straight_ruler.png
new file mode 100644
index 00000000000..1017b7433a1
--- /dev/null
+++ b/app/assets/images/emoji/straight_ruler.png
Binary files differ
diff --git a/app/assets/images/emoji/strawberry.png b/app/assets/images/emoji/strawberry.png
new file mode 100644
index 00000000000..7bb86f0b29c
--- /dev/null
+++ b/app/assets/images/emoji/strawberry.png
Binary files differ
diff --git a/app/assets/images/emoji/stuck_out_tongue.png b/app/assets/images/emoji/stuck_out_tongue.png
new file mode 100644
index 00000000000..25757341f96
--- /dev/null
+++ b/app/assets/images/emoji/stuck_out_tongue.png
Binary files differ
diff --git a/app/assets/images/emoji/stuck_out_tongue_closed_eyes.png b/app/assets/images/emoji/stuck_out_tongue_closed_eyes.png
new file mode 100644
index 00000000000..5c0401e9b1d
--- /dev/null
+++ b/app/assets/images/emoji/stuck_out_tongue_closed_eyes.png
Binary files differ
diff --git a/app/assets/images/emoji/stuck_out_tongue_winking_eye.png b/app/assets/images/emoji/stuck_out_tongue_winking_eye.png
new file mode 100644
index 00000000000..4817eaa3dc6
--- /dev/null
+++ b/app/assets/images/emoji/stuck_out_tongue_winking_eye.png
Binary files differ
diff --git a/app/assets/images/emoji/stuffed_flatbread.png b/app/assets/images/emoji/stuffed_flatbread.png
new file mode 100644
index 00000000000..a2e10df40a5
--- /dev/null
+++ b/app/assets/images/emoji/stuffed_flatbread.png
Binary files differ
diff --git a/app/assets/images/emoji/sun_with_face.png b/app/assets/images/emoji/sun_with_face.png
new file mode 100644
index 00000000000..14a4ea971db
--- /dev/null
+++ b/app/assets/images/emoji/sun_with_face.png
Binary files differ
diff --git a/app/assets/images/emoji/sunflower.png b/app/assets/images/emoji/sunflower.png
new file mode 100644
index 00000000000..08cc07761ea
--- /dev/null
+++ b/app/assets/images/emoji/sunflower.png
Binary files differ
diff --git a/app/assets/images/emoji/sunglasses.png b/app/assets/images/emoji/sunglasses.png
new file mode 100644
index 00000000000..20011735110
--- /dev/null
+++ b/app/assets/images/emoji/sunglasses.png
Binary files differ
diff --git a/app/assets/images/emoji/sunny.png b/app/assets/images/emoji/sunny.png
new file mode 100644
index 00000000000..fd521ae31a7
--- /dev/null
+++ b/app/assets/images/emoji/sunny.png
Binary files differ
diff --git a/app/assets/images/emoji/sunrise.png b/app/assets/images/emoji/sunrise.png
new file mode 100644
index 00000000000..4ad36003c20
--- /dev/null
+++ b/app/assets/images/emoji/sunrise.png
Binary files differ
diff --git a/app/assets/images/emoji/sunrise_over_mountains.png b/app/assets/images/emoji/sunrise_over_mountains.png
new file mode 100644
index 00000000000..2b99307344d
--- /dev/null
+++ b/app/assets/images/emoji/sunrise_over_mountains.png
Binary files differ
diff --git a/app/assets/images/emoji/surfer.png b/app/assets/images/emoji/surfer.png
new file mode 100644
index 00000000000..3ab017adf4b
--- /dev/null
+++ b/app/assets/images/emoji/surfer.png
Binary files differ
diff --git a/app/assets/images/emoji/surfer_tone1.png b/app/assets/images/emoji/surfer_tone1.png
new file mode 100644
index 00000000000..b5faaa524cc
--- /dev/null
+++ b/app/assets/images/emoji/surfer_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/surfer_tone2.png b/app/assets/images/emoji/surfer_tone2.png
new file mode 100644
index 00000000000..6d92e412ff1
--- /dev/null
+++ b/app/assets/images/emoji/surfer_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/surfer_tone3.png b/app/assets/images/emoji/surfer_tone3.png
new file mode 100644
index 00000000000..f05ef59496e
--- /dev/null
+++ b/app/assets/images/emoji/surfer_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/surfer_tone4.png b/app/assets/images/emoji/surfer_tone4.png
new file mode 100644
index 00000000000..35e143d19dc
--- /dev/null
+++ b/app/assets/images/emoji/surfer_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/surfer_tone5.png b/app/assets/images/emoji/surfer_tone5.png
new file mode 100644
index 00000000000..38917658eac
--- /dev/null
+++ b/app/assets/images/emoji/surfer_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/sushi.png b/app/assets/images/emoji/sushi.png
new file mode 100644
index 00000000000..f171fd2f7a1
--- /dev/null
+++ b/app/assets/images/emoji/sushi.png
Binary files differ
diff --git a/app/assets/images/emoji/suspension_railway.png b/app/assets/images/emoji/suspension_railway.png
new file mode 100644
index 00000000000..a59d5f48c24
--- /dev/null
+++ b/app/assets/images/emoji/suspension_railway.png
Binary files differ
diff --git a/app/assets/images/emoji/sweat.png b/app/assets/images/emoji/sweat.png
new file mode 100644
index 00000000000..f0dae7b7893
--- /dev/null
+++ b/app/assets/images/emoji/sweat.png
Binary files differ
diff --git a/app/assets/images/emoji/sweat_drops.png b/app/assets/images/emoji/sweat_drops.png
new file mode 100644
index 00000000000..4106117ebc8
--- /dev/null
+++ b/app/assets/images/emoji/sweat_drops.png
Binary files differ
diff --git a/app/assets/images/emoji/sweat_smile.png b/app/assets/images/emoji/sweat_smile.png
new file mode 100644
index 00000000000..cb18d9c899b
--- /dev/null
+++ b/app/assets/images/emoji/sweat_smile.png
Binary files differ
diff --git a/app/assets/images/emoji/sweet_potato.png b/app/assets/images/emoji/sweet_potato.png
new file mode 100644
index 00000000000..92a425f2e20
--- /dev/null
+++ b/app/assets/images/emoji/sweet_potato.png
Binary files differ
diff --git a/app/assets/images/emoji/swimmer.png b/app/assets/images/emoji/swimmer.png
new file mode 100644
index 00000000000..55b4d72f9a7
--- /dev/null
+++ b/app/assets/images/emoji/swimmer.png
Binary files differ
diff --git a/app/assets/images/emoji/swimmer_tone1.png b/app/assets/images/emoji/swimmer_tone1.png
new file mode 100644
index 00000000000..38441c9ca9a
--- /dev/null
+++ b/app/assets/images/emoji/swimmer_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/swimmer_tone2.png b/app/assets/images/emoji/swimmer_tone2.png
new file mode 100644
index 00000000000..b0d43112444
--- /dev/null
+++ b/app/assets/images/emoji/swimmer_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/swimmer_tone3.png b/app/assets/images/emoji/swimmer_tone3.png
new file mode 100644
index 00000000000..211e77e2aa0
--- /dev/null
+++ b/app/assets/images/emoji/swimmer_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/swimmer_tone4.png b/app/assets/images/emoji/swimmer_tone4.png
new file mode 100644
index 00000000000..f34c34db9d2
--- /dev/null
+++ b/app/assets/images/emoji/swimmer_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/swimmer_tone5.png b/app/assets/images/emoji/swimmer_tone5.png
new file mode 100644
index 00000000000..3e9231ff868
--- /dev/null
+++ b/app/assets/images/emoji/swimmer_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/symbols.png b/app/assets/images/emoji/symbols.png
new file mode 100644
index 00000000000..ac2fc1f358f
--- /dev/null
+++ b/app/assets/images/emoji/symbols.png
Binary files differ
diff --git a/app/assets/images/emoji/synagogue.png b/app/assets/images/emoji/synagogue.png
new file mode 100644
index 00000000000..ee347904c80
--- /dev/null
+++ b/app/assets/images/emoji/synagogue.png
Binary files differ
diff --git a/app/assets/images/emoji/syringe.png b/app/assets/images/emoji/syringe.png
new file mode 100644
index 00000000000..71c1a9528d5
--- /dev/null
+++ b/app/assets/images/emoji/syringe.png
Binary files differ
diff --git a/app/assets/images/emoji/taco.png b/app/assets/images/emoji/taco.png
new file mode 100644
index 00000000000..10e847a4619
--- /dev/null
+++ b/app/assets/images/emoji/taco.png
Binary files differ
diff --git a/app/assets/images/emoji/tada.png b/app/assets/images/emoji/tada.png
new file mode 100644
index 00000000000..0244d60f269
--- /dev/null
+++ b/app/assets/images/emoji/tada.png
Binary files differ
diff --git a/app/assets/images/emoji/tanabata_tree.png b/app/assets/images/emoji/tanabata_tree.png
new file mode 100644
index 00000000000..46fcb3a1aac
--- /dev/null
+++ b/app/assets/images/emoji/tanabata_tree.png
Binary files differ
diff --git a/app/assets/images/emoji/tangerine.png b/app/assets/images/emoji/tangerine.png
new file mode 100644
index 00000000000..ab14e5378db
--- /dev/null
+++ b/app/assets/images/emoji/tangerine.png
Binary files differ
diff --git a/app/assets/images/emoji/taurus.png b/app/assets/images/emoji/taurus.png
new file mode 100644
index 00000000000..b2a370df42b
--- /dev/null
+++ b/app/assets/images/emoji/taurus.png
Binary files differ
diff --git a/app/assets/images/emoji/taxi.png b/app/assets/images/emoji/taxi.png
new file mode 100644
index 00000000000..55f4cc84797
--- /dev/null
+++ b/app/assets/images/emoji/taxi.png
Binary files differ
diff --git a/app/assets/images/emoji/tea.png b/app/assets/images/emoji/tea.png
new file mode 100644
index 00000000000..b53b98f0c45
--- /dev/null
+++ b/app/assets/images/emoji/tea.png
Binary files differ
diff --git a/app/assets/images/emoji/telephone.png b/app/assets/images/emoji/telephone.png
new file mode 100644
index 00000000000..a1e69f566bc
--- /dev/null
+++ b/app/assets/images/emoji/telephone.png
Binary files differ
diff --git a/app/assets/images/emoji/telephone_receiver.png b/app/assets/images/emoji/telephone_receiver.png
new file mode 100644
index 00000000000..69388316c35
--- /dev/null
+++ b/app/assets/images/emoji/telephone_receiver.png
Binary files differ
diff --git a/app/assets/images/emoji/telescope.png b/app/assets/images/emoji/telescope.png
new file mode 100644
index 00000000000..d63154614b5
--- /dev/null
+++ b/app/assets/images/emoji/telescope.png
Binary files differ
diff --git a/app/assets/images/emoji/ten.png b/app/assets/images/emoji/ten.png
new file mode 100644
index 00000000000..782d4004962
--- /dev/null
+++ b/app/assets/images/emoji/ten.png
Binary files differ
diff --git a/app/assets/images/emoji/tennis.png b/app/assets/images/emoji/tennis.png
new file mode 100644
index 00000000000..7e68ba8f301
--- /dev/null
+++ b/app/assets/images/emoji/tennis.png
Binary files differ
diff --git a/app/assets/images/emoji/tent.png b/app/assets/images/emoji/tent.png
new file mode 100644
index 00000000000..3fddcfc56eb
--- /dev/null
+++ b/app/assets/images/emoji/tent.png
Binary files differ
diff --git a/app/assets/images/emoji/thermometer.png b/app/assets/images/emoji/thermometer.png
new file mode 100644
index 00000000000..b1147392426
--- /dev/null
+++ b/app/assets/images/emoji/thermometer.png
Binary files differ
diff --git a/app/assets/images/emoji/thermometer_face.png b/app/assets/images/emoji/thermometer_face.png
new file mode 100644
index 00000000000..8fc57387563
--- /dev/null
+++ b/app/assets/images/emoji/thermometer_face.png
Binary files differ
diff --git a/app/assets/images/emoji/thinking.png b/app/assets/images/emoji/thinking.png
new file mode 100644
index 00000000000..c18f6fd14ad
--- /dev/null
+++ b/app/assets/images/emoji/thinking.png
Binary files differ
diff --git a/app/assets/images/emoji/third_place.png b/app/assets/images/emoji/third_place.png
new file mode 100644
index 00000000000..636e04a5950
--- /dev/null
+++ b/app/assets/images/emoji/third_place.png
Binary files differ
diff --git a/app/assets/images/emoji/thought_balloon.png b/app/assets/images/emoji/thought_balloon.png
new file mode 100644
index 00000000000..72fe8fa7022
--- /dev/null
+++ b/app/assets/images/emoji/thought_balloon.png
Binary files differ
diff --git a/app/assets/images/emoji/three.png b/app/assets/images/emoji/three.png
new file mode 100644
index 00000000000..dbaa6183e72
--- /dev/null
+++ b/app/assets/images/emoji/three.png
Binary files differ
diff --git a/app/assets/images/emoji/thumbsdown.png b/app/assets/images/emoji/thumbsdown.png
new file mode 100644
index 00000000000..b63da2f20a8
--- /dev/null
+++ b/app/assets/images/emoji/thumbsdown.png
Binary files differ
diff --git a/app/assets/images/emoji/thumbsdown_tone1.png b/app/assets/images/emoji/thumbsdown_tone1.png
new file mode 100644
index 00000000000..a1631af8e92
--- /dev/null
+++ b/app/assets/images/emoji/thumbsdown_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/thumbsdown_tone2.png b/app/assets/images/emoji/thumbsdown_tone2.png
new file mode 100644
index 00000000000..85fff82d595
--- /dev/null
+++ b/app/assets/images/emoji/thumbsdown_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/thumbsdown_tone3.png b/app/assets/images/emoji/thumbsdown_tone3.png
new file mode 100644
index 00000000000..eeba3be80fd
--- /dev/null
+++ b/app/assets/images/emoji/thumbsdown_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/thumbsdown_tone4.png b/app/assets/images/emoji/thumbsdown_tone4.png
new file mode 100644
index 00000000000..1addafdaed0
--- /dev/null
+++ b/app/assets/images/emoji/thumbsdown_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/thumbsdown_tone5.png b/app/assets/images/emoji/thumbsdown_tone5.png
new file mode 100644
index 00000000000..37ec07b5721
--- /dev/null
+++ b/app/assets/images/emoji/thumbsdown_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/thumbsup.png b/app/assets/images/emoji/thumbsup.png
new file mode 100644
index 00000000000..f9e6f13a34f
--- /dev/null
+++ b/app/assets/images/emoji/thumbsup.png
Binary files differ
diff --git a/app/assets/images/emoji/thumbsup_tone1.png b/app/assets/images/emoji/thumbsup_tone1.png
new file mode 100644
index 00000000000..39684cd5cc7
--- /dev/null
+++ b/app/assets/images/emoji/thumbsup_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/thumbsup_tone2.png b/app/assets/images/emoji/thumbsup_tone2.png
new file mode 100644
index 00000000000..a9b59723573
--- /dev/null
+++ b/app/assets/images/emoji/thumbsup_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/thumbsup_tone3.png b/app/assets/images/emoji/thumbsup_tone3.png
new file mode 100644
index 00000000000..c5e29167015
--- /dev/null
+++ b/app/assets/images/emoji/thumbsup_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/thumbsup_tone4.png b/app/assets/images/emoji/thumbsup_tone4.png
new file mode 100644
index 00000000000..5bf4857a884
--- /dev/null
+++ b/app/assets/images/emoji/thumbsup_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/thumbsup_tone5.png b/app/assets/images/emoji/thumbsup_tone5.png
new file mode 100644
index 00000000000..d829f787c61
--- /dev/null
+++ b/app/assets/images/emoji/thumbsup_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/thunder_cloud_rain.png b/app/assets/images/emoji/thunder_cloud_rain.png
new file mode 100644
index 00000000000..31a26a1b6ee
--- /dev/null
+++ b/app/assets/images/emoji/thunder_cloud_rain.png
Binary files differ
diff --git a/app/assets/images/emoji/ticket.png b/app/assets/images/emoji/ticket.png
new file mode 100644
index 00000000000..605936bb6b3
--- /dev/null
+++ b/app/assets/images/emoji/ticket.png
Binary files differ
diff --git a/app/assets/images/emoji/tickets.png b/app/assets/images/emoji/tickets.png
new file mode 100644
index 00000000000..e510f4a7a50
--- /dev/null
+++ b/app/assets/images/emoji/tickets.png
Binary files differ
diff --git a/app/assets/images/emoji/tiger.png b/app/assets/images/emoji/tiger.png
new file mode 100644
index 00000000000..a4d3ef086d4
--- /dev/null
+++ b/app/assets/images/emoji/tiger.png
Binary files differ
diff --git a/app/assets/images/emoji/tiger2.png b/app/assets/images/emoji/tiger2.png
new file mode 100644
index 00000000000..871a8b74d56
--- /dev/null
+++ b/app/assets/images/emoji/tiger2.png
Binary files differ
diff --git a/app/assets/images/emoji/timer.png b/app/assets/images/emoji/timer.png
new file mode 100644
index 00000000000..8a3be574c24
--- /dev/null
+++ b/app/assets/images/emoji/timer.png
Binary files differ
diff --git a/app/assets/images/emoji/tired_face.png b/app/assets/images/emoji/tired_face.png
new file mode 100644
index 00000000000..4e01eff5b23
--- /dev/null
+++ b/app/assets/images/emoji/tired_face.png
Binary files differ
diff --git a/app/assets/images/emoji/tm.png b/app/assets/images/emoji/tm.png
new file mode 100644
index 00000000000..7a0c44a2c2b
--- /dev/null
+++ b/app/assets/images/emoji/tm.png
Binary files differ
diff --git a/app/assets/images/emoji/toilet.png b/app/assets/images/emoji/toilet.png
new file mode 100644
index 00000000000..1392f761835
--- /dev/null
+++ b/app/assets/images/emoji/toilet.png
Binary files differ
diff --git a/app/assets/images/emoji/tokyo_tower.png b/app/assets/images/emoji/tokyo_tower.png
new file mode 100644
index 00000000000..37df7fc65b1
--- /dev/null
+++ b/app/assets/images/emoji/tokyo_tower.png
Binary files differ
diff --git a/app/assets/images/emoji/tomato.png b/app/assets/images/emoji/tomato.png
new file mode 100644
index 00000000000..497da8f6b22
--- /dev/null
+++ b/app/assets/images/emoji/tomato.png
Binary files differ
diff --git a/app/assets/images/emoji/tone1.png b/app/assets/images/emoji/tone1.png
new file mode 100644
index 00000000000..c395f3d0d68
--- /dev/null
+++ b/app/assets/images/emoji/tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/tone2.png b/app/assets/images/emoji/tone2.png
new file mode 100644
index 00000000000..080847431c1
--- /dev/null
+++ b/app/assets/images/emoji/tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/tone3.png b/app/assets/images/emoji/tone3.png
new file mode 100644
index 00000000000..482dd403475
--- /dev/null
+++ b/app/assets/images/emoji/tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/tone4.png b/app/assets/images/emoji/tone4.png
new file mode 100644
index 00000000000..5cae8bb20b0
--- /dev/null
+++ b/app/assets/images/emoji/tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/tone5.png b/app/assets/images/emoji/tone5.png
new file mode 100644
index 00000000000..49d1a8c3a64
--- /dev/null
+++ b/app/assets/images/emoji/tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/tongue.png b/app/assets/images/emoji/tongue.png
new file mode 100644
index 00000000000..70ce9c1225f
--- /dev/null
+++ b/app/assets/images/emoji/tongue.png
Binary files differ
diff --git a/app/assets/images/emoji/tools.png b/app/assets/images/emoji/tools.png
new file mode 100644
index 00000000000..3c6049273a9
--- /dev/null
+++ b/app/assets/images/emoji/tools.png
Binary files differ
diff --git a/app/assets/images/emoji/top.png b/app/assets/images/emoji/top.png
new file mode 100644
index 00000000000..49dea8c08b5
--- /dev/null
+++ b/app/assets/images/emoji/top.png
Binary files differ
diff --git a/app/assets/images/emoji/tophat.png b/app/assets/images/emoji/tophat.png
new file mode 100644
index 00000000000..131b657b109
--- /dev/null
+++ b/app/assets/images/emoji/tophat.png
Binary files differ
diff --git a/app/assets/images/emoji/track_next.png b/app/assets/images/emoji/track_next.png
new file mode 100644
index 00000000000..f8880d33bab
--- /dev/null
+++ b/app/assets/images/emoji/track_next.png
Binary files differ
diff --git a/app/assets/images/emoji/track_previous.png b/app/assets/images/emoji/track_previous.png
new file mode 100644
index 00000000000..1ffd0566cfc
--- /dev/null
+++ b/app/assets/images/emoji/track_previous.png
Binary files differ
diff --git a/app/assets/images/emoji/trackball.png b/app/assets/images/emoji/trackball.png
new file mode 100644
index 00000000000..3bea84ad7ce
--- /dev/null
+++ b/app/assets/images/emoji/trackball.png
Binary files differ
diff --git a/app/assets/images/emoji/tractor.png b/app/assets/images/emoji/tractor.png
new file mode 100644
index 00000000000..c1bf8cae44f
--- /dev/null
+++ b/app/assets/images/emoji/tractor.png
Binary files differ
diff --git a/app/assets/images/emoji/traffic_light.png b/app/assets/images/emoji/traffic_light.png
new file mode 100644
index 00000000000..6b312285b00
--- /dev/null
+++ b/app/assets/images/emoji/traffic_light.png
Binary files differ
diff --git a/app/assets/images/emoji/train.png b/app/assets/images/emoji/train.png
new file mode 100644
index 00000000000..3c80321f7e8
--- /dev/null
+++ b/app/assets/images/emoji/train.png
Binary files differ
diff --git a/app/assets/images/emoji/train2.png b/app/assets/images/emoji/train2.png
new file mode 100644
index 00000000000..367c7bc5d39
--- /dev/null
+++ b/app/assets/images/emoji/train2.png
Binary files differ
diff --git a/app/assets/images/emoji/tram.png b/app/assets/images/emoji/tram.png
new file mode 100644
index 00000000000..b6f0e69038f
--- /dev/null
+++ b/app/assets/images/emoji/tram.png
Binary files differ
diff --git a/app/assets/images/emoji/triangular_flag_on_post.png b/app/assets/images/emoji/triangular_flag_on_post.png
new file mode 100644
index 00000000000..c12d8b06886
--- /dev/null
+++ b/app/assets/images/emoji/triangular_flag_on_post.png
Binary files differ
diff --git a/app/assets/images/emoji/triangular_ruler.png b/app/assets/images/emoji/triangular_ruler.png
new file mode 100644
index 00000000000..77dee9ee843
--- /dev/null
+++ b/app/assets/images/emoji/triangular_ruler.png
Binary files differ
diff --git a/app/assets/images/emoji/trident.png b/app/assets/images/emoji/trident.png
new file mode 100644
index 00000000000..777a1dad121
--- /dev/null
+++ b/app/assets/images/emoji/trident.png
Binary files differ
diff --git a/app/assets/images/emoji/triumph.png b/app/assets/images/emoji/triumph.png
new file mode 100644
index 00000000000..0be7a501969
--- /dev/null
+++ b/app/assets/images/emoji/triumph.png
Binary files differ
diff --git a/app/assets/images/emoji/trolleybus.png b/app/assets/images/emoji/trolleybus.png
new file mode 100644
index 00000000000..139a9931b52
--- /dev/null
+++ b/app/assets/images/emoji/trolleybus.png
Binary files differ
diff --git a/app/assets/images/emoji/trophy.png b/app/assets/images/emoji/trophy.png
new file mode 100644
index 00000000000..ac2895c1896
--- /dev/null
+++ b/app/assets/images/emoji/trophy.png
Binary files differ
diff --git a/app/assets/images/emoji/tropical_drink.png b/app/assets/images/emoji/tropical_drink.png
new file mode 100644
index 00000000000..cd714f81b36
--- /dev/null
+++ b/app/assets/images/emoji/tropical_drink.png
Binary files differ
diff --git a/app/assets/images/emoji/tropical_fish.png b/app/assets/images/emoji/tropical_fish.png
new file mode 100644
index 00000000000..252105235a6
--- /dev/null
+++ b/app/assets/images/emoji/tropical_fish.png
Binary files differ
diff --git a/app/assets/images/emoji/truck.png b/app/assets/images/emoji/truck.png
new file mode 100644
index 00000000000..130de047f8b
--- /dev/null
+++ b/app/assets/images/emoji/truck.png
Binary files differ
diff --git a/app/assets/images/emoji/trumpet.png b/app/assets/images/emoji/trumpet.png
new file mode 100644
index 00000000000..864ccbcd04a
--- /dev/null
+++ b/app/assets/images/emoji/trumpet.png
Binary files differ
diff --git a/app/assets/images/emoji/tulip.png b/app/assets/images/emoji/tulip.png
new file mode 100644
index 00000000000..f799d75c182
--- /dev/null
+++ b/app/assets/images/emoji/tulip.png
Binary files differ
diff --git a/app/assets/images/emoji/tumbler_glass.png b/app/assets/images/emoji/tumbler_glass.png
new file mode 100644
index 00000000000..7bf09229879
--- /dev/null
+++ b/app/assets/images/emoji/tumbler_glass.png
Binary files differ
diff --git a/app/assets/images/emoji/turkey.png b/app/assets/images/emoji/turkey.png
new file mode 100644
index 00000000000..344af94c9ec
--- /dev/null
+++ b/app/assets/images/emoji/turkey.png
Binary files differ
diff --git a/app/assets/images/emoji/turtle.png b/app/assets/images/emoji/turtle.png
new file mode 100644
index 00000000000..c22f7519fe8
--- /dev/null
+++ b/app/assets/images/emoji/turtle.png
Binary files differ
diff --git a/app/assets/images/emoji/tv.png b/app/assets/images/emoji/tv.png
new file mode 100644
index 00000000000..999f1fb5c6d
--- /dev/null
+++ b/app/assets/images/emoji/tv.png
Binary files differ
diff --git a/app/assets/images/emoji/twisted_rightwards_arrows.png b/app/assets/images/emoji/twisted_rightwards_arrows.png
new file mode 100644
index 00000000000..5904badde65
--- /dev/null
+++ b/app/assets/images/emoji/twisted_rightwards_arrows.png
Binary files differ
diff --git a/app/assets/images/emoji/two.png b/app/assets/images/emoji/two.png
new file mode 100644
index 00000000000..927339c9bff
--- /dev/null
+++ b/app/assets/images/emoji/two.png
Binary files differ
diff --git a/app/assets/images/emoji/two_hearts.png b/app/assets/images/emoji/two_hearts.png
new file mode 100644
index 00000000000..4d8c3386042
--- /dev/null
+++ b/app/assets/images/emoji/two_hearts.png
Binary files differ
diff --git a/app/assets/images/emoji/two_men_holding_hands.png b/app/assets/images/emoji/two_men_holding_hands.png
new file mode 100644
index 00000000000..a511fda822a
--- /dev/null
+++ b/app/assets/images/emoji/two_men_holding_hands.png
Binary files differ
diff --git a/app/assets/images/emoji/two_women_holding_hands.png b/app/assets/images/emoji/two_women_holding_hands.png
new file mode 100644
index 00000000000..b077cd3e40f
--- /dev/null
+++ b/app/assets/images/emoji/two_women_holding_hands.png
Binary files differ
diff --git a/app/assets/images/emoji/u5272.png b/app/assets/images/emoji/u5272.png
new file mode 100644
index 00000000000..c4f837fe684
--- /dev/null
+++ b/app/assets/images/emoji/u5272.png
Binary files differ
diff --git a/app/assets/images/emoji/u5408.png b/app/assets/images/emoji/u5408.png
new file mode 100644
index 00000000000..8375ad9d9af
--- /dev/null
+++ b/app/assets/images/emoji/u5408.png
Binary files differ
diff --git a/app/assets/images/emoji/u55b6.png b/app/assets/images/emoji/u55b6.png
new file mode 100644
index 00000000000..d21cb30eaf3
--- /dev/null
+++ b/app/assets/images/emoji/u55b6.png
Binary files differ
diff --git a/app/assets/images/emoji/u6307.png b/app/assets/images/emoji/u6307.png
new file mode 100644
index 00000000000..078e23e4ff3
--- /dev/null
+++ b/app/assets/images/emoji/u6307.png
Binary files differ
diff --git a/app/assets/images/emoji/u6708.png b/app/assets/images/emoji/u6708.png
new file mode 100644
index 00000000000..c41bd36a26a
--- /dev/null
+++ b/app/assets/images/emoji/u6708.png
Binary files differ
diff --git a/app/assets/images/emoji/u6709.png b/app/assets/images/emoji/u6709.png
new file mode 100644
index 00000000000..a4510de41c0
--- /dev/null
+++ b/app/assets/images/emoji/u6709.png
Binary files differ
diff --git a/app/assets/images/emoji/u6e80.png b/app/assets/images/emoji/u6e80.png
new file mode 100644
index 00000000000..f9dea8b8833
--- /dev/null
+++ b/app/assets/images/emoji/u6e80.png
Binary files differ
diff --git a/app/assets/images/emoji/u7121.png b/app/assets/images/emoji/u7121.png
new file mode 100644
index 00000000000..d3a19b420de
--- /dev/null
+++ b/app/assets/images/emoji/u7121.png
Binary files differ
diff --git a/app/assets/images/emoji/u7533.png b/app/assets/images/emoji/u7533.png
new file mode 100644
index 00000000000..6b7af0ee222
--- /dev/null
+++ b/app/assets/images/emoji/u7533.png
Binary files differ
diff --git a/app/assets/images/emoji/u7981.png b/app/assets/images/emoji/u7981.png
new file mode 100644
index 00000000000..4c704e03433
--- /dev/null
+++ b/app/assets/images/emoji/u7981.png
Binary files differ
diff --git a/app/assets/images/emoji/u7a7a.png b/app/assets/images/emoji/u7a7a.png
new file mode 100644
index 00000000000..47966c1ea93
--- /dev/null
+++ b/app/assets/images/emoji/u7a7a.png
Binary files differ
diff --git a/app/assets/images/emoji/umbrella.png b/app/assets/images/emoji/umbrella.png
new file mode 100644
index 00000000000..5b35b7ff6a4
--- /dev/null
+++ b/app/assets/images/emoji/umbrella.png
Binary files differ
diff --git a/app/assets/images/emoji/umbrella2.png b/app/assets/images/emoji/umbrella2.png
new file mode 100644
index 00000000000..97fe859e74f
--- /dev/null
+++ b/app/assets/images/emoji/umbrella2.png
Binary files differ
diff --git a/app/assets/images/emoji/unamused.png b/app/assets/images/emoji/unamused.png
new file mode 100644
index 00000000000..25e3677f2eb
--- /dev/null
+++ b/app/assets/images/emoji/unamused.png
Binary files differ
diff --git a/app/assets/images/emoji/underage.png b/app/assets/images/emoji/underage.png
new file mode 100644
index 00000000000..6dfe6da51e2
--- /dev/null
+++ b/app/assets/images/emoji/underage.png
Binary files differ
diff --git a/app/assets/images/emoji/unicorn.png b/app/assets/images/emoji/unicorn.png
new file mode 100644
index 00000000000..05a97969f7e
--- /dev/null
+++ b/app/assets/images/emoji/unicorn.png
Binary files differ
diff --git a/app/assets/images/emoji/unlock.png b/app/assets/images/emoji/unlock.png
new file mode 100644
index 00000000000..4a74a693911
--- /dev/null
+++ b/app/assets/images/emoji/unlock.png
Binary files differ
diff --git a/app/assets/images/emoji/up.png b/app/assets/images/emoji/up.png
new file mode 100644
index 00000000000..0d42142ba04
--- /dev/null
+++ b/app/assets/images/emoji/up.png
Binary files differ
diff --git a/app/assets/images/emoji/upside_down.png b/app/assets/images/emoji/upside_down.png
new file mode 100644
index 00000000000..128f31c9828
--- /dev/null
+++ b/app/assets/images/emoji/upside_down.png
Binary files differ
diff --git a/app/assets/images/emoji/urn.png b/app/assets/images/emoji/urn.png
new file mode 100644
index 00000000000..6b5b3503438
--- /dev/null
+++ b/app/assets/images/emoji/urn.png
Binary files differ
diff --git a/app/assets/images/emoji/v.png b/app/assets/images/emoji/v.png
new file mode 100644
index 00000000000..70c5516ffee
--- /dev/null
+++ b/app/assets/images/emoji/v.png
Binary files differ
diff --git a/app/assets/images/emoji/v_tone1.png b/app/assets/images/emoji/v_tone1.png
new file mode 100644
index 00000000000..6ac54a745f4
--- /dev/null
+++ b/app/assets/images/emoji/v_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/v_tone2.png b/app/assets/images/emoji/v_tone2.png
new file mode 100644
index 00000000000..6dd9669866d
--- /dev/null
+++ b/app/assets/images/emoji/v_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/v_tone3.png b/app/assets/images/emoji/v_tone3.png
new file mode 100644
index 00000000000..a615e53f02f
--- /dev/null
+++ b/app/assets/images/emoji/v_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/v_tone4.png b/app/assets/images/emoji/v_tone4.png
new file mode 100644
index 00000000000..33a34bd5a78
--- /dev/null
+++ b/app/assets/images/emoji/v_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/v_tone5.png b/app/assets/images/emoji/v_tone5.png
new file mode 100644
index 00000000000..45ad14b6c9c
--- /dev/null
+++ b/app/assets/images/emoji/v_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/vertical_traffic_light.png b/app/assets/images/emoji/vertical_traffic_light.png
new file mode 100644
index 00000000000..8085973eecf
--- /dev/null
+++ b/app/assets/images/emoji/vertical_traffic_light.png
Binary files differ
diff --git a/app/assets/images/emoji/vhs.png b/app/assets/images/emoji/vhs.png
new file mode 100644
index 00000000000..b9eb78ecd92
--- /dev/null
+++ b/app/assets/images/emoji/vhs.png
Binary files differ
diff --git a/app/assets/images/emoji/vibration_mode.png b/app/assets/images/emoji/vibration_mode.png
new file mode 100644
index 00000000000..cc46510e48e
--- /dev/null
+++ b/app/assets/images/emoji/vibration_mode.png
Binary files differ
diff --git a/app/assets/images/emoji/video_camera.png b/app/assets/images/emoji/video_camera.png
new file mode 100644
index 00000000000..85b300d425c
--- /dev/null
+++ b/app/assets/images/emoji/video_camera.png
Binary files differ
diff --git a/app/assets/images/emoji/video_game.png b/app/assets/images/emoji/video_game.png
new file mode 100644
index 00000000000..316a9106a55
--- /dev/null
+++ b/app/assets/images/emoji/video_game.png
Binary files differ
diff --git a/app/assets/images/emoji/violin.png b/app/assets/images/emoji/violin.png
new file mode 100644
index 00000000000..e1e76cce242
--- /dev/null
+++ b/app/assets/images/emoji/violin.png
Binary files differ
diff --git a/app/assets/images/emoji/virgo.png b/app/assets/images/emoji/virgo.png
new file mode 100644
index 00000000000..a6b56c2cb5e
--- /dev/null
+++ b/app/assets/images/emoji/virgo.png
Binary files differ
diff --git a/app/assets/images/emoji/volcano.png b/app/assets/images/emoji/volcano.png
new file mode 100644
index 00000000000..931d569294c
--- /dev/null
+++ b/app/assets/images/emoji/volcano.png
Binary files differ
diff --git a/app/assets/images/emoji/volleyball.png b/app/assets/images/emoji/volleyball.png
new file mode 100644
index 00000000000..7a0e49d4b07
--- /dev/null
+++ b/app/assets/images/emoji/volleyball.png
Binary files differ
diff --git a/app/assets/images/emoji/vs.png b/app/assets/images/emoji/vs.png
new file mode 100644
index 00000000000..e1180f4a464
--- /dev/null
+++ b/app/assets/images/emoji/vs.png
Binary files differ
diff --git a/app/assets/images/emoji/vulcan.png b/app/assets/images/emoji/vulcan.png
new file mode 100644
index 00000000000..54728bcaf5c
--- /dev/null
+++ b/app/assets/images/emoji/vulcan.png
Binary files differ
diff --git a/app/assets/images/emoji/vulcan_tone1.png b/app/assets/images/emoji/vulcan_tone1.png
new file mode 100644
index 00000000000..8aff5d8fa16
--- /dev/null
+++ b/app/assets/images/emoji/vulcan_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/vulcan_tone2.png b/app/assets/images/emoji/vulcan_tone2.png
new file mode 100644
index 00000000000..82b7ad519b4
--- /dev/null
+++ b/app/assets/images/emoji/vulcan_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/vulcan_tone3.png b/app/assets/images/emoji/vulcan_tone3.png
new file mode 100644
index 00000000000..d1400e1dd28
--- /dev/null
+++ b/app/assets/images/emoji/vulcan_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/vulcan_tone4.png b/app/assets/images/emoji/vulcan_tone4.png
new file mode 100644
index 00000000000..47e2b280148
--- /dev/null
+++ b/app/assets/images/emoji/vulcan_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/vulcan_tone5.png b/app/assets/images/emoji/vulcan_tone5.png
new file mode 100644
index 00000000000..60b5c6077be
--- /dev/null
+++ b/app/assets/images/emoji/vulcan_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/walking.png b/app/assets/images/emoji/walking.png
new file mode 100644
index 00000000000..06dc169a3fd
--- /dev/null
+++ b/app/assets/images/emoji/walking.png
Binary files differ
diff --git a/app/assets/images/emoji/walking_tone1.png b/app/assets/images/emoji/walking_tone1.png
new file mode 100644
index 00000000000..4e391b45a0b
--- /dev/null
+++ b/app/assets/images/emoji/walking_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/walking_tone2.png b/app/assets/images/emoji/walking_tone2.png
new file mode 100644
index 00000000000..31f94a1bce1
--- /dev/null
+++ b/app/assets/images/emoji/walking_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/walking_tone3.png b/app/assets/images/emoji/walking_tone3.png
new file mode 100644
index 00000000000..f7ed8e39c2e
--- /dev/null
+++ b/app/assets/images/emoji/walking_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/walking_tone4.png b/app/assets/images/emoji/walking_tone4.png
new file mode 100644
index 00000000000..e58dc04c7b2
--- /dev/null
+++ b/app/assets/images/emoji/walking_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/walking_tone5.png b/app/assets/images/emoji/walking_tone5.png
new file mode 100644
index 00000000000..ba4e1b58fcb
--- /dev/null
+++ b/app/assets/images/emoji/walking_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/waning_crescent_moon.png b/app/assets/images/emoji/waning_crescent_moon.png
new file mode 100644
index 00000000000..cf68706b871
--- /dev/null
+++ b/app/assets/images/emoji/waning_crescent_moon.png
Binary files differ
diff --git a/app/assets/images/emoji/waning_gibbous_moon.png b/app/assets/images/emoji/waning_gibbous_moon.png
new file mode 100644
index 00000000000..24e16266119
--- /dev/null
+++ b/app/assets/images/emoji/waning_gibbous_moon.png
Binary files differ
diff --git a/app/assets/images/emoji/warning.png b/app/assets/images/emoji/warning.png
new file mode 100644
index 00000000000..35691c2ed97
--- /dev/null
+++ b/app/assets/images/emoji/warning.png
Binary files differ
diff --git a/app/assets/images/emoji/wastebasket.png b/app/assets/images/emoji/wastebasket.png
new file mode 100644
index 00000000000..2b3c484b498
--- /dev/null
+++ b/app/assets/images/emoji/wastebasket.png
Binary files differ
diff --git a/app/assets/images/emoji/watch.png b/app/assets/images/emoji/watch.png
new file mode 100644
index 00000000000..64819bc6e21
--- /dev/null
+++ b/app/assets/images/emoji/watch.png
Binary files differ
diff --git a/app/assets/images/emoji/water_buffalo.png b/app/assets/images/emoji/water_buffalo.png
new file mode 100644
index 00000000000..80446615caf
--- /dev/null
+++ b/app/assets/images/emoji/water_buffalo.png
Binary files differ
diff --git a/app/assets/images/emoji/water_polo.png b/app/assets/images/emoji/water_polo.png
new file mode 100644
index 00000000000..cb44576780d
--- /dev/null
+++ b/app/assets/images/emoji/water_polo.png
Binary files differ
diff --git a/app/assets/images/emoji/water_polo_tone1.png b/app/assets/images/emoji/water_polo_tone1.png
new file mode 100644
index 00000000000..bed1a908d6a
--- /dev/null
+++ b/app/assets/images/emoji/water_polo_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/water_polo_tone2.png b/app/assets/images/emoji/water_polo_tone2.png
new file mode 100644
index 00000000000..ec5a43b4d4a
--- /dev/null
+++ b/app/assets/images/emoji/water_polo_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/water_polo_tone3.png b/app/assets/images/emoji/water_polo_tone3.png
new file mode 100644
index 00000000000..b081a4a5a96
--- /dev/null
+++ b/app/assets/images/emoji/water_polo_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/water_polo_tone4.png b/app/assets/images/emoji/water_polo_tone4.png
new file mode 100644
index 00000000000..82cfbc3b0c7
--- /dev/null
+++ b/app/assets/images/emoji/water_polo_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/water_polo_tone5.png b/app/assets/images/emoji/water_polo_tone5.png
new file mode 100644
index 00000000000..bd3366eb06c
--- /dev/null
+++ b/app/assets/images/emoji/water_polo_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/watermelon.png b/app/assets/images/emoji/watermelon.png
new file mode 100644
index 00000000000..0761488b4c9
--- /dev/null
+++ b/app/assets/images/emoji/watermelon.png
Binary files differ
diff --git a/app/assets/images/emoji/wave.png b/app/assets/images/emoji/wave.png
new file mode 100644
index 00000000000..e0cd79b45f5
--- /dev/null
+++ b/app/assets/images/emoji/wave.png
Binary files differ
diff --git a/app/assets/images/emoji/wave_tone1.png b/app/assets/images/emoji/wave_tone1.png
new file mode 100644
index 00000000000..6b2b34b106e
--- /dev/null
+++ b/app/assets/images/emoji/wave_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/wave_tone2.png b/app/assets/images/emoji/wave_tone2.png
new file mode 100644
index 00000000000..b857119732e
--- /dev/null
+++ b/app/assets/images/emoji/wave_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/wave_tone3.png b/app/assets/images/emoji/wave_tone3.png
new file mode 100644
index 00000000000..6283b670f43
--- /dev/null
+++ b/app/assets/images/emoji/wave_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/wave_tone4.png b/app/assets/images/emoji/wave_tone4.png
new file mode 100644
index 00000000000..fe6b2baa747
--- /dev/null
+++ b/app/assets/images/emoji/wave_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/wave_tone5.png b/app/assets/images/emoji/wave_tone5.png
new file mode 100644
index 00000000000..4bd168ebb78
--- /dev/null
+++ b/app/assets/images/emoji/wave_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/wavy_dash.png b/app/assets/images/emoji/wavy_dash.png
new file mode 100644
index 00000000000..001c8d6e47d
--- /dev/null
+++ b/app/assets/images/emoji/wavy_dash.png
Binary files differ
diff --git a/app/assets/images/emoji/waxing_crescent_moon.png b/app/assets/images/emoji/waxing_crescent_moon.png
new file mode 100644
index 00000000000..687125173d9
--- /dev/null
+++ b/app/assets/images/emoji/waxing_crescent_moon.png
Binary files differ
diff --git a/app/assets/images/emoji/waxing_gibbous_moon.png b/app/assets/images/emoji/waxing_gibbous_moon.png
new file mode 100644
index 00000000000..3a808156318
--- /dev/null
+++ b/app/assets/images/emoji/waxing_gibbous_moon.png
Binary files differ
diff --git a/app/assets/images/emoji/wc.png b/app/assets/images/emoji/wc.png
new file mode 100644
index 00000000000..aa433e84ba6
--- /dev/null
+++ b/app/assets/images/emoji/wc.png
Binary files differ
diff --git a/app/assets/images/emoji/weary.png b/app/assets/images/emoji/weary.png
new file mode 100644
index 00000000000..98bfbd24a16
--- /dev/null
+++ b/app/assets/images/emoji/weary.png
Binary files differ
diff --git a/app/assets/images/emoji/wedding.png b/app/assets/images/emoji/wedding.png
new file mode 100644
index 00000000000..d0d8aa0bfae
--- /dev/null
+++ b/app/assets/images/emoji/wedding.png
Binary files differ
diff --git a/app/assets/images/emoji/whale.png b/app/assets/images/emoji/whale.png
new file mode 100644
index 00000000000..9f19b44257c
--- /dev/null
+++ b/app/assets/images/emoji/whale.png
Binary files differ
diff --git a/app/assets/images/emoji/whale2.png b/app/assets/images/emoji/whale2.png
new file mode 100644
index 00000000000..0df9d3c73a4
--- /dev/null
+++ b/app/assets/images/emoji/whale2.png
Binary files differ
diff --git a/app/assets/images/emoji/wheel_of_dharma.png b/app/assets/images/emoji/wheel_of_dharma.png
new file mode 100644
index 00000000000..3666db0016b
--- /dev/null
+++ b/app/assets/images/emoji/wheel_of_dharma.png
Binary files differ
diff --git a/app/assets/images/emoji/wheelchair.png b/app/assets/images/emoji/wheelchair.png
new file mode 100644
index 00000000000..4e5b2698eac
--- /dev/null
+++ b/app/assets/images/emoji/wheelchair.png
Binary files differ
diff --git a/app/assets/images/emoji/white_check_mark.png b/app/assets/images/emoji/white_check_mark.png
new file mode 100644
index 00000000000..e55f087e544
--- /dev/null
+++ b/app/assets/images/emoji/white_check_mark.png
Binary files differ
diff --git a/app/assets/images/emoji/white_circle.png b/app/assets/images/emoji/white_circle.png
new file mode 100644
index 00000000000..c19e15684dd
--- /dev/null
+++ b/app/assets/images/emoji/white_circle.png
Binary files differ
diff --git a/app/assets/images/emoji/white_flower.png b/app/assets/images/emoji/white_flower.png
new file mode 100644
index 00000000000..d6af8b60077
--- /dev/null
+++ b/app/assets/images/emoji/white_flower.png
Binary files differ
diff --git a/app/assets/images/emoji/white_large_square.png b/app/assets/images/emoji/white_large_square.png
new file mode 100644
index 00000000000..6f06c1c79de
--- /dev/null
+++ b/app/assets/images/emoji/white_large_square.png
Binary files differ
diff --git a/app/assets/images/emoji/white_medium_small_square.png b/app/assets/images/emoji/white_medium_small_square.png
new file mode 100644
index 00000000000..ae874126750
--- /dev/null
+++ b/app/assets/images/emoji/white_medium_small_square.png
Binary files differ
diff --git a/app/assets/images/emoji/white_medium_square.png b/app/assets/images/emoji/white_medium_square.png
new file mode 100644
index 00000000000..8daacf57059
--- /dev/null
+++ b/app/assets/images/emoji/white_medium_square.png
Binary files differ
diff --git a/app/assets/images/emoji/white_small_square.png b/app/assets/images/emoji/white_small_square.png
new file mode 100644
index 00000000000..d7ebdb0c0ed
--- /dev/null
+++ b/app/assets/images/emoji/white_small_square.png
Binary files differ
diff --git a/app/assets/images/emoji/white_square_button.png b/app/assets/images/emoji/white_square_button.png
new file mode 100644
index 00000000000..934b1cedfd2
--- /dev/null
+++ b/app/assets/images/emoji/white_square_button.png
Binary files differ
diff --git a/app/assets/images/emoji/white_sun_cloud.png b/app/assets/images/emoji/white_sun_cloud.png
new file mode 100644
index 00000000000..0a4cc100269
--- /dev/null
+++ b/app/assets/images/emoji/white_sun_cloud.png
Binary files differ
diff --git a/app/assets/images/emoji/white_sun_rain_cloud.png b/app/assets/images/emoji/white_sun_rain_cloud.png
new file mode 100644
index 00000000000..491f9ca4839
--- /dev/null
+++ b/app/assets/images/emoji/white_sun_rain_cloud.png
Binary files differ
diff --git a/app/assets/images/emoji/white_sun_small_cloud.png b/app/assets/images/emoji/white_sun_small_cloud.png
new file mode 100644
index 00000000000..cead0bfa521
--- /dev/null
+++ b/app/assets/images/emoji/white_sun_small_cloud.png
Binary files differ
diff --git a/app/assets/images/emoji/wilted_rose.png b/app/assets/images/emoji/wilted_rose.png
new file mode 100644
index 00000000000..62412b143ae
--- /dev/null
+++ b/app/assets/images/emoji/wilted_rose.png
Binary files differ
diff --git a/app/assets/images/emoji/wind_blowing_face.png b/app/assets/images/emoji/wind_blowing_face.png
new file mode 100644
index 00000000000..df81b652eb6
--- /dev/null
+++ b/app/assets/images/emoji/wind_blowing_face.png
Binary files differ
diff --git a/app/assets/images/emoji/wind_chime.png b/app/assets/images/emoji/wind_chime.png
new file mode 100644
index 00000000000..3c9ef3a95f6
--- /dev/null
+++ b/app/assets/images/emoji/wind_chime.png
Binary files differ
diff --git a/app/assets/images/emoji/wine_glass.png b/app/assets/images/emoji/wine_glass.png
new file mode 100644
index 00000000000..3cc98689192
--- /dev/null
+++ b/app/assets/images/emoji/wine_glass.png
Binary files differ
diff --git a/app/assets/images/emoji/wink.png b/app/assets/images/emoji/wink.png
new file mode 100644
index 00000000000..7ea7810a37d
--- /dev/null
+++ b/app/assets/images/emoji/wink.png
Binary files differ
diff --git a/app/assets/images/emoji/wolf.png b/app/assets/images/emoji/wolf.png
new file mode 100644
index 00000000000..ba7220f2de9
--- /dev/null
+++ b/app/assets/images/emoji/wolf.png
Binary files differ
diff --git a/app/assets/images/emoji/woman.png b/app/assets/images/emoji/woman.png
new file mode 100644
index 00000000000..ece440e7a61
--- /dev/null
+++ b/app/assets/images/emoji/woman.png
Binary files differ
diff --git a/app/assets/images/emoji/woman_tone1.png b/app/assets/images/emoji/woman_tone1.png
new file mode 100644
index 00000000000..ff089b8889b
--- /dev/null
+++ b/app/assets/images/emoji/woman_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/woman_tone2.png b/app/assets/images/emoji/woman_tone2.png
new file mode 100644
index 00000000000..0719c378016
--- /dev/null
+++ b/app/assets/images/emoji/woman_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/woman_tone3.png b/app/assets/images/emoji/woman_tone3.png
new file mode 100644
index 00000000000..5672e2fd52d
--- /dev/null
+++ b/app/assets/images/emoji/woman_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/woman_tone4.png b/app/assets/images/emoji/woman_tone4.png
new file mode 100644
index 00000000000..5754aab558b
--- /dev/null
+++ b/app/assets/images/emoji/woman_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/woman_tone5.png b/app/assets/images/emoji/woman_tone5.png
new file mode 100644
index 00000000000..fc252af3a39
--- /dev/null
+++ b/app/assets/images/emoji/woman_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/womans_clothes.png b/app/assets/images/emoji/womans_clothes.png
new file mode 100644
index 00000000000..01410dc8107
--- /dev/null
+++ b/app/assets/images/emoji/womans_clothes.png
Binary files differ
diff --git a/app/assets/images/emoji/womans_hat.png b/app/assets/images/emoji/womans_hat.png
new file mode 100644
index 00000000000..b837b6a2e47
--- /dev/null
+++ b/app/assets/images/emoji/womans_hat.png
Binary files differ
diff --git a/app/assets/images/emoji/womens.png b/app/assets/images/emoji/womens.png
new file mode 100644
index 00000000000..d4ecc22e7b3
--- /dev/null
+++ b/app/assets/images/emoji/womens.png
Binary files differ
diff --git a/app/assets/images/emoji/worried.png b/app/assets/images/emoji/worried.png
new file mode 100644
index 00000000000..7074afcf5b7
--- /dev/null
+++ b/app/assets/images/emoji/worried.png
Binary files differ
diff --git a/app/assets/images/emoji/wrench.png b/app/assets/images/emoji/wrench.png
new file mode 100644
index 00000000000..c16b7439697
--- /dev/null
+++ b/app/assets/images/emoji/wrench.png
Binary files differ
diff --git a/app/assets/images/emoji/wrestlers.png b/app/assets/images/emoji/wrestlers.png
new file mode 100644
index 00000000000..71e67cfad85
--- /dev/null
+++ b/app/assets/images/emoji/wrestlers.png
Binary files differ
diff --git a/app/assets/images/emoji/wrestlers_tone1.png b/app/assets/images/emoji/wrestlers_tone1.png
new file mode 100644
index 00000000000..379070fd03b
--- /dev/null
+++ b/app/assets/images/emoji/wrestlers_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/wrestlers_tone2.png b/app/assets/images/emoji/wrestlers_tone2.png
new file mode 100644
index 00000000000..6863ea9209d
--- /dev/null
+++ b/app/assets/images/emoji/wrestlers_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/wrestlers_tone3.png b/app/assets/images/emoji/wrestlers_tone3.png
new file mode 100644
index 00000000000..b7e62910127
--- /dev/null
+++ b/app/assets/images/emoji/wrestlers_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/wrestlers_tone4.png b/app/assets/images/emoji/wrestlers_tone4.png
new file mode 100644
index 00000000000..750f9589233
--- /dev/null
+++ b/app/assets/images/emoji/wrestlers_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/wrestlers_tone5.png b/app/assets/images/emoji/wrestlers_tone5.png
new file mode 100644
index 00000000000..36ab9bb3f42
--- /dev/null
+++ b/app/assets/images/emoji/wrestlers_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/writing_hand.png b/app/assets/images/emoji/writing_hand.png
new file mode 100644
index 00000000000..85639f8ac40
--- /dev/null
+++ b/app/assets/images/emoji/writing_hand.png
Binary files differ
diff --git a/app/assets/images/emoji/writing_hand_tone1.png b/app/assets/images/emoji/writing_hand_tone1.png
new file mode 100644
index 00000000000..7923d8ebb17
--- /dev/null
+++ b/app/assets/images/emoji/writing_hand_tone1.png
Binary files differ
diff --git a/app/assets/images/emoji/writing_hand_tone2.png b/app/assets/images/emoji/writing_hand_tone2.png
new file mode 100644
index 00000000000..bcb304e15d2
--- /dev/null
+++ b/app/assets/images/emoji/writing_hand_tone2.png
Binary files differ
diff --git a/app/assets/images/emoji/writing_hand_tone3.png b/app/assets/images/emoji/writing_hand_tone3.png
new file mode 100644
index 00000000000..fd885fd2d90
--- /dev/null
+++ b/app/assets/images/emoji/writing_hand_tone3.png
Binary files differ
diff --git a/app/assets/images/emoji/writing_hand_tone4.png b/app/assets/images/emoji/writing_hand_tone4.png
new file mode 100644
index 00000000000..d065b8c64ab
--- /dev/null
+++ b/app/assets/images/emoji/writing_hand_tone4.png
Binary files differ
diff --git a/app/assets/images/emoji/writing_hand_tone5.png b/app/assets/images/emoji/writing_hand_tone5.png
new file mode 100644
index 00000000000..a44b3dd757c
--- /dev/null
+++ b/app/assets/images/emoji/writing_hand_tone5.png
Binary files differ
diff --git a/app/assets/images/emoji/x.png b/app/assets/images/emoji/x.png
new file mode 100644
index 00000000000..9f9ed0f7ad2
--- /dev/null
+++ b/app/assets/images/emoji/x.png
Binary files differ
diff --git a/app/assets/images/emoji/yellow_heart.png b/app/assets/images/emoji/yellow_heart.png
new file mode 100644
index 00000000000..7901a9d0103
--- /dev/null
+++ b/app/assets/images/emoji/yellow_heart.png
Binary files differ
diff --git a/app/assets/images/emoji/yen.png b/app/assets/images/emoji/yen.png
new file mode 100644
index 00000000000..63ee4799d66
--- /dev/null
+++ b/app/assets/images/emoji/yen.png
Binary files differ
diff --git a/app/assets/images/emoji/yin_yang.png b/app/assets/images/emoji/yin_yang.png
new file mode 100644
index 00000000000..f2900f6338f
--- /dev/null
+++ b/app/assets/images/emoji/yin_yang.png
Binary files differ
diff --git a/app/assets/images/emoji/yum.png b/app/assets/images/emoji/yum.png
new file mode 100644
index 00000000000..2df15753ca1
--- /dev/null
+++ b/app/assets/images/emoji/yum.png
Binary files differ
diff --git a/app/assets/images/emoji/zap.png b/app/assets/images/emoji/zap.png
new file mode 100644
index 00000000000..47e68e48e49
--- /dev/null
+++ b/app/assets/images/emoji/zap.png
Binary files differ
diff --git a/app/assets/images/emoji/zero.png b/app/assets/images/emoji/zero.png
new file mode 100644
index 00000000000..13aca83e018
--- /dev/null
+++ b/app/assets/images/emoji/zero.png
Binary files differ
diff --git a/app/assets/images/emoji/zipper_mouth.png b/app/assets/images/emoji/zipper_mouth.png
new file mode 100644
index 00000000000..f8ced2502a7
--- /dev/null
+++ b/app/assets/images/emoji/zipper_mouth.png
Binary files differ
diff --git a/app/assets/images/emoji/zzz.png b/app/assets/images/emoji/zzz.png
new file mode 100644
index 00000000000..9bc72b4469f
--- /dev/null
+++ b/app/assets/images/emoji/zzz.png
Binary files differ
diff --git a/app/assets/images/emoji@2x.png b/app/assets/images/emoji@2x.png
index dc9cae1d44c..b0fa9e1139e 100644
--- a/app/assets/images/emoji@2x.png
+++ b/app/assets/images/emoji@2x.png
Binary files differ
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index a4ccb30e447..4667980a960 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -1,380 +1,518 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, no-var, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-template, quotes, comma-dangle, no-param-reassign, no-void, brace-style, no-underscore-dangle, no-return-assign, camelcase */
/* global Cookies */
-var emojiAliases = require('emoji-aliases');
-
-(function() {
- this.AwardsHandler = (function() {
- var FROM_SENTENCE_REGEX = /(?:, and | and |, )/; // For separating lists produced by ruby's Array#toSentence
- function AwardsHandler() {
- this.aliases = emojiAliases;
- $(document).off('click', '.js-add-award').on('click', '.js-add-award', (function(_this) {
- return function(e) {
- e.stopPropagation();
- e.preventDefault();
- return _this.showEmojiMenu($(e.currentTarget));
- };
- })(this));
- $('html').on('click', function(e) {
- var $target;
- $target = $(e.target);
- if (!$target.closest('.emoji-menu-content').length) {
- $('.js-awards-block.current').removeClass('current');
- }
- if (!$target.closest('.emoji-menu').length) {
- if ($('.emoji-menu').is(':visible')) {
- $('.js-add-award.is-active').removeClass('is-active');
- return $('.emoji-menu').removeClass('is-visible');
- }
- }
- });
- $(document).off('click', '.js-emoji-btn').on('click', '.js-emoji-btn', (function(_this) {
- return function(e) {
- var $target, emoji;
- e.preventDefault();
- $target = $(e.currentTarget);
- emoji = $target.find('.icon').data('emoji');
- $target.closest('.js-awards-block').addClass('current');
- return _this.addAward(_this.getVotesBlock(), _this.getAwardUrl(), emoji);
- };
- })(this));
+const emojiMap = require('emoji-map');
+const emojiAliases = require('emoji-aliases');
+const glEmoji = require('./behaviors/gl_emoji');
+
+const glEmojiTag = glEmoji.glEmojiTag;
+
+const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
+const requestAnimationFrame = window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.setTimeout;
+
+const FROM_SENTENCE_REGEX = /(?:, and | and |, )/; // For separating lists produced by ruby's Array#toSentence
+
+let categoryMap = null;
+
+const categoryLabelMap = {
+ activity: 'Activity',
+ people: 'People',
+ nature: 'Nature',
+ food: 'Food',
+ travel: 'Travel',
+ objects: 'Objects',
+ symbols: 'Symbols',
+ flags: 'Flags',
+};
+
+function buildCategoryMap() {
+ return Object.keys(emojiMap).reduce((currentCategoryMap, emojiNameKey) => {
+ const emojiInfo = emojiMap[emojiNameKey];
+ if (currentCategoryMap[emojiInfo.category]) {
+ currentCategoryMap[emojiInfo.category].push(emojiNameKey);
}
- AwardsHandler.prototype.showEmojiMenu = function($addBtn) {
- var $holder, $menu, url;
- $menu = $('.emoji-menu');
- if ($addBtn.hasClass('js-note-emoji')) {
- $addBtn.closest('.note').find('.js-awards-block').addClass('current');
- } else {
- $addBtn.closest('.js-awards-block').addClass('current');
- }
- if ($menu.length) {
- $holder = $addBtn.closest('.js-award-holder');
- if ($menu.is('.is-visible')) {
- $addBtn.removeClass('is-active');
- $menu.removeClass('is-visible');
- return $('#emoji_search').blur();
- } else {
- $addBtn.addClass('is-active');
- this.positionMenu($menu, $addBtn);
- $menu.addClass('is-visible');
- return $('#emoji_search').focus();
- }
- } else {
- $addBtn.addClass('is-loading is-active');
- url = this.getAwardMenuUrl();
- return this.createEmojiMenu(url, (function(_this) {
- return function() {
- $addBtn.removeClass('is-loading');
- $menu = $('.emoji-menu');
- _this.positionMenu($menu, $addBtn);
- if (!_this.frequentEmojiBlockRendered) {
- _this.renderFrequentlyUsedBlock();
- }
- return setTimeout(function() {
- $menu.addClass('is-visible');
- $('#emoji_search').focus();
- return _this.setupSearch();
- }, 200);
- };
- })(this));
- }
- };
-
- AwardsHandler.prototype.createEmojiMenu = function(awardMenuUrl, callback) {
- return $.get(awardMenuUrl, function(response) {
- $('body').append(response);
- return callback();
+ return currentCategoryMap;
+ }, {
+ activity: [],
+ people: [],
+ nature: [],
+ food: [],
+ travel: [],
+ objects: [],
+ symbols: [],
+ flags: [],
+ });
+}
+
+function renderCategory(name, emojiList) {
+ return `
+ <h5 class="emoji-menu-title">
+ ${name}
+ </h5>
+ <ul class="clearfix emoji-menu-list">
+ ${emojiList.map(emojiName => `
+ <li class="emoji-menu-list-item">
+ <button class="emoji-menu-btn text-center js-emoji-btn" type="button">
+ ${glEmojiTag(emojiName, {
+ sprite: true,
+ })}
+ </button>
+ </li>
+ `).join('\n')}
+ </ul>
+ `;
+}
+
+function AwardsHandler() {
+ this.eventListeners = [];
+ this.aliases = emojiAliases;
+ // If the user shows intent let's pre-build the menu
+ this.registerEventListener('one', $(document), 'mouseenter focus', '.js-add-award', 'mouseenter focus', () => {
+ const $menu = $('.emoji-menu');
+ if ($menu.length === 0) {
+ requestAnimationFrame(() => {
+ this.createEmojiMenu();
});
- };
-
- AwardsHandler.prototype.positionMenu = function($menu, $addBtn) {
- var css, position;
- position = $addBtn.data('position');
- // The menu could potentially be off-screen or in a hidden overflow element
- // So we position the element absolute in the body
- css = {
- top: ($addBtn.offset().top + $addBtn.outerHeight()) + "px"
- };
- if (position === 'right') {
- css.left = (($addBtn.offset().left - $menu.outerWidth()) + 20) + "px";
- $menu.addClass('is-aligned-right');
- } else {
- css.left = ($addBtn.offset().left) + "px";
- $menu.removeClass('is-aligned-right');
- }
- return $menu.css(css);
- };
-
- AwardsHandler.prototype.addAward = function(votesBlock, awardUrl, emoji, checkMutuality, callback) {
- if (checkMutuality == null) {
- checkMutuality = true;
- }
- emoji = this.normilizeEmojiName(emoji);
- this.postEmoji(awardUrl, emoji, (function(_this) {
- return function() {
- _this.addAwardToEmojiBar(votesBlock, emoji, checkMutuality);
- return typeof callback === "function" ? callback() : void 0;
- };
- })(this));
- return $('.emoji-menu').removeClass('is-visible');
- };
-
- AwardsHandler.prototype.addAwardToEmojiBar = function(votesBlock, emoji, checkForMutuality) {
- var $emojiButton, counter;
- if (checkForMutuality == null) {
- checkForMutuality = true;
- }
- if (checkForMutuality) {
- this.checkMutuality(votesBlock, emoji);
- }
- this.addEmojiToFrequentlyUsedList(emoji);
- emoji = this.normilizeEmojiName(emoji);
- $emojiButton = this.findEmojiIcon(votesBlock, emoji).parent();
- if ($emojiButton.length > 0) {
- if (this.isActive($emojiButton)) {
- return this.decrementCounter($emojiButton, emoji);
- } else {
- counter = $emojiButton.find('.js-counter');
- counter.text(parseInt(counter.text(), 10) + 1);
- $emojiButton.addClass('active');
- this.addYouToUserList(votesBlock, emoji);
- return this.animateEmoji($emojiButton);
- }
- } else {
- votesBlock.removeClass('hidden');
- return this.createEmoji(votesBlock, emoji);
- }
- };
-
- AwardsHandler.prototype.getVotesBlock = function() {
- var currentBlock;
- currentBlock = $('.js-awards-block.current');
- if (currentBlock.length) {
- return currentBlock;
- } else {
- return $('.js-awards-block').eq(0);
- }
- };
-
- AwardsHandler.prototype.getAwardUrl = function() {
- return this.getVotesBlock().data('award-url');
- };
-
- AwardsHandler.prototype.checkMutuality = function(votesBlock, emoji) {
- var $emojiButton, awardUrl, isAlreadyVoted, mutualVote;
- awardUrl = this.getAwardUrl();
- if (emoji === 'thumbsup' || emoji === 'thumbsdown') {
- mutualVote = emoji === 'thumbsup' ? 'thumbsdown' : 'thumbsup';
- $emojiButton = votesBlock.find("[data-emoji=" + mutualVote + "]").parent();
- isAlreadyVoted = $emojiButton.hasClass('active');
- if (isAlreadyVoted) {
- this.addAward(votesBlock, awardUrl, mutualVote, false);
- }
- }
- };
-
- AwardsHandler.prototype.isActive = function($emojiButton) {
- return $emojiButton.hasClass('active');
- };
-
- AwardsHandler.prototype.decrementCounter = function($emojiButton, emoji) {
- var counter, counterNumber;
- counter = $('.js-counter', $emojiButton);
- counterNumber = parseInt(counter.text(), 10);
- if (counterNumber > 1) {
- counter.text(counterNumber - 1);
- this.removeYouFromUserList($emojiButton, emoji);
- } else if (emoji === 'thumbsup' || emoji === 'thumbsdown') {
- $emojiButton.tooltip('destroy');
- counter.text('0');
- this.removeYouFromUserList($emojiButton, emoji);
- if ($emojiButton.parents('.note').length) {
- this.removeEmoji($emojiButton);
- }
- } else {
- this.removeEmoji($emojiButton);
- }
- return $emojiButton.removeClass('active');
- };
-
- AwardsHandler.prototype.removeEmoji = function($emojiButton) {
- var $votesBlock;
- $emojiButton.tooltip('destroy');
- $emojiButton.remove();
- $votesBlock = this.getVotesBlock();
- if ($votesBlock.find('.js-emoji-btn').length === 0) {
- return $votesBlock.addClass('hidden');
- }
- };
-
- AwardsHandler.prototype.getAwardTooltip = function($awardBlock) {
- return $awardBlock.attr('data-original-title') || $awardBlock.attr('data-title') || '';
- };
-
- AwardsHandler.prototype.toSentence = function(list) {
- if (list.length <= 2) {
- return list.join(' and ');
+ }
+ // Prebuild the categoryMap
+ categoryMap = categoryMap || buildCategoryMap();
+ });
+ this.registerEventListener('on', $(document), 'click', '.js-add-award', (e) => {
+ e.stopPropagation();
+ e.preventDefault();
+ this.showEmojiMenu($(e.currentTarget));
+ });
+
+ this.registerEventListener('on', $('html'), 'click', (e) => {
+ const $target = $(e.target);
+ if (!$target.closest('.emoji-menu-content').length) {
+ $('.js-awards-block.current').removeClass('current');
+ }
+ if (!$target.closest('.emoji-menu').length) {
+ if ($('.emoji-menu').is(':visible')) {
+ $('.js-add-award.is-active').removeClass('is-active');
+ $('.emoji-menu').removeClass('is-visible');
}
- else {
- return list.slice(0, -1).join(', ') + ', and ' + list[list.length - 1];
+ }
+ });
+ this.registerEventListener('on', $(document), 'click', '.js-emoji-btn', (e) => {
+ e.preventDefault();
+ const $target = $(e.currentTarget);
+ const $glEmojiElement = $target.find('gl-emoji');
+ const $spriteIconElement = $target.find('.icon');
+ const emoji = ($glEmojiElement.length ? $glEmojiElement : $spriteIconElement).data('name');
+ $target.closest('.js-awards-block').addClass('current');
+ return this.addAward(this.getVotesBlock(), this.getAwardUrl(), emoji);
+ });
+}
+
+AwardsHandler.prototype.registerEventListener = function registerEventListener(method = 'on', element, ...args) {
+ element[method].call(element, ...args);
+ this.eventListeners.push({
+ element,
+ args,
+ });
+};
+
+AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) {
+ if ($addBtn.hasClass('js-note-emoji')) {
+ $addBtn.closest('.note').find('.js-awards-block').addClass('current');
+ } else {
+ $addBtn.closest('.js-awards-block').addClass('current');
+ }
+
+ const $menu = $('.emoji-menu');
+ if ($menu.length) {
+ if ($menu.is('.is-visible')) {
+ $addBtn.removeClass('is-active');
+ $menu.removeClass('is-visible');
+ $('#emoji_search').blur();
+ } else {
+ $addBtn.addClass('is-active');
+ this.positionMenu($menu, $addBtn);
+ $menu.addClass('is-visible');
+ $('#emoji_search').focus();
+ }
+ } else {
+ $addBtn.addClass('is-loading is-active');
+ this.createEmojiMenu(() => {
+ const $createdMenu = $('.emoji-menu');
+ $addBtn.removeClass('is-loading');
+ this.positionMenu($createdMenu, $addBtn);
+ if (!this.frequentEmojiBlockRendered) {
+ this.renderFrequentlyUsedBlock();
}
- };
-
- AwardsHandler.prototype.removeYouFromUserList = function($emojiButton, emoji) {
- var authors, awardBlock, newAuthors, originalTitle;
- awardBlock = $emojiButton;
- originalTitle = this.getAwardTooltip(awardBlock);
- authors = originalTitle.split(FROM_SENTENCE_REGEX);
- authors.splice(authors.indexOf('You'), 1);
- return awardBlock
- .closest('.js-emoji-btn')
- .removeData('title')
- .removeAttr('data-title')
- .removeAttr('data-original-title')
- .attr('title', this.toSentence(authors))
- .tooltip('fixTitle');
- };
-
- AwardsHandler.prototype.addYouToUserList = function(votesBlock, emoji) {
- var awardBlock, origTitle, users;
- awardBlock = this.findEmojiIcon(votesBlock, emoji).parent();
- origTitle = this.getAwardTooltip(awardBlock);
- users = [];
- if (origTitle) {
- users = origTitle.trim().split(FROM_SENTENCE_REGEX);
+ return setTimeout(() => {
+ $createdMenu.addClass('is-visible');
+ $('#emoji_search').focus();
+ }, 200);
+ });
+ }
+};
+
+// Create the emoji menu with the first category of emojis.
+// Then render the remaining categories of emojis one by one to avoid jank.
+AwardsHandler.prototype.createEmojiMenu = function createEmojiMenu(callback) {
+ if (this.isCreatingEmojiMenu) {
+ return;
+ }
+ this.isCreatingEmojiMenu = true;
+
+ // Render the first category
+ categoryMap = categoryMap || buildCategoryMap();
+ const categoryNameKey = Object.keys(categoryMap)[0];
+ const emojisInCategory = categoryMap[categoryNameKey];
+ const firstCategory = renderCategory(categoryLabelMap[categoryNameKey], emojisInCategory);
+
+ const emojiMenuMarkup = `
+ <div class="emoji-menu">
+ <input type="text" name="emoji_search" id="emoji_search" value="" class="emoji-search search-input form-control" placeholder="Search emoji" />
+
+ <div class="emoji-menu-content">
+ ${firstCategory}
+ </div>
+ </div>
+ `;
+
+ document.body.insertAdjacentHTML('beforeend', emojiMenuMarkup);
+
+ this.addRemainingEmojiMenuCategories();
+ this.setupSearch();
+ if (callback) {
+ callback();
+ }
+};
+
+AwardsHandler
+ .prototype
+ .addRemainingEmojiMenuCategories = function addRemainingEmojiMenuCategories() {
+ if (this.isAddingRemainingEmojiMenuCategories) {
+ return;
+ }
+ this.isAddingRemainingEmojiMenuCategories = true;
+
+ categoryMap = categoryMap || buildCategoryMap();
+
+ // Avoid the jank and render the remaining categories separately
+ // This will take more time, but makes UI more responsive
+ const menu = document.querySelector('.emoji-menu');
+ const emojiContentElement = menu.querySelector('.emoji-menu-content');
+ const remainingCategories = Object.keys(categoryMap).slice(1);
+ const allCategoriesAddedPromise = remainingCategories.reduce(
+ (promiseChain, categoryNameKey) =>
+ promiseChain.then(() =>
+ new Promise((resolve) => {
+ const emojisInCategory = categoryMap[categoryNameKey];
+ const categoryMarkup = renderCategory(
+ categoryLabelMap[categoryNameKey],
+ emojisInCategory,
+ );
+ requestAnimationFrame(() => {
+ emojiContentElement.insertAdjacentHTML('beforeend', categoryMarkup);
+ resolve();
+ });
+ }),
+ ),
+ Promise.resolve(),
+ );
+
+ allCategoriesAddedPromise.then(() => {
+ // Used for tests
+ // We check for the menu in case it was destroyed in the meantime
+ if (menu) {
+ menu.dispatchEvent(new CustomEvent('build-emoji-menu-finish'));
}
- users.unshift('You');
- return awardBlock
- .attr('title', this.toSentence(users))
- .tooltip('fixTitle');
- };
-
- AwardsHandler.prototype.createEmoji_ = function(votesBlock, emoji) {
- var $emojiButton, buttonHtml, emojiCssClass;
- emojiCssClass = this.resolveNameToCssClass(emoji);
- buttonHtml = "<button class='btn award-control js-emoji-btn has-tooltip active' title='You' data-placement='bottom'> <div class='icon emoji-icon " + emojiCssClass + "' data-emoji='" + emoji + "'></div> <span class='award-control-text js-counter'>1</span> </button>";
- $emojiButton = $(buttonHtml);
- $emojiButton.insertBefore(votesBlock.find('.js-award-holder')).find('.emoji-icon').data('emoji', emoji);
+ });
+ };
+
+AwardsHandler.prototype.positionMenu = function positionMenu($menu, $addBtn) {
+ const position = $addBtn.data('position');
+ // The menu could potentially be off-screen or in a hidden overflow element
+ // So we position the element absolute in the body
+ const css = {
+ top: `${$addBtn.offset().top + $addBtn.outerHeight()}px`,
+ };
+ if (position === 'right') {
+ css.left = `${($addBtn.offset().left - $menu.outerWidth()) + 20}px`;
+ $menu.addClass('is-aligned-right');
+ } else {
+ css.left = `${$addBtn.offset().left}px`;
+ $menu.removeClass('is-aligned-right');
+ }
+ return $menu.css(css);
+};
+
+AwardsHandler.prototype.addAward = function addAward(
+ votesBlock,
+ awardUrl,
+ emoji,
+ checkMutuality,
+ callback,
+) {
+ const normalizedEmoji = this.normalizeEmojiName(emoji);
+ this.postEmoji(awardUrl, normalizedEmoji, () => {
+ this.addAwardToEmojiBar(votesBlock, normalizedEmoji, checkMutuality);
+ return typeof callback === 'function' ? callback() : undefined;
+ });
+ return $('.emoji-menu').removeClass('is-visible');
+};
+
+AwardsHandler.prototype.addAwardToEmojiBar = function addAwardToEmojiBar(
+ votesBlock,
+ emoji,
+ checkForMutuality,
+) {
+ if (checkForMutuality || checkForMutuality === null) {
+ this.checkMutuality(votesBlock, emoji);
+ }
+ this.addEmojiToFrequentlyUsedList(emoji);
+ const normalizedEmoji = this.normalizeEmojiName(emoji);
+ const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent();
+ if ($emojiButton.length > 0) {
+ if (this.isActive($emojiButton)) {
+ this.decrementCounter($emojiButton, normalizedEmoji);
+ } else {
+ const counter = $emojiButton.find('.js-counter');
+ counter.text(parseInt(counter.text(), 10) + 1);
+ $emojiButton.addClass('active');
+ this.addYouToUserList(votesBlock, normalizedEmoji);
this.animateEmoji($emojiButton);
- $('.award-control').tooltip();
- return votesBlock.removeClass('current');
- };
-
- AwardsHandler.prototype.animateEmoji = function($emoji) {
- var className = 'pulse animated once short';
- $emoji.addClass(className);
+ }
+ } else {
+ votesBlock.removeClass('hidden');
+ this.createEmoji(votesBlock, normalizedEmoji);
+ }
+};
+
+AwardsHandler.prototype.getVotesBlock = function getVotesBlock() {
+ const currentBlock = $('.js-awards-block.current');
+ let resultantVotesBlock = currentBlock;
+ if (currentBlock.length === 0) {
+ resultantVotesBlock = $('.js-awards-block').eq(0);
+ }
+
+ return resultantVotesBlock;
+};
+
+AwardsHandler.prototype.getAwardUrl = function getAwardUrl() {
+ return this.getVotesBlock().data('award-url');
+};
+
+AwardsHandler.prototype.checkMutuality = function checkMutuality(votesBlock, emoji) {
+ const awardUrl = this.getAwardUrl();
+ if (emoji === 'thumbsup' || emoji === 'thumbsdown') {
+ const mutualVote = emoji === 'thumbsup' ? 'thumbsdown' : 'thumbsup';
+ const $emojiButton = votesBlock.find(`[data-name="${mutualVote}"]`).parent();
+ const isAlreadyVoted = $emojiButton.hasClass('active');
+ if (isAlreadyVoted) {
+ this.addAward(votesBlock, awardUrl, mutualVote, false);
+ }
+ }
+};
+
+AwardsHandler.prototype.isActive = function isActive($emojiButton) {
+ return $emojiButton.hasClass('active');
+};
+
+AwardsHandler.prototype.decrementCounter = function decrementCounter($emojiButton, emoji) {
+ const counter = $('.js-counter', $emojiButton);
+ const counterNumber = parseInt(counter.text(), 10);
+ if (counterNumber > 1) {
+ counter.text(counterNumber - 1);
+ this.removeYouFromUserList($emojiButton);
+ } else if (emoji === 'thumbsup' || emoji === 'thumbsdown') {
+ $emojiButton.tooltip('destroy');
+ counter.text('0');
+ this.removeYouFromUserList($emojiButton);
+ if ($emojiButton.parents('.note').length) {
+ this.removeEmoji($emojiButton);
+ }
+ } else {
+ this.removeEmoji($emojiButton);
+ }
+ return $emojiButton.removeClass('active');
+};
+
+AwardsHandler.prototype.removeEmoji = function removeEmoji($emojiButton) {
+ $emojiButton.tooltip('destroy');
+ $emojiButton.remove();
+ const $votesBlock = this.getVotesBlock();
+ if ($votesBlock.find('.js-emoji-btn').length === 0) {
+ $votesBlock.addClass('hidden');
+ }
+};
+
+AwardsHandler.prototype.getAwardTooltip = function getAwardTooltip($awardBlock) {
+ return $awardBlock.attr('data-original-title') || $awardBlock.attr('data-title') || '';
+};
+
+AwardsHandler.prototype.toSentence = function toSentence(list) {
+ let sentence;
+ if (list.length <= 2) {
+ sentence = list.join(' and ');
+ } else {
+ sentence = `${list.slice(0, -1).join(', ')}, and ${list[list.length - 1]}`;
+ }
+
+ return sentence;
+};
+
+AwardsHandler.prototype.removeYouFromUserList = function removeYouFromUserList($emojiButton) {
+ const awardBlock = $emojiButton;
+ const originalTitle = this.getAwardTooltip(awardBlock);
+ const authors = originalTitle.split(FROM_SENTENCE_REGEX);
+ authors.splice(authors.indexOf('You'), 1);
+ return awardBlock
+ .closest('.js-emoji-btn')
+ .removeData('title')
+ .removeAttr('data-title')
+ .removeAttr('data-original-title')
+ .attr('title', this.toSentence(authors))
+ .tooltip('fixTitle');
+};
+
+AwardsHandler.prototype.addYouToUserList = function addYouToUserList(votesBlock, emoji) {
+ const awardBlock = this.findEmojiIcon(votesBlock, emoji).parent();
+ const origTitle = this.getAwardTooltip(awardBlock);
+ let users = [];
+ if (origTitle) {
+ users = origTitle.trim().split(FROM_SENTENCE_REGEX);
+ }
+ users.unshift('You');
+ return awardBlock
+ .attr('title', this.toSentence(users))
+ .tooltip('fixTitle');
+};
+
+AwardsHandler
+ .prototype
+ .createAwardButtonForVotesBlock = function createAwardButtonForVotesBlock(votesBlock, emojiName) {
+ const buttonHtml = `
+ <button class="btn award-control js-emoji-btn has-tooltip active" title="You" data-placement="bottom">
+ ${glEmojiTag(emojiName)}
+ <span class="award-control-text js-counter">1</span>
+ </button>
+ `;
+ const $emojiButton = $(buttonHtml);
+ $emojiButton.insertBefore(votesBlock.find('.js-award-holder')).find('.emoji-icon').data('name', emojiName);
+ this.animateEmoji($emojiButton);
+ $('.award-control').tooltip();
+ votesBlock.removeClass('current');
+ };
+
+AwardsHandler.prototype.animateEmoji = function animateEmoji($emoji) {
+ const className = 'pulse animated once short';
+ $emoji.addClass(className);
+
+ this.registerEventListener('on', $emoji, animationEndEventString, (e) => {
+ $(e.currentTarget).removeClass(className);
+ });
+};
+
+AwardsHandler.prototype.createEmoji = function createEmoji(votesBlock, emoji) {
+ if ($('.emoji-menu').length) {
+ this.createAwardButtonForVotesBlock(votesBlock, emoji);
+ }
+ this.createEmojiMenu(() => {
+ this.createAwardButtonForVotesBlock(votesBlock, emoji);
+ });
+};
+
+AwardsHandler.prototype.postEmoji = function postEmoji(awardUrl, emoji, callback) {
+ return $.post(awardUrl, {
+ name: emoji,
+ }, (data) => {
+ if (data.ok) {
+ callback();
+ }
+ });
+};
+
+AwardsHandler.prototype.findEmojiIcon = function findEmojiIcon(votesBlock, emoji) {
+ return votesBlock.find(`.js-emoji-btn [data-name="${emoji}"]`);
+};
+
+AwardsHandler.prototype.scrollToAwards = function scrollToAwards() {
+ const options = {
+ scrollTop: $('.awards').offset().top - 110,
+ };
+ return $('body, html').animate(options, 200);
+};
+
+AwardsHandler.prototype.normalizeEmojiName = function normalizeEmojiName(emoji) {
+ return Object.prototype.hasOwnProperty.call(this.aliases, emoji) ? this.aliases[emoji] : emoji;
+};
+
+AwardsHandler
+ .prototype
+ .addEmojiToFrequentlyUsedList = function addEmojiToFrequentlyUsedList(emoji) {
+ const frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
+ frequentlyUsedEmojis.push(emoji);
+ Cookies.set('frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 });
+ };
+
+AwardsHandler.prototype.getFrequentlyUsedEmojis = function getFrequentlyUsedEmojis() {
+ const frequentlyUsedEmojis = (Cookies.get('frequently_used_emojis') || '').split(',');
+ return _.compact(_.uniq(frequentlyUsedEmojis));
+};
+
+AwardsHandler.prototype.renderFrequentlyUsedBlock = function renderFrequentlyUsedBlock() {
+ if (Cookies.get('frequently_used_emojis')) {
+ const frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
+ const ul = $('<ul class="clearfix emoji-menu-list frequent-emojis">');
+ for (let i = 0, len = frequentlyUsedEmojis.length; i < len; i += 1) {
+ const emoji = frequentlyUsedEmojis[i];
+ $(`.emoji-menu-content [data-name="${emoji}"]`).closest('li').clone().appendTo(ul);
+ }
+ $('.emoji-menu-content').prepend(ul).prepend($('<h5>').text('Frequently used'));
+ }
+ this.frequentEmojiBlockRendered = true;
+};
+
+AwardsHandler.prototype.setupSearch = function setupSearch() {
+ this.registerEventListener('on', $('input.emoji-search'), 'input', (e) => {
+ const term = $(e.target).val().trim();
+ // Clean previous search results
+ $('ul.emoji-menu-search, h5.emoji-search').remove();
+ if (term.length > 0) {
+ // Generate a search result block
+ const h5 = $('<h5 class="emoji-search" />').text('Search results');
+ const foundEmojis = this.searchEmojis(term).show();
+ const ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis);
+ $('.emoji-menu-content ul, .emoji-menu-content h5').hide();
+ $('.emoji-menu-content').append(h5).append(ul);
+ } else {
+ $('.emoji-menu-content').children().show();
+ }
+ });
+};
- $emoji.on('webkitAnimationEnd animationEnd', function() {
- $(this).removeClass(className);
- });
- };
+AwardsHandler.prototype.searchEmojis = function searchEmojis(term) {
+ const safeTerm = term.toLowerCase();
- AwardsHandler.prototype.createEmoji = function(votesBlock, emoji) {
- if ($('.emoji-menu').length) {
- return this.createEmoji_(votesBlock, emoji);
- }
- return this.createEmojiMenu(this.getAwardMenuUrl(), (function(_this) {
- return function() {
- return _this.createEmoji_(votesBlock, emoji);
- };
- })(this));
- };
-
- AwardsHandler.prototype.getAwardMenuUrl = function() {
- return gon.award_menu_url;
- };
-
- AwardsHandler.prototype.resolveNameToCssClass = function(emoji) {
- var emojiIcon, unicodeName;
- emojiIcon = $(".emoji-menu-content [data-emoji='" + emoji + "']");
- if (emojiIcon.length > 0) {
- unicodeName = emojiIcon.data('unicode-name');
- } else {
- // Find by alias
- unicodeName = $(".emoji-menu-content [data-aliases*=':" + emoji + ":']").data('unicode-name');
- }
- return "emoji-" + unicodeName;
- };
-
- AwardsHandler.prototype.postEmoji = function(awardUrl, emoji, callback) {
- return $.post(awardUrl, {
- name: emoji
- }, function(data) {
- if (data.ok) {
- return callback();
- }
- });
- };
-
- AwardsHandler.prototype.findEmojiIcon = function(votesBlock, emoji) {
- return votesBlock.find(".js-emoji-btn [data-emoji='" + emoji + "']");
- };
-
- AwardsHandler.prototype.scrollToAwards = function() {
- var options;
- options = {
- scrollTop: $('.awards').offset().top - 110
- };
- return $('body, html').animate(options, 200);
- };
-
- AwardsHandler.prototype.normilizeEmojiName = function(emoji) {
- return this.aliases[emoji] || emoji;
- };
-
- AwardsHandler.prototype.addEmojiToFrequentlyUsedList = function(emoji) {
- var frequentlyUsedEmojis;
- frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
- frequentlyUsedEmojis.push(emoji);
- Cookies.set('frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 });
- };
-
- AwardsHandler.prototype.getFrequentlyUsedEmojis = function() {
- var frequentlyUsedEmojis;
- frequentlyUsedEmojis = (Cookies.get('frequently_used_emojis') || '').split(',');
- return _.compact(_.uniq(frequentlyUsedEmojis));
- };
-
- AwardsHandler.prototype.renderFrequentlyUsedBlock = function() {
- var emoji, frequentlyUsedEmojis, i, len, ul;
- if (Cookies.get('frequently_used_emojis')) {
- frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
- ul = $("<ul class='clearfix emoji-menu-list frequent-emojis'>");
- for (i = 0, len = frequentlyUsedEmojis.length; i < len; i += 1) {
- emoji = frequentlyUsedEmojis[i];
- $(".emoji-menu-content [data-emoji='" + emoji + "']").closest('li').clone().appendTo(ul);
- }
- $('.emoji-menu-content').prepend(ul).prepend($('<h5>').text('Frequently used'));
- }
- return this.frequentEmojiBlockRendered = true;
- };
-
- AwardsHandler.prototype.setupSearch = function() {
- return $('input.emoji-search').on('keyup', (function(_this) {
- return function(ev) {
- var found_emojis, h5, term, ul;
- term = $(ev.target).val();
- // Clean previous search results
- $('ul.emoji-menu-search, h5.emoji-search').remove();
- if (term) {
- // Generate a search result block
- h5 = $('<h5 class="emoji-search" />').text('Search results');
- found_emojis = _this.searchEmojis(term).show();
- ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(found_emojis);
- $('.emoji-menu-content ul, .emoji-menu-content h5').hide();
- return $('.emoji-menu-content').append(h5).append(ul);
- } else {
- return $('.emoji-menu-content').children().show();
- }
- };
- })(this));
- };
-
- AwardsHandler.prototype.searchEmojis = function(term) {
- return $(".emoji-menu-list:not(.frequent-emojis) [data-emoji*='" + term + "']").closest('li').clone();
- };
-
- return AwardsHandler;
- })();
-}).call(window);
+ const namesMatchingAlias = [];
+ Object.keys(emojiAliases).forEach((alias) => {
+ if (alias.indexOf(safeTerm) >= 0) {
+ namesMatchingAlias.push(emojiAliases[alias]);
+ }
+ });
+ const $matchingElements = namesMatchingAlias.concat(safeTerm)
+ .reduce(
+ ($result, searchTerm) =>
+ $result.add($(`.emoji-menu-list:not(.frequent-emojis) [data-name*="${searchTerm}"]`)),
+ $([]),
+ );
+ return $matchingElements.closest('li').clone();
+};
+
+AwardsHandler.prototype.destroy = function destroy() {
+ this.eventListeners.forEach((entry) => {
+ entry.element.off.call(entry.element, ...entry.args);
+ });
+ $('.emoji-menu').remove();
+};
+
+module.exports = AwardsHandler;
diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js
new file mode 100644
index 00000000000..d1d98c3919f
--- /dev/null
+++ b/app/assets/javascripts/behaviors/gl_emoji.js
@@ -0,0 +1,217 @@
+const installCustomElements = require('document-register-element');
+const emojiMap = require('emoji-map');
+const emojiAliases = require('emoji-aliases');
+const generatedUnicodeSupportMap = require('./gl_emoji/unicode_support_map');
+const spreadString = require('./gl_emoji/spread_string');
+
+installCustomElements(window);
+
+function emojiImageTag(name, src) {
+ return `<img class="emoji" title=":${name}:" alt=":${name}:" src="${src}" width="20" height="20" align="absmiddle" />`;
+}
+
+function assembleFallbackImageSrc(inputName) {
+ const name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ?
+ emojiAliases[inputName] : inputName;
+ const emojiInfo = emojiMap[name];
+ const fallbackImageSrc = `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/emoji/${name}-${emojiInfo.digest}.png`;
+
+ return fallbackImageSrc;
+}
+const glEmojiTagDefaults = {
+ sprite: false,
+ forceFallback: false,
+};
+function glEmojiTag(inputName, options) {
+ const opts = Object.assign({}, glEmojiTagDefaults, options);
+ const name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ?
+ emojiAliases[inputName] : inputName;
+ const emojiInfo = emojiMap[name];
+ const fallbackImageSrc = assembleFallbackImageSrc(name);
+ const fallbackSpriteClass = `emoji-${name}`;
+
+ const classList = [];
+ if (opts.forceFallback && opts.sprite) {
+ classList.push('emoji-icon');
+ classList.push(fallbackSpriteClass);
+ }
+ const classAttribute = classList.length > 0 ? `class="${classList.join(' ')}"` : '';
+ const fallbackSpriteAttribute = opts.sprite ? `data-fallback-sprite-class="${fallbackSpriteClass}"` : '';
+ let contents = emojiInfo.moji;
+ if (opts.forceFallback && !opts.sprite) {
+ contents = emojiImageTag(name, fallbackImageSrc);
+ }
+
+ return `
+ <gl-emoji
+ ${classAttribute}
+ data-name="${name}"
+ data-fallback-src="${fallbackImageSrc}"
+ ${fallbackSpriteAttribute}
+ data-unicode-version="${emojiInfo.unicodeVersion}"
+ >
+ ${contents}
+ </gl-emoji>
+ `;
+}
+
+// On Windows, flags render as two-letter country codes, see http://emojipedia.org/flags/
+const flagACodePoint = 127462; // parseInt('1F1E6', 16)
+const flagZCodePoint = 127487; // parseInt('1F1FF', 16)
+function isFlagEmoji(emojiUnicode) {
+ const cp = emojiUnicode.codePointAt(0);
+ // Length 4 because flags are made of 2 characters which are surrogate pairs
+ return emojiUnicode.length === 4 && cp >= flagACodePoint && cp <= flagZCodePoint;
+}
+
+// Chrome <57 renders keycaps oddly
+// See https://bugs.chromium.org/p/chromium/issues/detail?id=632294
+// Same issue on Windows also fixed in Chrome 57, http://i.imgur.com/rQF7woO.png
+function isKeycapEmoji(emojiUnicode) {
+ return emojiUnicode.length === 3 && emojiUnicode[2] === '\u20E3';
+}
+
+// Check for a skin tone variation emoji which aren't always supported
+const tone1 = 127995;// parseInt('1F3FB', 16)
+const tone5 = 127999;// parseInt('1F3FF', 16)
+function isSkinToneComboEmoji(emojiUnicode) {
+ return emojiUnicode.length > 2 && spreadString(emojiUnicode).some((char) => {
+ const cp = char.codePointAt(0);
+ return cp >= tone1 && cp <= tone5;
+ });
+}
+
+// macOS supports most skin tone emoji's but
+// doesn't support the skin tone versions of horse racing
+const horseRacingCodePoint = 127943;// parseInt('1F3C7', 16)
+function isHorceRacingSkinToneComboEmoji(emojiUnicode) {
+ return spreadString(emojiUnicode)[0].codePointAt(0) === horseRacingCodePoint &&
+ isSkinToneComboEmoji(emojiUnicode);
+}
+
+// Check for `family_*`, `kiss_*`, `couple_*`
+// For ex. Windows 8.1 Firefox 51.0.1, doesn't support these
+const zwj = 8205; // parseInt('200D', 16)
+const personStartCodePoint = 128102; // parseInt('1F466', 16)
+const personEndCodePoint = 128105; // parseInt('1F469', 16)
+function isPersonZwjEmoji(emojiUnicode) {
+ let hasPersonEmoji = false;
+ let hasZwj = false;
+ spreadString(emojiUnicode).forEach((character) => {
+ const cp = character.codePointAt(0);
+ if (cp === zwj) {
+ hasZwj = true;
+ } else if (cp >= personStartCodePoint && cp <= personEndCodePoint) {
+ hasPersonEmoji = true;
+ }
+ });
+
+ return hasPersonEmoji && hasZwj;
+}
+
+// Helper so we don't have to run `isFlagEmoji` twice
+// in `isEmojiUnicodeSupported` logic
+function checkFlagEmojiSupport(unicodeSupportMap, emojiUnicode) {
+ const isFlagResult = isFlagEmoji(emojiUnicode);
+ return (
+ (unicodeSupportMap.flag && isFlagResult) ||
+ !isFlagResult
+ );
+}
+
+// Helper so we don't have to run `isSkinToneComboEmoji` twice
+// in `isEmojiUnicodeSupported` logic
+function checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) {
+ const isSkinToneResult = isSkinToneComboEmoji(emojiUnicode);
+ return (
+ (unicodeSupportMap.skinToneModifier && isSkinToneResult) ||
+ !isSkinToneResult
+ );
+}
+
+// Helper func so we don't have to run `isHorceRacingSkinToneComboEmoji` twice
+// in `isEmojiUnicodeSupported` logic
+function checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnicode) {
+ const isHorseRacingSkinToneResult = isHorceRacingSkinToneComboEmoji(emojiUnicode);
+ return (
+ (unicodeSupportMap.horseRacing && isHorseRacingSkinToneResult) ||
+ !isHorseRacingSkinToneResult
+ );
+}
+
+// Helper so we don't have to run `isPersonZwjEmoji` twice
+// in `isEmojiUnicodeSupported` logic
+function checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode) {
+ const isPersonZwjResult = isPersonZwjEmoji(emojiUnicode);
+ return (
+ (unicodeSupportMap.personZwj && isPersonZwjResult) ||
+ !isPersonZwjResult
+ );
+}
+
+// Takes in a support map and determines whether
+// the given unicode emoji is supported on the platform.
+//
+// Combines all the edge case tests into a one-stop shop method
+function isEmojiUnicodeSupported(unicodeSupportMap = {}, emojiUnicode, unicodeVersion) {
+ const isOlderThanChrome57 = unicodeSupportMap.meta && unicodeSupportMap.meta.isChrome &&
+ unicodeSupportMap.meta.chromeVersion < 57;
+
+ // For comments about each scenario, see the comments above each individual respective function
+ return unicodeSupportMap[unicodeVersion] &&
+ !(isOlderThanChrome57 && isKeycapEmoji(emojiUnicode)) &&
+ checkFlagEmojiSupport(unicodeSupportMap, emojiUnicode) &&
+ checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) &&
+ checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnicode) &&
+ checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode);
+}
+
+const GlEmojiElementProto = Object.create(HTMLElement.prototype);
+GlEmojiElementProto.createdCallback = function createdCallback() {
+ const emojiUnicode = this.textContent.trim();
+ const {
+ name,
+ unicodeVersion,
+ fallbackSrc,
+ fallbackSpriteClass,
+ } = this.dataset;
+
+ const isEmojiUnicode = this.childNodes && Array.prototype.every.call(
+ this.childNodes,
+ childNode => childNode.nodeType === 3,
+ );
+ const hasImageFallback = fallbackSrc && fallbackSrc.length > 0;
+ const hasCssSpriteFalback = fallbackSpriteClass && fallbackSpriteClass.length > 0;
+
+ if (
+ isEmojiUnicode &&
+ !isEmojiUnicodeSupported(generatedUnicodeSupportMap, emojiUnicode, unicodeVersion)
+ ) {
+ // CSS sprite fallback takes precedence over image fallback
+ if (hasCssSpriteFalback) {
+ // IE 11 doesn't like adding multiple at once :(
+ this.classList.add('emoji-icon');
+ this.classList.add(fallbackSpriteClass);
+ } else if (hasImageFallback) {
+ this.innerHTML = emojiImageTag(name, fallbackSrc);
+ } else {
+ const src = assembleFallbackImageSrc(name);
+ this.innerHTML = emojiImageTag(name, src);
+ }
+ }
+};
+
+document.registerElement('gl-emoji', {
+ prototype: GlEmojiElementProto,
+});
+
+module.exports = {
+ emojiImageTag,
+ glEmojiTag,
+ isEmojiUnicodeSupported,
+ isFlagEmoji,
+ isKeycapEmoji,
+ isSkinToneComboEmoji,
+ isHorceRacingSkinToneComboEmoji,
+ isPersonZwjEmoji,
+};
diff --git a/app/assets/javascripts/behaviors/gl_emoji/spread_string.js b/app/assets/javascripts/behaviors/gl_emoji/spread_string.js
new file mode 100644
index 00000000000..2380349c4fa
--- /dev/null
+++ b/app/assets/javascripts/behaviors/gl_emoji/spread_string.js
@@ -0,0 +1,50 @@
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt#Fixing_charCodeAt()_to_handle_non-Basic-Multilingual-Plane_characters_if_their_presence_earlier_in_the_string_is_known
+function knownCharCodeAt(givenString, index) {
+ const str = `${givenString}`;
+ const end = str.length;
+
+ const surrogatePairs = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
+ let idx = index;
+ while ((surrogatePairs.exec(str)) != null) {
+ const li = surrogatePairs.lastIndex;
+ if (li - 2 < idx) {
+ idx += 1;
+ } else {
+ break;
+ }
+ }
+
+ if (idx >= end || idx < 0) {
+ return NaN;
+ }
+
+ const code = str.charCodeAt(idx);
+
+ let high;
+ let low;
+ if (code >= 0xD800 && code <= 0xDBFF) {
+ high = code;
+ low = str.charCodeAt(idx + 1);
+ // Go one further, since one of the "characters" is part of a surrogate pair
+ return ((high - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;
+ }
+ return code;
+}
+
+// See http://stackoverflow.com/a/38901550/796832
+// ES5/PhantomJS compatible version of spreading a string
+//
+// [...'foo'] -> ['f', 'o', 'o']
+// [...'🖐🏿'] -> ['🖐', '🏿']
+function spreadString(str) {
+ const arr = [];
+ let i = 0;
+ while (!isNaN(knownCharCodeAt(str, i))) {
+ const codePoint = knownCharCodeAt(str, i);
+ arr.push(String.fromCodePoint(codePoint));
+ i += 1;
+ }
+ return arr;
+}
+
+module.exports = spreadString;
diff --git a/app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js b/app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js
new file mode 100644
index 00000000000..f31716d4c07
--- /dev/null
+++ b/app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js
@@ -0,0 +1,154 @@
+const unicodeSupportTestMap = {
+ // man, student (emojione does not have any of these yet), http://emojipedia.org/emoji-zwj-sequences/
+ // occupationZwj: '\u{1F468}\u{200D}\u{1F393}',
+ // woman, biking (emojione does not have any of these yet), http://emojipedia.org/emoji-zwj-sequences/
+ // sexZwj: '\u{1F6B4}\u{200D}\u{2640}',
+ // family_mwgb
+ // Windows 8.1, Firefox 51.0.1 does not support `family_`, `kiss_`, `couple_`
+ personZwj: '\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}',
+ // horse_racing_tone5
+ // Special case that is not supported on macOS 10.12 even though `skinToneModifier` succeeds
+ horseRacing: '\u{1F3C7}\u{1F3FF}',
+ // US flag, http://emojipedia.org/flags/
+ flag: '\u{1F1FA}\u{1F1F8}',
+ // http://emojipedia.org/modifiers/
+ skinToneModifier: [
+ // spy_tone5
+ '\u{1F575}\u{1F3FF}',
+ // person_with_ball_tone5
+ '\u{26F9}\u{1F3FF}',
+ // angel_tone5
+ '\u{1F47C}\u{1F3FF}',
+ ],
+ // rofl, http://emojipedia.org/unicode-9.0/
+ '9.0': '\u{1F923}',
+ // metal, http://emojipedia.org/unicode-8.0/
+ '8.0': '\u{1F918}',
+ // spy, http://emojipedia.org/unicode-7.0/
+ '7.0': '\u{1F575}',
+ // expressionless, http://emojipedia.org/unicode-6.1/
+ 6.1: '\u{1F611}',
+ // japanese_goblin, http://emojipedia.org/unicode-6.0/
+ '6.0': '\u{1F47A}',
+ // sailboat, http://emojipedia.org/unicode-5.2/
+ 5.2: '\u{26F5}',
+ // mahjong, http://emojipedia.org/unicode-5.1/
+ 5.1: '\u{1F004}',
+ // gear, http://emojipedia.org/unicode-4.1/
+ 4.1: '\u{2699}',
+ // zap, http://emojipedia.org/unicode-4.0/
+ '4.0': '\u{26A1}',
+ // recycle, http://emojipedia.org/unicode-3.2/
+ 3.2: '\u{267B}',
+ // information_source, http://emojipedia.org/unicode-3.0/
+ '3.0': '\u{2139}',
+ // heart, http://emojipedia.org/unicode-1.1/
+ 1.1: '\u{2764}',
+};
+
+function checkPixelInImageDataArray(pixelOffset, imageDataArray) {
+ // `4 *` because RGBA
+ const indexOffset = 4 * pixelOffset;
+ const hasColor = imageDataArray[indexOffset + 0] ||
+ imageDataArray[indexOffset + 1] ||
+ imageDataArray[indexOffset + 2];
+ const isVisible = imageDataArray[indexOffset + 3];
+ // Check for some sort of color other than black
+ if (hasColor && isVisible) {
+ return true;
+ }
+ return false;
+}
+
+const chromeMatches = navigator.userAgent.match(/Chrom(?:e|ium)\/([0-9]+)\./);
+const isChrome = chromeMatches && chromeMatches.length > 0;
+const chromeVersion = chromeMatches && chromeMatches[1] && parseInt(chromeMatches[1], 10);
+
+// We use 16px because mobile Safari (iOS 9.3) doesn't properly scale emojis :/
+// See 32px, https://i.imgur.com/htY6Zym.png
+// See 16px, https://i.imgur.com/FPPsIF8.png
+const fontSize = 16;
+function testUnicodeSupportMap(testMap) {
+ const testMapKeys = Object.keys(testMap);
+ const numTestEntries = testMapKeys
+ .reduce((list, testKey) => list.concat(testMap[testKey]), []).length;
+
+ const canvas = document.createElement('canvas');
+ (window.gl || window).testEmojiUnicodeSupportMapCanvas = canvas;
+ const ctx = canvas.getContext('2d');
+ canvas.width = (2 * fontSize);
+ canvas.height = (numTestEntries * fontSize);
+ ctx.fillStyle = '#000000';
+ ctx.textBaseline = 'middle';
+ ctx.font = `${fontSize}px "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`;
+ // Write each emoji to the canvas vertically
+ let writeIndex = 0;
+ testMapKeys.forEach((testKey) => {
+ const testEntry = testMap[testKey];
+ [].concat(testEntry).forEach((emojiUnicode) => {
+ ctx.fillText(emojiUnicode, 0, (writeIndex * fontSize) + (fontSize / 2));
+ writeIndex += 1;
+ });
+ });
+
+ // Read from the canvas
+ const resultMap = {};
+ let readIndex = 0;
+ testMapKeys.forEach((testKey) => {
+ const testEntry = testMap[testKey];
+ // This needs to be a `reduce` instead of `every` because we need to
+ // keep the `readIndex` in sync from the writes by running all entries
+ const isTestSatisfied = [].concat(testEntry).reduce((isSatisfied) => {
+ // Sample along the vertical-middle for a couple of characters
+ const imageData = ctx.getImageData(
+ 0,
+ (readIndex * fontSize) + (fontSize / 2),
+ 2 * fontSize,
+ 1,
+ ).data;
+
+ let isValidEmoji = false;
+ for (let currentPixel = 0; currentPixel < 64; currentPixel += 1) {
+ const isLookingAtFirstChar = currentPixel < fontSize;
+ const isLookingAtSecondChar = currentPixel >= (fontSize + (fontSize / 2));
+ // Check for the emoji somewhere along the row
+ if (isLookingAtFirstChar && checkPixelInImageDataArray(currentPixel, imageData)) {
+ isValidEmoji = true;
+
+ // Check to see that nothing is rendered next to the first character
+ // to ensure that the ZWJ sequence rendered as one piece
+ } else if (isLookingAtSecondChar && checkPixelInImageDataArray(currentPixel, imageData)) {
+ isValidEmoji = false;
+ break;
+ }
+ }
+
+ readIndex += 1;
+ return isSatisfied && isValidEmoji;
+ }, true);
+
+ resultMap[testKey] = isTestSatisfied;
+ });
+
+ resultMap.meta = {
+ isChrome,
+ chromeVersion,
+ };
+
+ return resultMap;
+}
+
+let unicodeSupportMap;
+const userAgentFromCache = window.localStorage.getItem('gl-emoji-user-agent');
+try {
+ unicodeSupportMap = JSON.parse(window.localStorage.getItem('gl-emoji-unicode-support-map'));
+} catch (err) {
+ // swallow
+}
+if (!unicodeSupportMap || userAgentFromCache !== navigator.userAgent) {
+ unicodeSupportMap = testUnicodeSupportMap(unicodeSupportTestMap);
+ window.localStorage.setItem('gl-emoji-user-agent', navigator.userAgent);
+ window.localStorage.setItem('gl-emoji-unicode-support-map', JSON.stringify(unicodeSupportMap));
+}
+
+module.exports = unicodeSupportMap;
diff --git a/app/assets/javascripts/boards/components/board_list.js b/app/assets/javascripts/boards/components/board_list.js
index 2d52e96e7fb..1330d4ae840 100644
--- a/app/assets/javascripts/boards/components/board_list.js
+++ b/app/assets/javascripts/boards/components/board_list.js
@@ -56,11 +56,6 @@ import boardCard from './board_card';
});
}
},
- computed: {
- orderedIssues () {
- return _.sortBy(this.issues, 'priority');
- },
- },
methods: {
listHeight () {
return this.$refs.list.getBoundingClientRect().height;
@@ -92,9 +87,9 @@ import boardCard from './board_card';
const options = gl.issueBoards.getBoardSortableDefaultOptions({
scroll: document.querySelectorAll('.boards-list')[0],
group: 'issues',
- sort: false,
disabled: this.disabled,
filter: '.board-list-count, .is-disabled',
+ dataIdAttr: 'data-issue-id',
onStart: (e) => {
const card = this.$refs.issue[e.oldIndex];
@@ -111,6 +106,13 @@ import boardCard from './board_card';
e.item.remove();
});
},
+ onUpdate: (e) => {
+ const sortedArray = this.sortable.toArray().filter(id => id !== '-1');
+ gl.issueBoards.BoardsStore.moveIssueInList(this.list, Store.moving.issue, e.oldIndex, e.newIndex, sortedArray);
+ },
+ onMove(e) {
+ return !e.related.classList.contains('board-list-count');
+ }
});
this.sortable = Sortable.create(this.$refs.list, options);
diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js
index 2d0a295ae4d..ca5e6fa7e9d 100644
--- a/app/assets/javascripts/boards/models/issue.js
+++ b/app/assets/javascripts/boards/models/issue.js
@@ -15,6 +15,7 @@ class ListIssue {
this.labels = [];
this.selected = false;
this.assignee = false;
+ this.position = obj.relative_position || Infinity;
if (obj.assignee) {
this.assignee = new ListUser(obj.assignee);
@@ -27,10 +28,6 @@ class ListIssue {
obj.labels.forEach((label) => {
this.labels.push(new ListLabel(label));
});
-
- this.priority = this.labels.reduce((max, label) => {
- return (label.priority < max) ? label.priority : max;
- }, Infinity);
}
addLabel (label) {
diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js
index 8158ed4ec2c..f237567208c 100644
--- a/app/assets/javascripts/boards/models/list.js
+++ b/app/assets/javascripts/boards/models/list.js
@@ -110,9 +110,20 @@ class List {
}
addIssue (issue, listFrom, newIndex) {
+ let moveBeforeIid = null;
+ let moveAfterIid = null;
+
if (!this.findIssue(issue.id)) {
if (newIndex !== undefined) {
this.issues.splice(newIndex, 0, issue);
+
+ if (this.issues[newIndex - 1]) {
+ moveBeforeIid = this.issues[newIndex - 1].id;
+ }
+
+ if (this.issues[newIndex + 1]) {
+ moveAfterIid = this.issues[newIndex + 1].id;
+ }
} else {
this.issues.push(issue);
}
@@ -123,13 +134,21 @@ class List {
if (listFrom) {
this.issuesSize += 1;
- this.updateIssueLabel(issue, listFrom);
+
+ this.updateIssueLabel(issue, listFrom, moveBeforeIid, moveAfterIid);
}
}
}
- updateIssueLabel(issue, listFrom) {
- gl.boardService.moveIssue(issue.id, listFrom.id, this.id)
+ moveIssue (issue, oldIndex, newIndex, moveBeforeIid, moveAfterIid) {
+ this.issues.splice(oldIndex, 1);
+ this.issues.splice(newIndex, 0, issue);
+
+ gl.boardService.moveIssue(issue.id, null, null, moveBeforeIid, moveAfterIid);
+ }
+
+ updateIssueLabel(issue, listFrom, moveBeforeIid, moveAfterIid) {
+ gl.boardService.moveIssue(issue.id, listFrom.id, this.id, moveBeforeIid, moveAfterIid)
.then(() => {
listFrom.getIssues(false);
});
diff --git a/app/assets/javascripts/boards/services/board_service.js b/app/assets/javascripts/boards/services/board_service.js
index 065e90518df..e54102814d6 100644
--- a/app/assets/javascripts/boards/services/board_service.js
+++ b/app/assets/javascripts/boards/services/board_service.js
@@ -64,10 +64,12 @@ class BoardService {
return this.issues.get(data);
}
- moveIssue (id, from_list_id, to_list_id) {
+ moveIssue (id, from_list_id = null, to_list_id = null, move_before_iid = null, move_after_iid = null) {
return this.issue.update({ id }, {
from_list_id,
- to_list_id
+ to_list_id,
+ move_before_iid,
+ move_after_iid,
});
}
diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js
index 56436c8fdc7..3866c6bbfc6 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js
+++ b/app/assets/javascripts/boards/stores/boards_store.js
@@ -109,6 +109,12 @@
listFrom.removeIssue(issue);
}
},
+ moveIssueInList (list, issue, oldIndex, newIndex, idArray) {
+ const beforeId = parseInt(idArray[newIndex - 1], 10) || null;
+ const afterId = parseInt(idArray[newIndex + 1], 10) || null;
+
+ list.moveIssue(issue, oldIndex, newIndex, beforeId, afterId);
+ },
findList (key, val, type = 'label') {
return this.state.lists.filter((list) => {
const byType = type ? list['type'] === type : true;
diff --git a/app/assets/javascripts/copy_as_gfm.js b/app/assets/javascripts/copy_as_gfm.js
index 16bdb4db5af..8883c339335 100644
--- a/app/assets/javascripts/copy_as_gfm.js
+++ b/app/assets/javascripts/copy_as_gfm.js
@@ -49,6 +49,9 @@ require('./lib/utils/common_utils');
'img.emoji'(el, text) {
return el.getAttribute('alt');
},
+ 'gl-emoji'(el, text) {
+ return `:${el.getAttribute('data-name')}:`;
+ },
},
ImageLinkFilter: {
'a.no-attachment-icon'(el, text) {
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 31f10f89245..546bdc9c8d7 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -1,3 +1,4 @@
+import PrometheusGraph from './monitoring/prometheus_graph'; // TODO: Maybe Make this a bundle
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */
/* global UsernameValidator */
/* global ActiveTabMemoizer */
@@ -286,7 +287,7 @@ const UserCallout = require('./user_callout');
case 'search:show':
new Search();
break;
- case 'projects:protected_branches:index':
+ case 'projects:repository:show':
new gl.ProtectedBranchCreate();
new gl.ProtectedBranchEditList();
break;
@@ -297,6 +298,8 @@ const UserCallout = require('./user_callout');
case 'ci:lints:show':
new gl.CILintEditor();
break;
+ case 'projects:environments:metrics':
+ new PrometheusGraph();
case 'users:show':
new UserCallout();
break;
diff --git a/app/assets/javascripts/droplab/droplab_ajax.js b/app/assets/javascripts/droplab/droplab_ajax.js
index 5cdf11c6a2c..f61be741b4a 100644
--- a/app/assets/javascripts/droplab/droplab_ajax.js
+++ b/app/assets/javascripts/droplab/droplab_ajax.js
@@ -37,11 +37,14 @@ require('../window')(function(w){
}
}
- self.hook.list[config.method].call(self.hook.list, data);
+ if (!self.destroyed) {
+ self.hook.list[config.method].call(self.hook.list, data);
+ }
},
init: function init(hook) {
var self = this;
+ self.destroyed = false;
self.cache = self.cache || {};
var config = hook.config.droplabAjax;
this.hook = hook;
@@ -79,6 +82,7 @@ require('../window')(function(w){
destroy: function() {
var dynamicList = this.hook.list.list.querySelector('[data-dynamic]');
+ this.destroyed = true;
if (this.listTemplate && dynamicList) {
dynamicList.outerHTML = this.listTemplate;
}
diff --git a/app/assets/javascripts/extensions/string.js b/app/assets/javascripts/extensions/string.js
new file mode 100644
index 00000000000..fe23be0bbc1
--- /dev/null
+++ b/app/assets/javascripts/extensions/string.js
@@ -0,0 +1,2 @@
+require('string.prototype.codepointat');
+require('string.fromcodepoint');
diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js b/app/assets/javascripts/filtered_search/dropdown_hint.js
index 9e92d544bef..38ff3fb7158 100644
--- a/app/assets/javascripts/filtered_search/dropdown_hint.js
+++ b/app/assets/javascripts/filtered_search/dropdown_hint.js
@@ -28,6 +28,23 @@ require('./filtered_search_dropdown');
const tag = selected.querySelector('.js-filter-tag').innerText.trim();
if (tag.length) {
+ // Get previous input values in the input field and convert them into visual tokens
+ const previousInputValues = this.input.value.split(' ');
+ const searchTerms = [];
+
+ previousInputValues.forEach((value, index) => {
+ searchTerms.push(value);
+
+ if (index === previousInputValues.length - 1
+ && token.indexOf(value.toLowerCase()) !== -1) {
+ searchTerms.pop();
+ }
+ });
+
+ if (searchTerms.length > 0) {
+ gl.FilteredSearchVisualTokens.addSearchVisualToken(searchTerms.join(' '));
+ }
+
gl.FilteredSearchDropdownManager.addWordToInput(token.replace(':', ''));
}
this.dismissDropdown();
@@ -39,7 +56,7 @@ require('./filtered_search_dropdown');
renderContent() {
const dropdownData = [];
- [].forEach.call(this.input.parentElement.querySelectorAll('.dropdown-menu'), (dropdownMenu) => {
+ [].forEach.call(this.input.closest('.filtered-search-input-container').querySelectorAll('.dropdown-menu'), (dropdownMenu) => {
const { icon, hint, tag } = dropdownMenu.dataset;
if (icon && hint && tag) {
dropdownData.push({
diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js b/app/assets/javascripts/filtered_search/dropdown_user.js
index 7e9c6f74aa5..04e2afad02f 100644
--- a/app/assets/javascripts/filtered_search/dropdown_user.js
+++ b/app/assets/javascripts/filtered_search/dropdown_user.js
@@ -39,7 +39,12 @@ require('./filtered_search_dropdown');
getSearchInput() {
const query = gl.DropdownUtils.getSearchInput(this.input);
const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query);
- let value = lastToken.value || '';
+
+ let value = lastToken || '';
+
+ if (value[0] === '@') {
+ value = value.slice(1);
+ }
// Removes the first character if it is a quotation so that we can search
// with multiple words
diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js b/app/assets/javascripts/filtered_search/dropdown_utils.js
index de3fa116717..b52081df646 100644
--- a/app/assets/javascripts/filtered_search/dropdown_utils.js
+++ b/app/assets/javascripts/filtered_search/dropdown_utils.js
@@ -22,38 +22,40 @@
static filterWithSymbol(filterSymbol, input, item) {
const updatedItem = item;
- const query = gl.DropdownUtils.getSearchInput(input);
- const { lastToken, searchToken } = gl.FilteredSearchTokenizer.processTokens(query);
+ const searchInput = gl.DropdownUtils.getSearchInput(input);
- if (lastToken !== searchToken) {
- const title = updatedItem.title.toLowerCase();
- let value = lastToken.value.toLowerCase();
+ const title = updatedItem.title.toLowerCase();
+ let value = searchInput.toLowerCase();
+ let symbol = '';
- // Removes the first character if it is a quotation so that we can search
- // with multiple words
- if ((value[0] === '"' || value[0] === '\'') && title.indexOf(' ') !== -1) {
- value = value.slice(1);
- }
-
- // Eg. filterSymbol = ~ for labels
- const matchWithoutSymbol = lastToken.symbol === filterSymbol && title.indexOf(value) !== -1;
- const match = title.indexOf(`${lastToken.symbol}${value}`) !== -1;
+ // Remove the symbol for filter
+ if (value[0] === filterSymbol) {
+ symbol = value[0];
+ value = value.slice(1);
+ }
- updatedItem.droplab_hidden = !match && !matchWithoutSymbol;
- } else {
- updatedItem.droplab_hidden = false;
+ // Removes the first character if it is a quotation so that we can search
+ // with multiple words
+ if ((value[0] === '"' || value[0] === '\'') && title.indexOf(' ') !== -1) {
+ value = value.slice(1);
}
+ // Eg. filterSymbol = ~ for labels
+ const matchWithoutSymbol = symbol === filterSymbol && title.indexOf(value) !== -1;
+ const match = title.indexOf(`${symbol}${value}`) !== -1;
+
+ updatedItem.droplab_hidden = !match && !matchWithoutSymbol;
+
return updatedItem;
}
static filterHint(input, item) {
const updatedItem = item;
- const query = gl.DropdownUtils.getSearchInput(input);
- let { lastToken } = gl.FilteredSearchTokenizer.processTokens(query);
+ const searchInput = gl.DropdownUtils.getSearchInput(input);
+ let { lastToken } = gl.FilteredSearchTokenizer.processTokens(searchInput);
lastToken = lastToken.key || lastToken || '';
- if (!lastToken || query.split('').last() === ' ') {
+ if (!lastToken || searchInput.split('').last() === ' ') {
updatedItem.droplab_hidden = false;
} else if (lastToken) {
const split = lastToken.split(':');
@@ -70,13 +72,41 @@
const dataValue = selected.getAttribute('data-value');
if (dataValue) {
- gl.FilteredSearchDropdownManager.addWordToInput(filter, dataValue);
+ gl.FilteredSearchDropdownManager.addWordToInput(filter, dataValue, true);
}
// Return boolean based on whether it was set
return dataValue !== null;
}
+ // Determines the full search query (visual tokens + input)
+ static getSearchQuery() {
+ const tokensContainer = document.querySelector('.tokens-container');
+ const values = [];
+
+ [].forEach.call(tokensContainer.querySelectorAll('.js-visual-token'), (token) => {
+ const name = token.querySelector('.name');
+ const value = token.querySelector('.value');
+ const symbol = value && value.dataset.symbol ? value.dataset.symbol : '';
+ let valueText = '';
+
+ if (value && value.innerText) {
+ valueText = value.innerText;
+ }
+
+ if (token.className.indexOf('filtered-search-token') !== -1) {
+ values.push(`${name.innerText.toLowerCase()}:${symbol}${valueText}`);
+ } else {
+ values.push(name.innerText);
+ }
+ });
+
+ const input = document.querySelector('.filtered-search');
+ values.push(input && input.value);
+
+ return values.join(' ');
+ }
+
static getSearchInput(filteredSearchInput) {
const inputValue = filteredSearchInput.value;
const { right } = gl.DropdownUtils.getInputSelectionPosition(filteredSearchInput);
diff --git a/app/assets/javascripts/filtered_search/filtered_search_bundle.js b/app/assets/javascripts/filtered_search/filtered_search_bundle.js
index faaba994f46..856eb6590ee 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_bundle.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_bundle.js
@@ -7,3 +7,4 @@ require('./filtered_search_dropdown');
require('./filtered_search_manager');
require('./filtered_search_token_keys');
require('./filtered_search_tokenizer');
+require('./filtered_search_visual_tokens');
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js
index dd565da507e..134bdc6ad80 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js
@@ -35,7 +35,7 @@
if (!dataValueSet) {
const value = getValueFunction(selected);
- gl.FilteredSearchDropdownManager.addWordToInput(this.filter, value);
+ gl.FilteredSearchDropdownManager.addWordToInput(this.filter, value, true);
}
this.dismissDropdown();
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
index cecd3518ce3..608c65c78a4 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
@@ -58,35 +58,15 @@
};
}
- static addWordToInput(tokenName, tokenValue = '') {
+ static addWordToInput(tokenName, tokenValue = '', clicked = false) {
const input = document.querySelector('.filtered-search');
- const inputValue = input.value;
- const word = `${tokenName}:${tokenValue}`;
- // Get the string to replace
- let newCaretPosition = input.selectionStart;
- const { left, right } = gl.DropdownUtils.getInputSelectionPosition(input);
+ gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenName, tokenValue);
+ input.value = '';
- input.value = `${inputValue.substr(0, left)}${word}${inputValue.substr(right)}`;
-
- // If we have added a tokenValue at the end of the input,
- // add a space and set selection to the end
- if (right >= inputValue.length && tokenValue !== '') {
- input.value += ' ';
- newCaretPosition = input.value.length;
+ if (clicked) {
+ gl.FilteredSearchVisualTokens.moveInputToTheRight();
}
-
- gl.FilteredSearchDropdownManager.updateInputCaretPosition(newCaretPosition, input);
- }
-
- static updateInputCaretPosition(selectionStart, input) {
- // Reset the position
- // Sometimes can end up at end of input
- input.setSelectionRange(selectionStart, selectionStart);
-
- const { right } = gl.DropdownUtils.getInputSelectionPosition(input);
-
- input.setSelectionRange(right, right);
}
updateCurrentDropdownOffset() {
@@ -94,19 +74,14 @@
}
updateDropdownOffset(key) {
- if (!this.font) {
- this.font = window.getComputedStyle(this.filteredSearchInput).font;
- }
-
- const input = this.filteredSearchInput;
- const inputText = input.value.slice(0, input.selectionStart);
- const filterIconPadding = 27;
- let offset = gl.text.getTextWidth(inputText, this.font) + filterIconPadding;
+ // Always align dropdown with the input field
+ let offset = this.filteredSearchInput.getBoundingClientRect().left - document.querySelector('.scroll-container').getBoundingClientRect().left;
- const currentDropdownWidth = this.mapping[key].element.clientWidth === 0 ? 200 :
- this.mapping[key].element.clientWidth;
- const offsetMaxWidth = this.filteredSearchInput.clientWidth - currentDropdownWidth;
+ const maxInputWidth = 240;
+ const currentDropdownWidth = this.mapping[key].element.clientWidth || maxInputWidth;
+ // Make sure offset never exceeds the input container
+ const offsetMaxWidth = document.querySelector('.scroll-container').clientWidth - currentDropdownWidth;
if (offsetMaxWidth < offset) {
offset = offsetMaxWidth;
}
@@ -164,8 +139,8 @@
}
setDropdown() {
- const { lastToken, searchToken } = this.tokenizer
- .processTokens(gl.DropdownUtils.getSearchInput(this.filteredSearchInput));
+ const query = gl.DropdownUtils.getSearchQuery();
+ const { lastToken, searchToken } = this.tokenizer.processTokens(query);
if (this.currentDropdown) {
this.updateCurrentDropdownOffset();
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index bbafead0305..58a984048de 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -3,6 +3,7 @@
constructor(page) {
this.filteredSearchInput = document.querySelector('.filtered-search');
this.clearSearchButton = document.querySelector('.clear-search');
+ this.tokensContainer = document.querySelector('.tokens-container');
this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
if (this.filteredSearchInput) {
@@ -27,36 +28,62 @@
this.handleFormSubmit = this.handleFormSubmit.bind(this);
this.setDropdownWrapper = this.dropdownManager.setDropdown.bind(this.dropdownManager);
this.toggleClearSearchButtonWrapper = this.toggleClearSearchButton.bind(this);
+ this.handleInputPlaceholderWrapper = this.handleInputPlaceholder.bind(this);
+ this.handleInputVisualTokenWrapper = this.handleInputVisualToken.bind(this);
this.checkForEnterWrapper = this.checkForEnter.bind(this);
this.clearSearchWrapper = this.clearSearch.bind(this);
this.checkForBackspaceWrapper = this.checkForBackspace.bind(this);
+ this.removeSelectedTokenWrapper = this.removeSelectedToken.bind(this);
+ this.unselectEditTokensWrapper = this.unselectEditTokens.bind(this);
+ this.editTokenWrapper = this.editToken.bind(this);
this.tokenChange = this.tokenChange.bind(this);
this.filteredSearchInput.form.addEventListener('submit', this.handleFormSubmit);
this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper);
this.filteredSearchInput.addEventListener('input', this.toggleClearSearchButtonWrapper);
+ this.filteredSearchInput.addEventListener('input', this.handleInputPlaceholderWrapper);
+ this.filteredSearchInput.addEventListener('input', this.handleInputVisualTokenWrapper);
this.filteredSearchInput.addEventListener('keydown', this.checkForEnterWrapper);
this.filteredSearchInput.addEventListener('keyup', this.checkForBackspaceWrapper);
this.filteredSearchInput.addEventListener('click', this.tokenChange);
this.filteredSearchInput.addEventListener('keyup', this.tokenChange);
+ this.tokensContainer.addEventListener('click', FilteredSearchManager.selectToken);
+ this.tokensContainer.addEventListener('dblclick', this.editTokenWrapper);
this.clearSearchButton.addEventListener('click', this.clearSearchWrapper);
+ document.addEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens);
+ document.addEventListener('click', this.unselectEditTokensWrapper);
+ document.addEventListener('keydown', this.removeSelectedTokenWrapper);
}
unbindEvents() {
this.filteredSearchInput.form.removeEventListener('submit', this.handleFormSubmit);
this.filteredSearchInput.removeEventListener('input', this.setDropdownWrapper);
this.filteredSearchInput.removeEventListener('input', this.toggleClearSearchButtonWrapper);
+ this.filteredSearchInput.removeEventListener('input', this.handleInputPlaceholderWrapper);
+ this.filteredSearchInput.removeEventListener('input', this.handleInputVisualTokenWrapper);
this.filteredSearchInput.removeEventListener('keydown', this.checkForEnterWrapper);
this.filteredSearchInput.removeEventListener('keyup', this.checkForBackspaceWrapper);
this.filteredSearchInput.removeEventListener('click', this.tokenChange);
this.filteredSearchInput.removeEventListener('keyup', this.tokenChange);
+ this.tokensContainer.removeEventListener('click', FilteredSearchManager.selectToken);
+ this.tokensContainer.removeEventListener('dblclick', this.editTokenWrapper);
this.clearSearchButton.removeEventListener('click', this.clearSearchWrapper);
+ document.removeEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens);
+ document.removeEventListener('click', this.unselectEditTokensWrapper);
+ document.removeEventListener('keydown', this.removeSelectedTokenWrapper);
}
checkForBackspace(e) {
// 8 = Backspace Key
// 46 = Delete Key
if (e.keyCode === 8 || e.keyCode === 46) {
+ const { lastVisualToken } = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+
+ if (this.filteredSearchInput.value === '' && lastVisualToken) {
+ this.filteredSearchInput.value = gl.FilteredSearchVisualTokens.getLastTokenPartial();
+ gl.FilteredSearchVisualTokens.removeLastTokenPartial();
+ }
+
// Reposition dropdown so that it is aligned with cursor
this.dropdownManager.updateCurrentDropdownOffset();
}
@@ -86,11 +113,68 @@
}
}
- toggleClearSearchButton(e) {
- if (e.target.value) {
- this.clearSearchButton.classList.remove('hidden');
- } else {
- this.clearSearchButton.classList.add('hidden');
+ static selectToken(e) {
+ const button = e.target.closest('.selectable');
+
+ if (button) {
+ e.preventDefault();
+ e.stopPropagation();
+ gl.FilteredSearchVisualTokens.selectToken(button);
+ }
+ }
+
+ unselectEditTokens(e) {
+ const inputContainer = document.querySelector('.filtered-search-input-container');
+ const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target);
+ const isElementInFilterDropdown = e.target.closest('.filter-dropdown') !== null;
+ const isElementTokensContainer = e.target.classList.contains('tokens-container');
+
+ if ((!isElementInFilteredSearch && !isElementInFilterDropdown) || isElementTokensContainer) {
+ gl.FilteredSearchVisualTokens.moveInputToTheRight();
+ this.dropdownManager.resetDropdowns();
+ }
+ }
+
+ editToken(e) {
+ const token = e.target.closest('.js-visual-token');
+
+ if (token) {
+ gl.FilteredSearchVisualTokens.editToken(token);
+ this.tokenChange();
+ }
+ }
+
+ toggleClearSearchButton() {
+ const query = gl.DropdownUtils.getSearchQuery();
+ const hidden = 'hidden';
+ const hasHidden = this.clearSearchButton.classList.contains(hidden);
+
+ if (query.length === 0 && !hasHidden) {
+ this.clearSearchButton.classList.add(hidden);
+ } else if (query.length && hasHidden) {
+ this.clearSearchButton.classList.remove(hidden);
+ }
+ }
+
+ handleInputPlaceholder() {
+ const query = gl.DropdownUtils.getSearchQuery();
+ const placeholder = 'Search or filter results...';
+ const currentPlaceholder = this.filteredSearchInput.placeholder;
+
+ if (query.length === 0 && currentPlaceholder !== placeholder) {
+ this.filteredSearchInput.placeholder = placeholder;
+ } else if (query.length > 0 && currentPlaceholder !== '') {
+ this.filteredSearchInput.placeholder = '';
+ }
+ }
+
+ removeSelectedToken(e) {
+ // 8 = Backspace Key
+ // 46 = Delete Key
+ if (e.keyCode === 8 || e.keyCode === 46) {
+ gl.FilteredSearchVisualTokens.removeSelectedToken();
+ this.handleInputPlaceholder();
+ this.toggleClearSearchButton();
}
}
@@ -98,11 +182,67 @@
e.preventDefault();
this.filteredSearchInput.value = '';
+
+ const removeElements = [];
+
+ [].forEach.call(this.tokensContainer.children, (t) => {
+ if (t.classList.contains('js-visual-token')) {
+ removeElements.push(t);
+ }
+ });
+
+ removeElements.forEach((el) => {
+ el.parentElement.removeChild(el);
+ });
+
this.clearSearchButton.classList.add('hidden');
+ this.handleInputPlaceholder();
this.dropdownManager.resetDropdowns();
}
+ handleInputVisualToken() {
+ const input = this.filteredSearchInput;
+ const { tokens, searchToken }
+ = gl.FilteredSearchTokenizer.processTokens(input.value);
+ const { isLastVisualTokenValid }
+ = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+
+ if (isLastVisualTokenValid) {
+ tokens.forEach((t) => {
+ input.value = input.value.replace(`${t.key}:${t.symbol}${t.value}`, '');
+ gl.FilteredSearchVisualTokens.addFilterVisualToken(t.key, `${t.symbol}${t.value}`);
+ });
+
+ const fragments = searchToken.split(':');
+ if (fragments.length > 1) {
+ const inputValues = fragments[0].split(' ');
+ const tokenKey = inputValues.last();
+
+ if (inputValues.length > 1) {
+ inputValues.pop();
+ const searchTerms = inputValues.join(' ');
+
+ input.value = input.value.replace(searchTerms, '');
+ gl.FilteredSearchVisualTokens.addSearchVisualToken(searchTerms);
+ }
+
+ gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenKey);
+ input.value = input.value.replace(`${tokenKey}:`, '');
+ }
+ } else {
+ // Keep listening to token until we determine that the user is done typing the token value
+ const valueCompletedRegex = /([~%@]{0,1}".+")|([~%@]{0,1}'.+')|^((?![~%@]')(?![~%@]")(?!')(?!")).*/g;
+
+ if (searchToken.match(valueCompletedRegex) && input.value[input.value.length - 1] === ' ') {
+ gl.FilteredSearchVisualTokens.addFilterVisualToken(searchToken);
+
+ // Trim the last space as seen in the if statement above
+ input.value = input.value.replace(searchToken, '').trim();
+ }
+ }
+ }
+
handleFormSubmit(e) {
e.preventDefault();
this.search();
@@ -111,7 +251,7 @@
loadSearchParamsFromURL() {
const params = gl.utils.getUrlParamsArray();
const usernameParams = this.getUsernameParams();
- const inputValues = [];
+ let hasFilteredSearch = false;
params.forEach((p) => {
const split = p.split('=');
@@ -122,7 +262,8 @@
const condition = this.filteredSearchTokenKeys.searchByConditionUrl(p);
if (condition) {
- inputValues.push(`${condition.tokenKey}:${condition.value}`);
+ hasFilteredSearch = true;
+ gl.FilteredSearchVisualTokens.addFilterVisualToken(condition.tokenKey, condition.value);
} else {
// Sanitize value since URL converts spaces into +
// Replace before decode so that we know what was originally + versus the encoded +
@@ -140,34 +281,37 @@
quotationsToUse = sanitizedValue.indexOf('"') === -1 ? '"' : '\'';
}
- inputValues.push(`${sanitizedKey}:${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}`);
+ hasFilteredSearch = true;
+ gl.FilteredSearchVisualTokens.addFilterVisualToken(sanitizedKey, `${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}`);
} else if (!match && keyParam === 'assignee_id') {
const id = parseInt(value, 10);
if (usernameParams[id]) {
- inputValues.push(`assignee:@${usernameParams[id]}`);
+ hasFilteredSearch = true;
+ gl.FilteredSearchVisualTokens.addFilterVisualToken('assignee', `@${usernameParams[id]}`);
}
} else if (!match && keyParam === 'author_id') {
const id = parseInt(value, 10);
if (usernameParams[id]) {
- inputValues.push(`author:@${usernameParams[id]}`);
+ hasFilteredSearch = true;
+ gl.FilteredSearchVisualTokens.addFilterVisualToken('author', `@${usernameParams[id]}`);
}
} else if (!match && keyParam === 'search') {
- inputValues.push(sanitizedValue);
+ hasFilteredSearch = true;
+ this.filteredSearchInput.value = sanitizedValue;
}
}
});
- // Trim the last space value
- this.filteredSearchInput.value = inputValues.join(' ');
-
- if (inputValues.length > 0) {
+ if (hasFilteredSearch) {
this.clearSearchButton.classList.remove('hidden');
+ this.handleInputPlaceholder();
}
}
search() {
const paths = [];
- const { tokens, searchToken } = this.tokenizer.processTokens(this.filteredSearchInput.value);
+ const { tokens, searchToken }
+ = this.tokenizer.processTokens(gl.DropdownUtils.getSearchQuery());
const currentState = gl.utils.getParameterByName('state') || 'opened';
paths.push(`state=${currentState}`);
diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
new file mode 100644
index 00000000000..320afa26130
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
@@ -0,0 +1,200 @@
+class FilteredSearchVisualTokens {
+ static getLastVisualTokenBeforeInput() {
+ const inputLi = document.querySelector('.input-token');
+ const lastVisualToken = inputLi && inputLi.previousElementSibling;
+
+ return {
+ lastVisualToken,
+ isLastVisualTokenValid: lastVisualToken === null || lastVisualToken.className.indexOf('filtered-search-term') !== -1 || (lastVisualToken && lastVisualToken.querySelector('.value') !== null),
+ };
+ }
+
+ static unselectTokens() {
+ const otherTokens = document.querySelectorAll('.js-visual-token .selectable.selected');
+ [].forEach.call(otherTokens, t => t.classList.remove('selected'));
+ }
+
+ static selectToken(tokenButton) {
+ const selected = tokenButton.classList.contains('selected');
+ FilteredSearchVisualTokens.unselectTokens();
+
+ if (!selected) {
+ tokenButton.classList.add('selected');
+ }
+ }
+
+ static removeSelectedToken() {
+ const selected = document.querySelector('.js-visual-token .selected');
+
+ if (selected) {
+ const li = selected.closest('.js-visual-token');
+ li.parentElement.removeChild(li);
+ }
+ }
+
+ static createVisualTokenElementHTML() {
+ return `
+ <div class="selectable" role="button">
+ <div class="name"></div>
+ <div class="value"></div>
+ </div>
+ `;
+ }
+
+ static addVisualTokenElement(name, value, isSearchTerm) {
+ const li = document.createElement('li');
+ li.classList.add('js-visual-token');
+ li.classList.add(isSearchTerm ? 'filtered-search-term' : 'filtered-search-token');
+
+ if (value) {
+ li.innerHTML = FilteredSearchVisualTokens.createVisualTokenElementHTML();
+ li.querySelector('.value').innerText = value;
+ } else {
+ li.innerHTML = '<div class="name"></div>';
+ }
+ li.querySelector('.name').innerText = name;
+
+ const tokensContainer = document.querySelector('.tokens-container');
+ const input = document.querySelector('.filtered-search');
+ tokensContainer.insertBefore(li, input.parentElement);
+ }
+
+ static addValueToPreviousVisualTokenElement(value) {
+ const { lastVisualToken, isLastVisualTokenValid } =
+ FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+
+ if (!isLastVisualTokenValid && lastVisualToken.classList.contains('filtered-search-token')) {
+ const name = FilteredSearchVisualTokens.getLastTokenPartial();
+ lastVisualToken.innerHTML = FilteredSearchVisualTokens.createVisualTokenElementHTML();
+ lastVisualToken.querySelector('.name').innerText = name;
+ lastVisualToken.querySelector('.value').innerText = value;
+ }
+ }
+
+ static addFilterVisualToken(tokenName, tokenValue) {
+ const { lastVisualToken, isLastVisualTokenValid }
+ = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+ const addVisualTokenElement = FilteredSearchVisualTokens.addVisualTokenElement;
+
+ if (isLastVisualTokenValid) {
+ addVisualTokenElement(tokenName, tokenValue);
+ } else {
+ const previousTokenName = lastVisualToken.querySelector('.name').innerText;
+ const tokensContainer = document.querySelector('.tokens-container');
+ tokensContainer.removeChild(lastVisualToken);
+
+ const value = tokenValue || tokenName;
+ addVisualTokenElement(previousTokenName, value);
+ }
+ }
+
+ static addSearchVisualToken(searchTerm) {
+ const { lastVisualToken } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+
+ if (lastVisualToken && lastVisualToken.classList.contains('filtered-search-term')) {
+ lastVisualToken.querySelector('.name').innerText += ` ${searchTerm}`;
+ } else {
+ FilteredSearchVisualTokens.addVisualTokenElement(searchTerm, null, true);
+ }
+ }
+
+ static getLastTokenPartial() {
+ const { lastVisualToken } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+
+ if (!lastVisualToken) return '';
+
+ const value = lastVisualToken.querySelector('.value');
+ const name = lastVisualToken.querySelector('.name');
+
+ const valueText = value ? value.innerText : '';
+ const nameText = name ? name.innerText : '';
+
+ return valueText || nameText;
+ }
+
+ static removeLastTokenPartial() {
+ const { lastVisualToken } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+
+ if (lastVisualToken) {
+ const value = lastVisualToken.querySelector('.value');
+
+ if (value) {
+ const button = lastVisualToken.querySelector('.selectable');
+ button.removeChild(value);
+ lastVisualToken.innerHTML = button.innerHTML;
+ } else {
+ lastVisualToken.closest('.tokens-container').removeChild(lastVisualToken);
+ }
+ }
+ }
+
+ static tokenizeInput() {
+ const input = document.querySelector('.filtered-search');
+ const { isLastVisualTokenValid } =
+ gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+
+ if (input.value) {
+ if (isLastVisualTokenValid) {
+ gl.FilteredSearchVisualTokens.addSearchVisualToken(input.value);
+ } else {
+ FilteredSearchVisualTokens.addValueToPreviousVisualTokenElement(input.value);
+ }
+
+ input.value = '';
+ }
+ }
+
+ static editToken(token) {
+ const input = document.querySelector('.filtered-search');
+
+ FilteredSearchVisualTokens.tokenizeInput();
+
+ // Replace token with input field
+ const tokenContainer = token.parentElement;
+ const inputLi = input.parentElement;
+ tokenContainer.replaceChild(inputLi, token);
+
+ const name = token.querySelector('.name');
+ const value = token.querySelector('.value');
+
+ if (token.classList.contains('filtered-search-token')) {
+ FilteredSearchVisualTokens.addFilterVisualToken(name.innerText);
+ input.value = value.innerText;
+ } else {
+ // token is a search term
+ input.value = name.innerText;
+ }
+
+ // Opens dropdown
+ const inputEvent = new Event('input');
+ input.dispatchEvent(inputEvent);
+
+ // Adds cursor to input
+ input.focus();
+ }
+
+ static moveInputToTheRight() {
+ const input = document.querySelector('.filtered-search');
+ const inputLi = input.parentElement;
+ const tokenContainer = document.querySelector('.tokens-container');
+
+ FilteredSearchVisualTokens.tokenizeInput();
+
+ if (!tokenContainer.lastElementChild.isEqualNode(inputLi)) {
+ const { isLastVisualTokenValid } =
+ gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+
+ if (!isLastVisualTokenValid) {
+ const lastPartial = gl.FilteredSearchVisualTokens.getLastTokenPartial();
+ gl.FilteredSearchVisualTokens.removeLastTokenPartial();
+ gl.FilteredSearchVisualTokens.addSearchVisualToken(lastPartial);
+ }
+
+ tokenContainer.removeChild(inputLi);
+ tokenContainer.appendChild(inputLi);
+ }
+ }
+}
+
+window.gl = window.gl || {};
+gl.FilteredSearchVisualTokens = FilteredSearchVisualTokens;
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index 60d6658dc16..1bc04a5ad96 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -1,5 +1,11 @@
/* eslint-disable func-names, space-before-function-paren, no-template-curly-in-string, comma-dangle, object-shorthand, quotes, dot-notation, no-else-return, one-var, no-var, no-underscore-dangle, one-var-declaration-per-line, no-param-reassign, no-useless-escape, prefer-template, consistent-return, wrap-iife, prefer-arrow-callback, camelcase, no-unused-vars, no-useless-return, vars-on-top, max-len */
+const emojiMap = require('emoji-map');
+const emojiAliases = require('emoji-aliases');
+const glEmoji = require('./behaviors/gl_emoji');
+
+const glEmojiTag = glEmoji.glEmojiTag;
+
// Creates the variables for setting up GFM auto-completion
(function() {
if (window.gl == null) {
@@ -26,7 +32,12 @@
},
// Emoji
Emoji: {
- template: '<li>${name} <img alt="${name}" height="20" src="${path}" width="20" /></li>'
+ templateFunction: function(name) {
+ return `<li>
+ ${name} ${glEmojiTag(name)}
+ </li>
+ `;
+ }
},
// Team Members
Members: {
@@ -113,7 +124,7 @@
$input.atwho({
at: ':',
displayTpl: function(value) {
- return value.path != null ? this.Emoji.template : this.Loading.template;
+ return value && value.name ? this.Emoji.templateFunction(value.name) : this.Loading.template;
}.bind(this),
insertTpl: ':${name}:',
skipSpecialCharacterTest: true,
@@ -355,6 +366,8 @@
this.isLoadingData[at] = true;
if (this.cachedData[at]) {
this.loadData($input, at, this.cachedData[at]);
+ } else if (this.atTypeMap[at] === 'emojis') {
+ this.loadData($input, at, Object.keys(emojiMap).concat(Object.keys(emojiAliases)));
} else {
$.getJSON(this.dataSources[this.atTypeMap[at]], (data) => {
this.loadData($input, at, data);
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 6ac659fd290..ae4dd64424c 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -3,7 +3,6 @@
/* global Cookies */
/* global Flash */
/* global ConfirmDangerModal */
-/* global AwardsHandler */
/* global Aside */
import jQuery from 'jquery';
@@ -19,6 +18,15 @@ require('mousetrap/plugins/pause/mousetrap-pause');
require('vendor/fuzzaldrin-plus');
require('es6-promise').polyfill();
+// extensions
+require('./extensions/string');
+require('./extensions/array');
+require('./extensions/custom_event');
+require('./extensions/element');
+require('./extensions/jquery');
+require('./extensions/object');
+require('es6-promise').polyfill();
+
// expose common libraries as globals (TODO: remove these)
window.jQuery = jQuery;
window.$ = jQuery;
@@ -62,13 +70,6 @@ require('./templates/issuable_template_selectors');
require('./commit/file.js');
require('./commit/image_file.js');
-// extensions
-require('./extensions/array');
-require('./extensions/custom_event');
-require('./extensions/element');
-require('./extensions/jquery');
-require('./extensions/object');
-
// lib/utils
require('./lib/utils/animate');
require('./lib/utils/bootstrap_linked_tabs');
@@ -100,7 +101,7 @@ require('./ajax_loading_spinner');
require('./api');
require('./aside');
require('./autosave');
-require('./awards_handler');
+const AwardsHandler = require('./awards_handler');
require('./breakpoints');
require('./broadcast_message');
require('./build');
diff --git a/app/assets/javascripts/monitoring/prometheus_graph.js b/app/assets/javascripts/monitoring/prometheus_graph.js
new file mode 100644
index 00000000000..9384fe3f276
--- /dev/null
+++ b/app/assets/javascripts/monitoring/prometheus_graph.js
@@ -0,0 +1,333 @@
+/* eslint-disable no-new*/
+import d3 from 'd3';
+import _ from 'underscore';
+import statusCodes from '~/lib/utils/http_status';
+import '~/lib/utils/common_utils';
+import Flash from '~/flash';
+
+const prometheusGraphsContainer = '.prometheus-graph';
+const metricsEndpoint = 'metrics.json';
+const timeFormat = d3.time.format('%H:%M');
+const dayFormat = d3.time.format('%b %e, %a');
+const bisectDate = d3.bisector(d => d.time).left;
+const extraAddedWidthParent = 100;
+
+class PrometheusGraph {
+
+ constructor() {
+ this.margin = { top: 80, right: 180, bottom: 80, left: 100 };
+ this.marginLabelContainer = { top: 40, right: 0, bottom: 40, left: 0 };
+ const parentContainerWidth = $(prometheusGraphsContainer).parent().width() +
+ extraAddedWidthParent;
+ this.originalWidth = parentContainerWidth;
+ this.originalHeight = 400;
+ this.width = parentContainerWidth - this.margin.left - this.margin.right;
+ this.height = 400 - this.margin.top - this.margin.bottom;
+ this.backOffRequestCounter = 0;
+ this.configureGraph();
+ this.init();
+ }
+
+ createGraph() {
+ const self = this;
+ _.each(this.data, (value, key) => {
+ if (value.length > 0 && (key === 'cpu_values' || key === 'memory_values')) {
+ self.plotValues(value, key);
+ }
+ });
+ }
+
+ init() {
+ const self = this;
+ this.getData().then((metricsResponse) => {
+ if (metricsResponse === {}) {
+ new Flash('Empty metrics', 'alert');
+ } else {
+ self.transformData(metricsResponse);
+ self.createGraph();
+ }
+ });
+ }
+
+ plotValues(valuesToPlot, key) {
+ const x = d3.time.scale()
+ .range([0, this.width]);
+
+ const y = d3.scale.linear()
+ .range([this.height, 0]);
+
+ const prometheusGraphContainer = `${prometheusGraphsContainer}[graph-type=${key}]`;
+
+ const graphSpecifics = this.graphSpecificProperties[key];
+
+ const chart = d3.select(prometheusGraphContainer)
+ .attr('width', this.width + this.margin.left + this.margin.right)
+ .attr('height', this.height + this.margin.bottom + this.margin.top)
+ .append('g')
+ .attr('transform', `translate(${this.margin.left},${this.margin.top})`);
+
+ const axisLabelContainer = d3.select(prometheusGraphContainer)
+ .attr('width', this.originalWidth + this.marginLabelContainer.left + this.marginLabelContainer.right)
+ .attr('height', this.originalHeight + this.marginLabelContainer.bottom + this.marginLabelContainer.top)
+ .append('g')
+ .attr('transform', `translate(${this.marginLabelContainer.left},${this.marginLabelContainer.top})`);
+
+ x.domain(d3.extent(valuesToPlot, d => d.time));
+ y.domain([0, d3.max(valuesToPlot.map(metricValue => metricValue.value))]);
+
+ const xAxis = d3.svg.axis()
+ .scale(x)
+ .ticks(this.commonGraphProperties.axis_no_ticks)
+ .orient('bottom');
+
+ const yAxis = d3.svg.axis()
+ .scale(y)
+ .ticks(this.commonGraphProperties.axis_no_ticks)
+ .tickSize(-this.width)
+ .orient('left');
+
+ this.createAxisLabelContainers(axisLabelContainer, key);
+
+ chart.append('g')
+ .attr('class', 'x-axis')
+ .attr('transform', `translate(0,${this.height})`)
+ .call(xAxis);
+
+ chart.append('g')
+ .attr('class', 'y-axis')
+ .call(yAxis);
+
+ const area = d3.svg.area()
+ .x(d => x(d.time))
+ .y0(this.height)
+ .y1(d => y(d.value))
+ .interpolate('linear');
+
+ const line = d3.svg.line()
+ .x(d => x(d.time))
+ .y(d => y(d.value));
+
+ chart.append('path')
+ .datum(valuesToPlot)
+ .attr('d', area)
+ .attr('class', 'metric-area')
+ .attr('fill', graphSpecifics.area_fill_color);
+
+ chart.append('path')
+ .datum(valuesToPlot)
+ .attr('class', 'metric-line')
+ .attr('stroke', graphSpecifics.line_color)
+ .attr('fill', 'none')
+ .attr('stroke-width', this.commonGraphProperties.area_stroke_width)
+ .attr('d', line);
+
+ // Overlay area for the mouseover events
+ chart.append('rect')
+ .attr('class', 'prometheus-graph-overlay')
+ .attr('width', this.width)
+ .attr('height', this.height)
+ .on('mousemove', this.handleMouseOverGraph.bind(this, x, y, valuesToPlot, chart, prometheusGraphContainer, key));
+ }
+
+ // The legends from the metric
+ createAxisLabelContainers(axisLabelContainer, key) {
+ const graphSpecifics = this.graphSpecificProperties[key];
+
+ axisLabelContainer.append('line')
+ .attr('class', 'label-x-axis-line')
+ .attr('stroke', '#000000')
+ .attr('stroke-width', '1')
+ .attr({
+ x1: 0,
+ y1: this.originalHeight - this.marginLabelContainer.top,
+ x2: this.originalWidth - this.margin.right,
+ y2: this.originalHeight - this.marginLabelContainer.top,
+ });
+
+ axisLabelContainer.append('line')
+ .attr('class', 'label-y-axis-line')
+ .attr('stroke', '#000000')
+ .attr('stroke-width', '1')
+ .attr({
+ x1: 0,
+ y1: 0,
+ x2: 0,
+ y2: this.originalHeight - this.marginLabelContainer.top,
+ });
+
+ axisLabelContainer.append('text')
+ .attr('class', 'label-axis-text')
+ .attr('text-anchor', 'middle')
+ .attr('transform', `translate(15, ${(this.originalHeight - this.marginLabelContainer.top) / 2}) rotate(-90)`)
+ .text(graphSpecifics.graph_legend_title);
+
+ axisLabelContainer.append('rect')
+ .attr('class', 'rect-axis-text')
+ .attr('x', (this.originalWidth / 2) - this.margin.right)
+ .attr('y', this.originalHeight - this.marginLabelContainer.top - 20)
+ .attr('width', 30)
+ .attr('height', 80);
+
+ axisLabelContainer.append('text')
+ .attr('class', 'label-axis-text')
+ .attr('x', (this.originalWidth / 2) - this.margin.right)
+ .attr('y', this.originalHeight - this.marginLabelContainer.top)
+ .attr('dy', '.35em')
+ .text('Time');
+
+ // Legends
+
+ // Metric Usage
+ axisLabelContainer.append('rect')
+ .attr('x', this.originalWidth - 170)
+ .attr('y', (this.originalHeight / 2) - 80)
+ .style('fill', graphSpecifics.area_fill_color)
+ .attr('width', 20)
+ .attr('height', 35);
+
+ axisLabelContainer.append('text')
+ .attr('class', 'label-axis-text')
+ .attr('x', this.originalWidth - 140)
+ .attr('y', (this.originalHeight / 2) - 65)
+ .text(graphSpecifics.graph_legend_title);
+
+ axisLabelContainer.append('text')
+ .attr('class', 'text-metric-usage')
+ .attr('x', this.originalWidth - 140)
+ .attr('y', (this.originalHeight / 2) - 50);
+ }
+
+ handleMouseOverGraph(x, y, valuesToPlot, chart, prometheusGraphContainer, key) {
+ const rectOverlay = document.querySelector(`${prometheusGraphContainer} .prometheus-graph-overlay`);
+ const timeValueFromOverlay = x.invert(d3.mouse(rectOverlay)[0]);
+ const timeValueIndex = bisectDate(valuesToPlot, timeValueFromOverlay, 1);
+ const d0 = valuesToPlot[timeValueIndex - 1];
+ const d1 = valuesToPlot[timeValueIndex];
+ const currentData = timeValueFromOverlay - d0.time > d1.time - timeValueFromOverlay ? d1 : d0;
+ const maxValueMetric = y(d3.max(valuesToPlot.map(metricValue => metricValue.value)));
+ const currentTimeCoordinate = x(currentData.time);
+ const graphSpecifics = this.graphSpecificProperties[key];
+ // Remove the current selectors
+ d3.selectAll(`${prometheusGraphContainer} .selected-metric-line`).remove();
+ d3.selectAll(`${prometheusGraphContainer} .circle-metric`).remove();
+ d3.selectAll(`${prometheusGraphContainer} .rect-text-metric`).remove();
+ d3.selectAll(`${prometheusGraphContainer} .text-metric`).remove();
+
+ chart.append('line')
+ .attr('class', 'selected-metric-line')
+ .attr({
+ x1: currentTimeCoordinate,
+ y1: y(0),
+ x2: currentTimeCoordinate,
+ y2: maxValueMetric,
+ });
+
+ chart.append('circle')
+ .attr('class', 'circle-metric')
+ .attr('fill', graphSpecifics.line_color)
+ .attr('cx', currentTimeCoordinate)
+ .attr('cy', y(currentData.value))
+ .attr('r', this.commonGraphProperties.circle_radius_metric);
+
+ // The little box with text
+ const rectTextMetric = chart.append('g')
+ .attr('class', 'rect-text-metric')
+ .attr('translate', `(${currentTimeCoordinate}, ${y(currentData.value)})`);
+
+ rectTextMetric.append('rect')
+ .attr('class', 'rect-metric')
+ .attr('x', currentTimeCoordinate + 10)
+ .attr('y', maxValueMetric)
+ .attr('width', this.commonGraphProperties.rect_text_width)
+ .attr('height', this.commonGraphProperties.rect_text_height);
+
+ rectTextMetric.append('text')
+ .attr('class', 'text-metric')
+ .attr('x', currentTimeCoordinate + 35)
+ .attr('y', maxValueMetric + 35)
+ .text(timeFormat(currentData.time));
+
+ rectTextMetric.append('text')
+ .attr('class', 'text-metric-date')
+ .attr('x', currentTimeCoordinate + 15)
+ .attr('y', maxValueMetric + 15)
+ .text(dayFormat(currentData.time));
+
+ // Update the text
+ d3.select(`${prometheusGraphContainer} .text-metric-usage`)
+ .text(currentData.value.substring(0, 8));
+ }
+
+ configureGraph() {
+ this.graphSpecificProperties = {
+ cpu_values: {
+ area_fill_color: '#edf3fc',
+ line_color: '#5b99f7',
+ graph_legend_title: 'CPU Usage (Cores)',
+ },
+ memory_values: {
+ area_fill_color: '#fca326',
+ line_color: '#fc6d26',
+ graph_legend_title: 'Memory Usage (MB)',
+ },
+ };
+
+ this.commonGraphProperties = {
+ area_stroke_width: 2,
+ median_total_characters: 8,
+ circle_radius_metric: 5,
+ rect_text_width: 90,
+ rect_text_height: 40,
+ axis_no_ticks: 3,
+ };
+ }
+
+ getData() {
+ const maxNumberOfRequests = 3;
+ return gl.utils.backOff((next, stop) => {
+ $.ajax({
+ url: metricsEndpoint,
+ dataType: 'json',
+ })
+ .done((data, statusText, resp) => {
+ if (resp.status === statusCodes.NO_CONTENT) {
+ this.backOffRequestCounter = this.backOffRequestCounter += 1;
+ if (this.backOffRequestCounter < maxNumberOfRequests) {
+ next();
+ } else {
+ stop({
+ status: resp.status,
+ metrics: data,
+ });
+ }
+ } else {
+ stop({
+ status: resp.status,
+ metrics: data,
+ });
+ }
+ }).fail(stop);
+ })
+ .then((resp) => {
+ if (resp.status === statusCodes.NO_CONTENT) {
+ return {};
+ }
+ return resp.metrics;
+ })
+ .catch(() => new Flash('An error occurred while fetching metrics.', 'alert'));
+ }
+
+ transformData(metricsResponse) {
+ const metricTypes = {};
+ _.each(metricsResponse.metrics, (value, key) => {
+ const metricValues = value[0].values;
+ metricTypes[key] = _.map(metricValues, metric => ({
+ time: new Date(metric[0] * 1000),
+ value: metric[1],
+ }));
+ });
+ this.data = metricTypes;
+ }
+}
+
+export default PrometheusGraph;
diff --git a/app/assets/javascripts/shortcuts_navigation.js b/app/assets/javascripts/shortcuts_navigation.js
index 73db8c10b99..09a58cad2b2 100644
--- a/app/assets/javascripts/shortcuts_navigation.js
+++ b/app/assets/javascripts/shortcuts_navigation.js
@@ -16,6 +16,9 @@ require('./shortcuts');
Mousetrap.bind('g p', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-project');
});
+ Mousetrap.bind('g e', function() {
+ return ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity');
+ });
Mousetrap.bind('g f', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-tree');
});
@@ -28,6 +31,9 @@ require('./shortcuts');
Mousetrap.bind('g n', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-network');
});
+ Mousetrap.bind('g g', function() {
+ return ShortcutsNavigation.findAndFollowLink('.shortcuts-repository-charts');
+ });
Mousetrap.bind('g i', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-issues');
});
diff --git a/app/assets/javascripts/test_utils/simulate_drag.js b/app/assets/javascripts/test_utils/simulate_drag.js
index 7dba5840c8a..d48f2404fa5 100644
--- a/app/assets/javascripts/test_utils/simulate_drag.js
+++ b/app/assets/javascripts/test_utils/simulate_drag.js
@@ -43,7 +43,14 @@
return event;
}
- function getTraget(target) {
+ function isLast(target) {
+ var el = typeof target.el === 'string' ? document.getElementById(target.el.substr(1)) : target.el;
+ var children = el.children;
+
+ return children.length - 1 === target.index;
+ }
+
+ function getTarget(target) {
var el = typeof target.el === 'string' ? document.getElementById(target.el.substr(1)) : target.el;
var children = el.children;
@@ -75,12 +82,22 @@
function simulateDrag(options, callback) {
options.to.el = options.to.el || options.from.el;
- var fromEl = getTraget(options.from);
- var toEl = getTraget(options.to);
+ var fromEl = getTarget(options.from);
+ var toEl = getTarget(options.to);
+ var firstEl = getTarget({
+ el: options.to.el,
+ index: 'first'
+ });
+ var lastEl = getTarget({
+ el: options.to.el,
+ index: 'last'
+ });
var scrollable = options.scrollable;
var fromRect = getRect(fromEl);
var toRect = getRect(toEl);
+ var firstRect = getRect(firstEl);
+ var lastRect = getRect(lastEl);
var startTime = new Date().getTime();
var duration = options.duration || 1000;
@@ -88,6 +105,12 @@
options.ontap && options.ontap();
window.SIMULATE_DRAG_ACTIVE = 1;
+ if (options.to.index === 0) {
+ toRect.cy = firstRect.y;
+ } else if (isLast(options.to)) {
+ toRect.cy = lastRect.y + lastRect.h + 50;
+ }
+
var dragInterval = setInterval(function loop() {
var progress = (new Date().getTime() - startTime) / duration;
var x = (fromRect.cx + (toRect.cx - fromRect.cx) * progress) - scrollable.scrollLeft;
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index de33a31b411..27af859f7d8 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -60,6 +60,15 @@
});
};
+ $('.assign-to-me-link').on('click', (e) => {
+ e.preventDefault();
+ $(e.currentTarget).hide();
+ const $input = $(`input[name="${$dropdown.data('field-name')}"]`);
+ $input.val(gon.current_user_id);
+ selectedId = $input.val();
+ $dropdown.find('.dropdown-toggle-text').text(gon.current_user_fullname).removeClass('is-default');
+ });
+
$block.on('click', '.js-assign-yourself', function(e) {
e.preventDefault();
@@ -199,6 +208,11 @@
if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) {
e.preventDefault();
selectedId = user.id;
+ if (selectedId === gon.current_user_id) {
+ $('.assign-to-me-link').hide();
+ } else {
+ $('.assign-to-me-link').show();
+ }
return;
}
if ($el.closest('.add-issues-modal').length) {
@@ -234,11 +248,16 @@
id: function (user) {
return user.id;
},
+ opened: function(e) {
+ const $el = $(e.currentTarget);
+ $el.find('.is-active').removeClass('is-active');
+ $el.find(`li[data-user-id="${selectedId}"] .dropdown-menu-user-link`).addClass('is-active');
+ },
renderRow: function(user) {
var avatar, img, listClosingTags, listWithName, listWithUserName, selected, username;
username = user.username ? "@" + user.username : "";
avatar = user.avatar_url ? user.avatar_url : false;
- selected = user.id === selectedId ? "is-active" : "";
+ selected = user.id === parseInt(selectedId, 10) ? "is-active" : "";
img = "";
if (user.beforeDivider != null) {
"<li> <a href='#' class='" + selected + "'> " + user.name + " </a> </li>";
@@ -248,7 +267,7 @@
}
}
// split into three parts so we can remove the username section if nessesary
- listWithName = "<li> <a href='#' class='dropdown-menu-user-link " + selected + "'> " + img + " <strong class='dropdown-menu-user-full-name'> " + user.name + " </strong>";
+ listWithName = "<li data-user-id=" + user.id + "> <a href='#' class='dropdown-menu-user-link " + selected + "'> " + img + " <strong class='dropdown-menu-user-full-name'> " + user.name + " </strong>";
listWithUserName = "<span class='dropdown-menu-user-username'> " + username + " </span>";
listClosingTags = "</a> </li>";
if (username === '') {
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 39cf3b5f8ae..5bb7e8caec1 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -44,5 +44,6 @@
@import "framework/images.scss";
@import "framework/broadcast-messages";
@import "framework/emojis.scss";
+@import "framework/emoji-sprites.scss";
@import "framework/icons.scss";
@import "framework/snippets.scss";
diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss
index 49907417e26..f363affa46c 100644
--- a/app/assets/stylesheets/framework/awards.scss
+++ b/app/assets/stylesheets/framework/awards.scss
@@ -7,6 +7,7 @@
.emoji-menu {
position: absolute;
+ top: 0;
margin-top: 3px;
padding: $gl-padding;
z-index: 9;
@@ -20,7 +21,7 @@
opacity: 0;
transform: scale(.2);
transform-origin: 0 -45px;
- transition: .3s cubic-bezier(.87,-.41,.19,1.44);
+ transition: .3s cubic-bezier(.67,.06,.19,1.44);
transition-property: transform, opacity;
&.is-aligned-right {
@@ -47,12 +48,13 @@
}
.emoji-menu-list {
- list-style: none;
- padding-left: 0;
margin-bottom: 0;
+ padding-left: 0;
+ list-style: none;
}
.emoji-menu-list-item {
+ float: left;
padding: 3px;
margin-left: 1px;
margin-right: 1px;
@@ -97,6 +99,8 @@
padding: 5px 6px;
outline: 0;
+ line-height: 1;
+
&.disabled {
cursor: default;
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 2a403589a53..887ab481de4 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -221,6 +221,7 @@
color: $gl-text-color-secondary;
font-size: 13px;
line-height: 22px;
+ text-transform: capitalize;
padding: 0 10px;
}
diff --git a/app/assets/stylesheets/framework/emoji-sprites.scss b/app/assets/stylesheets/framework/emoji-sprites.scss
new file mode 100644
index 00000000000..925415f84b1
--- /dev/null
+++ b/app/assets/stylesheets/framework/emoji-sprites.scss
@@ -0,0 +1,1811 @@
+.emoji-zzz { background-position: 0 0; }
+.emoji-1234 { background-position: -20px 0; }
+.emoji-1F627 { background-position: 0 -20px; }
+.emoji-8ball { background-position: -20px -20px; }
+.emoji-a { background-position: -40px 0; }
+.emoji-ab { background-position: -40px -20px; }
+.emoji-abc { background-position: 0 -40px; }
+.emoji-abcd { background-position: -20px -40px; }
+.emoji-accept { background-position: -40px -40px; }
+.emoji-aerial_tramway { background-position: -60px 0; }
+.emoji-airplane { background-position: -60px -20px; }
+.emoji-airplane_arriving { background-position: -60px -40px; }
+.emoji-airplane_departure { background-position: 0 -60px; }
+.emoji-airplane_small { background-position: -20px -60px; }
+.emoji-alarm_clock { background-position: -40px -60px; }
+.emoji-alembic { background-position: -60px -60px; }
+.emoji-alien { background-position: -80px 0; }
+.emoji-ambulance { background-position: -80px -20px; }
+.emoji-amphora { background-position: -80px -40px; }
+.emoji-anchor { background-position: -80px -60px; }
+.emoji-angel { background-position: 0 -80px; }
+.emoji-angel_tone1 { background-position: -20px -80px; }
+.emoji-angel_tone2 { background-position: -40px -80px; }
+.emoji-angel_tone3 { background-position: -60px -80px; }
+.emoji-angel_tone4 { background-position: -80px -80px; }
+.emoji-angel_tone5 { background-position: -100px 0; }
+.emoji-anger { background-position: -100px -20px; }
+.emoji-anger_right { background-position: -100px -40px; }
+.emoji-angry { background-position: -100px -60px; }
+.emoji-ant { background-position: -100px -80px; }
+.emoji-apple { background-position: 0 -100px; }
+.emoji-aquarius { background-position: -20px -100px; }
+.emoji-aries { background-position: -40px -100px; }
+.emoji-arrow_backward { background-position: -60px -100px; }
+.emoji-arrow_double_down { background-position: -80px -100px; }
+.emoji-arrow_double_up { background-position: -100px -100px; }
+.emoji-arrow_down { background-position: -120px 0; }
+.emoji-arrow_down_small { background-position: -120px -20px; }
+.emoji-arrow_forward { background-position: -120px -40px; }
+.emoji-arrow_heading_down { background-position: -120px -60px; }
+.emoji-arrow_heading_up { background-position: -120px -80px; }
+.emoji-arrow_left { background-position: -120px -100px; }
+.emoji-arrow_lower_left { background-position: 0 -120px; }
+.emoji-arrow_lower_right { background-position: -20px -120px; }
+.emoji-arrow_right { background-position: -40px -120px; }
+.emoji-arrow_right_hook { background-position: -60px -120px; }
+.emoji-arrow_up { background-position: -80px -120px; }
+.emoji-arrow_up_down { background-position: -100px -120px; }
+.emoji-arrow_up_small { background-position: -120px -120px; }
+.emoji-arrow_upper_left { background-position: -140px 0; }
+.emoji-arrow_upper_right { background-position: -140px -20px; }
+.emoji-arrows_clockwise { background-position: -140px -40px; }
+.emoji-arrows_counterclockwise { background-position: -140px -60px; }
+.emoji-art { background-position: -140px -80px; }
+.emoji-articulated_lorry { background-position: -140px -100px; }
+.emoji-asterisk { background-position: -140px -120px; }
+.emoji-astonished { background-position: 0 -140px; }
+.emoji-athletic_shoe { background-position: -20px -140px; }
+.emoji-atm { background-position: -40px -140px; }
+.emoji-atom { background-position: -60px -140px; }
+.emoji-avocado { background-position: -80px -140px; }
+.emoji-b { background-position: -100px -140px; }
+.emoji-baby { background-position: -120px -140px; }
+.emoji-baby_bottle { background-position: -140px -140px; }
+.emoji-baby_chick { background-position: -160px 0; }
+.emoji-baby_symbol { background-position: -160px -20px; }
+.emoji-baby_tone1 { background-position: -160px -40px; }
+.emoji-baby_tone2 { background-position: -160px -60px; }
+.emoji-baby_tone3 { background-position: -160px -80px; }
+.emoji-baby_tone4 { background-position: -160px -100px; }
+.emoji-baby_tone5 { background-position: -160px -120px; }
+.emoji-back { background-position: -160px -140px; }
+.emoji-bacon { background-position: 0 -160px; }
+.emoji-badminton { background-position: -20px -160px; }
+.emoji-baggage_claim { background-position: -40px -160px; }
+.emoji-balloon { background-position: -60px -160px; }
+.emoji-ballot_box { background-position: -80px -160px; }
+.emoji-ballot_box_with_check { background-position: -100px -160px; }
+.emoji-bamboo { background-position: -120px -160px; }
+.emoji-banana { background-position: -140px -160px; }
+.emoji-bangbang { background-position: -160px -160px; }
+.emoji-bank { background-position: -180px 0; }
+.emoji-bar_chart { background-position: -180px -20px; }
+.emoji-barber { background-position: -180px -40px; }
+.emoji-baseball { background-position: -180px -60px; }
+.emoji-basketball { background-position: -180px -80px; }
+.emoji-basketball_player { background-position: -180px -100px; }
+.emoji-basketball_player_tone1 { background-position: -180px -120px; }
+.emoji-basketball_player_tone2 { background-position: -180px -140px; }
+.emoji-basketball_player_tone3 { background-position: -180px -160px; }
+.emoji-basketball_player_tone4 { background-position: 0 -180px; }
+.emoji-basketball_player_tone5 { background-position: -20px -180px; }
+.emoji-bat { background-position: -40px -180px; }
+.emoji-bath { background-position: -60px -180px; }
+.emoji-bath_tone1 { background-position: -80px -180px; }
+.emoji-bath_tone2 { background-position: -100px -180px; }
+.emoji-bath_tone3 { background-position: -120px -180px; }
+.emoji-bath_tone4 { background-position: -140px -180px; }
+.emoji-bath_tone5 { background-position: -160px -180px; }
+.emoji-bathtub { background-position: -180px -180px; }
+.emoji-battery { background-position: -200px 0; }
+.emoji-beach { background-position: -200px -20px; }
+.emoji-beach_umbrella { background-position: -200px -40px; }
+.emoji-bear { background-position: -200px -60px; }
+.emoji-bed { background-position: -200px -80px; }
+.emoji-bee { background-position: -200px -100px; }
+.emoji-beer { background-position: -200px -120px; }
+.emoji-beers { background-position: -200px -140px; }
+.emoji-beetle { background-position: -200px -160px; }
+.emoji-beginner { background-position: -200px -180px; }
+.emoji-bell { background-position: 0 -200px; }
+.emoji-bellhop { background-position: -20px -200px; }
+.emoji-bento { background-position: -40px -200px; }
+.emoji-bicyclist { background-position: -60px -200px; }
+.emoji-bicyclist_tone1 { background-position: -80px -200px; }
+.emoji-bicyclist_tone2 { background-position: -100px -200px; }
+.emoji-bicyclist_tone3 { background-position: -120px -200px; }
+.emoji-bicyclist_tone4 { background-position: -140px -200px; }
+.emoji-bicyclist_tone5 { background-position: -160px -200px; }
+.emoji-bike { background-position: -180px -200px; }
+.emoji-bikini { background-position: -200px -200px; }
+.emoji-biohazard { background-position: -220px 0; }
+.emoji-bird { background-position: -220px -20px; }
+.emoji-birthday { background-position: -220px -40px; }
+.emoji-black_circle { background-position: -220px -60px; }
+.emoji-black_heart { background-position: -220px -80px; }
+.emoji-black_joker { background-position: -220px -100px; }
+.emoji-black_large_square { background-position: -220px -120px; }
+.emoji-black_medium_small_square { background-position: -220px -140px; }
+.emoji-black_medium_square { background-position: -220px -160px; }
+.emoji-black_nib { background-position: -220px -180px; }
+.emoji-black_small_square { background-position: -220px -200px; }
+.emoji-black_square_button { background-position: 0 -220px; }
+.emoji-blossom { background-position: -20px -220px; }
+.emoji-blowfish { background-position: -40px -220px; }
+.emoji-blue_book { background-position: -60px -220px; }
+.emoji-blue_car { background-position: -80px -220px; }
+.emoji-blue_heart { background-position: -100px -220px; }
+.emoji-blush { background-position: -120px -220px; }
+.emoji-boar { background-position: -140px -220px; }
+.emoji-bomb { background-position: -160px -220px; }
+.emoji-book { background-position: -180px -220px; }
+.emoji-bookmark { background-position: -200px -220px; }
+.emoji-bookmark_tabs { background-position: -220px -220px; }
+.emoji-books { background-position: -240px 0; }
+.emoji-boom { background-position: -240px -20px; }
+.emoji-boot { background-position: -240px -40px; }
+.emoji-bouquet { background-position: -240px -60px; }
+.emoji-bow { background-position: -240px -80px; }
+.emoji-bow_and_arrow { background-position: -240px -100px; }
+.emoji-bow_tone1 { background-position: -240px -120px; }
+.emoji-bow_tone2 { background-position: -240px -140px; }
+.emoji-bow_tone3 { background-position: -240px -160px; }
+.emoji-bow_tone4 { background-position: -240px -180px; }
+.emoji-bow_tone5 { background-position: -240px -200px; }
+.emoji-bowling { background-position: -240px -220px; }
+.emoji-boxing_glove { background-position: 0 -240px; }
+.emoji-boy { background-position: -20px -240px; }
+.emoji-boy_tone1 { background-position: -40px -240px; }
+.emoji-boy_tone2 { background-position: -60px -240px; }
+.emoji-boy_tone3 { background-position: -80px -240px; }
+.emoji-boy_tone4 { background-position: -100px -240px; }
+.emoji-boy_tone5 { background-position: -120px -240px; }
+.emoji-bread { background-position: -140px -240px; }
+.emoji-bride_with_veil { background-position: -160px -240px; }
+.emoji-bride_with_veil_tone1 { background-position: -180px -240px; }
+.emoji-bride_with_veil_tone2 { background-position: -200px -240px; }
+.emoji-bride_with_veil_tone3 { background-position: -220px -240px; }
+.emoji-bride_with_veil_tone4 { background-position: -240px -240px; }
+.emoji-bride_with_veil_tone5 { background-position: -260px 0; }
+.emoji-bridge_at_night { background-position: -260px -20px; }
+.emoji-briefcase { background-position: -260px -40px; }
+.emoji-broken_heart { background-position: -260px -60px; }
+.emoji-bug { background-position: -260px -80px; }
+.emoji-bulb { background-position: -260px -100px; }
+.emoji-bullettrain_front { background-position: -260px -120px; }
+.emoji-bullettrain_side { background-position: -260px -140px; }
+.emoji-burrito { background-position: -260px -160px; }
+.emoji-bus { background-position: -260px -180px; }
+.emoji-busstop { background-position: -260px -200px; }
+.emoji-bust_in_silhouette { background-position: -260px -220px; }
+.emoji-busts_in_silhouette { background-position: -260px -240px; }
+.emoji-butterfly { background-position: 0 -260px; }
+.emoji-cactus { background-position: -20px -260px; }
+.emoji-cake { background-position: -40px -260px; }
+.emoji-calendar { background-position: -60px -260px; }
+.emoji-calendar_spiral { background-position: -80px -260px; }
+.emoji-call_me { background-position: -100px -260px; }
+.emoji-call_me_tone1 { background-position: -120px -260px; }
+.emoji-call_me_tone2 { background-position: -140px -260px; }
+.emoji-call_me_tone3 { background-position: -160px -260px; }
+.emoji-call_me_tone4 { background-position: -180px -260px; }
+.emoji-call_me_tone5 { background-position: -200px -260px; }
+.emoji-calling { background-position: -220px -260px; }
+.emoji-camel { background-position: -240px -260px; }
+.emoji-camera { background-position: -260px -260px; }
+.emoji-camera_with_flash { background-position: -280px 0; }
+.emoji-camping { background-position: -280px -20px; }
+.emoji-cancer { background-position: -280px -40px; }
+.emoji-candle { background-position: -280px -60px; }
+.emoji-candy { background-position: -280px -80px; }
+.emoji-canoe { background-position: -280px -100px; }
+.emoji-capital_abcd { background-position: -280px -120px; }
+.emoji-capricorn { background-position: -280px -140px; }
+.emoji-card_box { background-position: -280px -160px; }
+.emoji-card_index { background-position: -280px -180px; }
+.emoji-carousel_horse { background-position: -280px -200px; }
+.emoji-carrot { background-position: -280px -220px; }
+.emoji-cartwheel { background-position: -280px -240px; }
+.emoji-cartwheel_tone1 { background-position: -280px -260px; }
+.emoji-cartwheel_tone2 { background-position: 0 -280px; }
+.emoji-cartwheel_tone3 { background-position: -20px -280px; }
+.emoji-cartwheel_tone4 { background-position: -40px -280px; }
+.emoji-cartwheel_tone5 { background-position: -60px -280px; }
+.emoji-cat { background-position: -80px -280px; }
+.emoji-cat2 { background-position: -100px -280px; }
+.emoji-cd { background-position: -120px -280px; }
+.emoji-chains { background-position: -140px -280px; }
+.emoji-champagne { background-position: -160px -280px; }
+.emoji-champagne_glass { background-position: -180px -280px; }
+.emoji-chart { background-position: -200px -280px; }
+.emoji-chart_with_downwards_trend { background-position: -220px -280px; }
+.emoji-chart_with_upwards_trend { background-position: -240px -280px; }
+.emoji-checkered_flag { background-position: -260px -280px; }
+.emoji-cheese { background-position: -280px -280px; }
+.emoji-cherries { background-position: -300px 0; }
+.emoji-cherry_blossom { background-position: -300px -20px; }
+.emoji-chestnut { background-position: -300px -40px; }
+.emoji-chicken { background-position: -300px -60px; }
+.emoji-children_crossing { background-position: -300px -80px; }
+.emoji-chipmunk { background-position: -300px -100px; }
+.emoji-chocolate_bar { background-position: -300px -120px; }
+.emoji-christmas_tree { background-position: -300px -140px; }
+.emoji-church { background-position: -300px -160px; }
+.emoji-cinema { background-position: -300px -180px; }
+.emoji-circus_tent { background-position: -300px -200px; }
+.emoji-city_dusk { background-position: -300px -220px; }
+.emoji-city_sunset { background-position: -300px -240px; }
+.emoji-cityscape { background-position: -300px -260px; }
+.emoji-cl { background-position: -300px -280px; }
+.emoji-clap { background-position: 0 -300px; }
+.emoji-clap_tone1 { background-position: -20px -300px; }
+.emoji-clap_tone2 { background-position: -40px -300px; }
+.emoji-clap_tone3 { background-position: -60px -300px; }
+.emoji-clap_tone4 { background-position: -80px -300px; }
+.emoji-clap_tone5 { background-position: -100px -300px; }
+.emoji-clapper { background-position: -120px -300px; }
+.emoji-classical_building { background-position: -140px -300px; }
+.emoji-clipboard { background-position: -160px -300px; }
+.emoji-clock { background-position: -180px -300px; }
+.emoji-clock1 { background-position: -200px -300px; }
+.emoji-clock10 { background-position: -220px -300px; }
+.emoji-clock1030 { background-position: -240px -300px; }
+.emoji-clock11 { background-position: -260px -300px; }
+.emoji-clock1130 { background-position: -280px -300px; }
+.emoji-clock12 { background-position: -300px -300px; }
+.emoji-clock1230 { background-position: -320px 0; }
+.emoji-clock130 { background-position: -320px -20px; }
+.emoji-clock2 { background-position: -320px -40px; }
+.emoji-clock230 { background-position: -320px -60px; }
+.emoji-clock3 { background-position: -320px -80px; }
+.emoji-clock330 { background-position: -320px -100px; }
+.emoji-clock4 { background-position: -320px -120px; }
+.emoji-clock430 { background-position: -320px -140px; }
+.emoji-clock5 { background-position: -320px -160px; }
+.emoji-clock530 { background-position: -320px -180px; }
+.emoji-clock6 { background-position: -320px -200px; }
+.emoji-clock630 { background-position: -320px -220px; }
+.emoji-clock7 { background-position: -320px -240px; }
+.emoji-clock730 { background-position: -320px -260px; }
+.emoji-clock8 { background-position: -320px -280px; }
+.emoji-clock830 { background-position: -320px -300px; }
+.emoji-clock9 { background-position: 0 -320px; }
+.emoji-clock930 { background-position: -20px -320px; }
+.emoji-closed_book { background-position: -40px -320px; }
+.emoji-closed_lock_with_key { background-position: -60px -320px; }
+.emoji-closed_umbrella { background-position: -80px -320px; }
+.emoji-cloud { background-position: -100px -320px; }
+.emoji-cloud_lightning { background-position: -120px -320px; }
+.emoji-cloud_rain { background-position: -140px -320px; }
+.emoji-cloud_snow { background-position: -160px -320px; }
+.emoji-cloud_tornado { background-position: -180px -320px; }
+.emoji-clown { background-position: -200px -320px; }
+.emoji-clubs { background-position: -220px -320px; }
+.emoji-cocktail { background-position: -240px -320px; }
+.emoji-coffee { background-position: -260px -320px; }
+.emoji-coffin { background-position: -280px -320px; }
+.emoji-cold_sweat { background-position: -300px -320px; }
+.emoji-comet { background-position: -320px -320px; }
+.emoji-compression { background-position: -340px 0; }
+.emoji-computer { background-position: -340px -20px; }
+.emoji-confetti_ball { background-position: -340px -40px; }
+.emoji-confounded { background-position: -340px -60px; }
+.emoji-confused { background-position: -340px -80px; }
+.emoji-congratulations { background-position: -340px -100px; }
+.emoji-construction { background-position: -340px -120px; }
+.emoji-construction_site { background-position: -340px -140px; }
+.emoji-construction_worker { background-position: -340px -160px; }
+.emoji-construction_worker_tone1 { background-position: -340px -180px; }
+.emoji-construction_worker_tone2 { background-position: -340px -200px; }
+.emoji-construction_worker_tone3 { background-position: -340px -220px; }
+.emoji-construction_worker_tone4 { background-position: -340px -240px; }
+.emoji-construction_worker_tone5 { background-position: -340px -260px; }
+.emoji-control_knobs { background-position: -340px -280px; }
+.emoji-convenience_store { background-position: -340px -300px; }
+.emoji-cookie { background-position: -340px -320px; }
+.emoji-cooking { background-position: 0 -340px; }
+.emoji-cool { background-position: -20px -340px; }
+.emoji-cop { background-position: -40px -340px; }
+.emoji-cop_tone1 { background-position: -60px -340px; }
+.emoji-cop_tone2 { background-position: -80px -340px; }
+.emoji-cop_tone3 { background-position: -100px -340px; }
+.emoji-cop_tone4 { background-position: -120px -340px; }
+.emoji-cop_tone5 { background-position: -140px -340px; }
+.emoji-copyright { background-position: -160px -340px; }
+.emoji-corn { background-position: -180px -340px; }
+.emoji-couch { background-position: -200px -340px; }
+.emoji-couple { background-position: -220px -340px; }
+.emoji-couple_mm { background-position: -240px -340px; }
+.emoji-couple_with_heart { background-position: -260px -340px; }
+.emoji-couple_ww { background-position: -280px -340px; }
+.emoji-couplekiss { background-position: -300px -340px; }
+.emoji-cow { background-position: -320px -340px; }
+.emoji-cow2 { background-position: -340px -340px; }
+.emoji-cowboy { background-position: -360px 0; }
+.emoji-crab { background-position: -360px -20px; }
+.emoji-crayon { background-position: -360px -40px; }
+.emoji-credit_card { background-position: -360px -60px; }
+.emoji-crescent_moon { background-position: -360px -80px; }
+.emoji-cricket { background-position: -360px -100px; }
+.emoji-crocodile { background-position: -360px -120px; }
+.emoji-croissant { background-position: -360px -140px; }
+.emoji-cross { background-position: -360px -160px; }
+.emoji-crossed_flags { background-position: -360px -180px; }
+.emoji-crossed_swords { background-position: -360px -200px; }
+.emoji-crown { background-position: -360px -220px; }
+.emoji-cruise_ship { background-position: -360px -240px; }
+.emoji-cry { background-position: -360px -260px; }
+.emoji-crying_cat_face { background-position: -360px -280px; }
+.emoji-crystal_ball { background-position: -360px -300px; }
+.emoji-cucumber { background-position: -360px -320px; }
+.emoji-cupid { background-position: -360px -340px; }
+.emoji-curly_loop { background-position: 0 -360px; }
+.emoji-currency_exchange { background-position: -20px -360px; }
+.emoji-curry { background-position: -40px -360px; }
+.emoji-custard { background-position: -60px -360px; }
+.emoji-customs { background-position: -80px -360px; }
+.emoji-cyclone { background-position: -100px -360px; }
+.emoji-dagger { background-position: -120px -360px; }
+.emoji-dancer { background-position: -140px -360px; }
+.emoji-dancer_tone1 { background-position: -160px -360px; }
+.emoji-dancer_tone2 { background-position: -180px -360px; }
+.emoji-dancer_tone3 { background-position: -200px -360px; }
+.emoji-dancer_tone4 { background-position: -220px -360px; }
+.emoji-dancer_tone5 { background-position: -240px -360px; }
+.emoji-dancers { background-position: -260px -360px; }
+.emoji-dango { background-position: -280px -360px; }
+.emoji-dark_sunglasses { background-position: -300px -360px; }
+.emoji-dart { background-position: -320px -360px; }
+.emoji-dash { background-position: -340px -360px; }
+.emoji-date { background-position: -360px -360px; }
+.emoji-deciduous_tree { background-position: -380px 0; }
+.emoji-deer { background-position: -380px -20px; }
+.emoji-department_store { background-position: -380px -40px; }
+.emoji-desert { background-position: -380px -60px; }
+.emoji-desktop { background-position: -380px -80px; }
+.emoji-diamond_shape_with_a_dot_inside { background-position: -380px -100px; }
+.emoji-diamonds { background-position: -380px -120px; }
+.emoji-disappointed { background-position: -380px -140px; }
+.emoji-disappointed_relieved { background-position: -380px -160px; }
+.emoji-dividers { background-position: -380px -180px; }
+.emoji-dizzy { background-position: -380px -200px; }
+.emoji-dizzy_face { background-position: -380px -220px; }
+.emoji-do_not_litter { background-position: -380px -240px; }
+.emoji-dog { background-position: -380px -260px; }
+.emoji-dog2 { background-position: -380px -280px; }
+.emoji-dollar { background-position: -380px -300px; }
+.emoji-dolls { background-position: -380px -320px; }
+.emoji-dolphin { background-position: -380px -340px; }
+.emoji-door { background-position: -380px -360px; }
+.emoji-doughnut { background-position: 0 -380px; }
+.emoji-dove { background-position: -20px -380px; }
+.emoji-dragon { background-position: -40px -380px; }
+.emoji-dragon_face { background-position: -60px -380px; }
+.emoji-dress { background-position: -80px -380px; }
+.emoji-dromedary_camel { background-position: -100px -380px; }
+.emoji-drooling_face { background-position: -120px -380px; }
+.emoji-droplet { background-position: -140px -380px; }
+.emoji-drum { background-position: -160px -380px; }
+.emoji-duck { background-position: -180px -380px; }
+.emoji-dvd { background-position: -200px -380px; }
+.emoji-e-mail { background-position: -220px -380px; }
+.emoji-eagle { background-position: -240px -380px; }
+.emoji-ear { background-position: -260px -380px; }
+.emoji-ear_of_rice { background-position: -280px -380px; }
+.emoji-ear_tone1 { background-position: -300px -380px; }
+.emoji-ear_tone2 { background-position: -320px -380px; }
+.emoji-ear_tone3 { background-position: -340px -380px; }
+.emoji-ear_tone4 { background-position: -360px -380px; }
+.emoji-ear_tone5 { background-position: -380px -380px; }
+.emoji-earth_africa { background-position: -400px 0; }
+.emoji-earth_americas { background-position: -400px -20px; }
+.emoji-earth_asia { background-position: -400px -40px; }
+.emoji-egg { background-position: -400px -60px; }
+.emoji-eggplant { background-position: -400px -80px; }
+.emoji-eight { background-position: -400px -100px; }
+.emoji-eight_pointed_black_star { background-position: -400px -120px; }
+.emoji-eight_spoked_asterisk { background-position: -400px -140px; }
+.emoji-eject { background-position: -400px -160px; }
+.emoji-electric_plug { background-position: -400px -180px; }
+.emoji-elephant { background-position: -400px -200px; }
+.emoji-end { background-position: -400px -220px; }
+.emoji-envelope { background-position: -400px -240px; }
+.emoji-envelope_with_arrow { background-position: -400px -260px; }
+.emoji-euro { background-position: -400px -280px; }
+.emoji-european_castle { background-position: -400px -300px; }
+.emoji-european_post_office { background-position: -400px -320px; }
+.emoji-evergreen_tree { background-position: -400px -340px; }
+.emoji-exclamation { background-position: -400px -360px; }
+.emoji-expressionless { background-position: -400px -380px; }
+.emoji-eye { background-position: 0 -400px; }
+.emoji-eye_in_speech_bubble { background-position: -20px -400px; }
+.emoji-eyeglasses { background-position: -40px -400px; }
+.emoji-eyes { background-position: -60px -400px; }
+.emoji-face_palm { background-position: -80px -400px; }
+.emoji-face_palm_tone1 { background-position: -100px -400px; }
+.emoji-face_palm_tone2 { background-position: -120px -400px; }
+.emoji-face_palm_tone3 { background-position: -140px -400px; }
+.emoji-face_palm_tone4 { background-position: -160px -400px; }
+.emoji-face_palm_tone5 { background-position: -180px -400px; }
+.emoji-factory { background-position: -200px -400px; }
+.emoji-fallen_leaf { background-position: -220px -400px; }
+.emoji-family { background-position: -240px -400px; }
+.emoji-family_mmb { background-position: -260px -400px; }
+.emoji-family_mmbb { background-position: -280px -400px; }
+.emoji-family_mmg { background-position: -300px -400px; }
+.emoji-family_mmgb { background-position: -320px -400px; }
+.emoji-family_mmgg { background-position: -340px -400px; }
+.emoji-family_mwbb { background-position: -360px -400px; }
+.emoji-family_mwg { background-position: -380px -400px; }
+.emoji-family_mwgb { background-position: -400px -400px; }
+.emoji-family_mwgg { background-position: -420px 0; }
+.emoji-family_wwb { background-position: -420px -20px; }
+.emoji-family_wwbb { background-position: -420px -40px; }
+.emoji-family_wwg { background-position: -420px -60px; }
+.emoji-family_wwgb { background-position: -420px -80px; }
+.emoji-family_wwgg { background-position: -420px -100px; }
+.emoji-fast_forward { background-position: -420px -120px; }
+.emoji-fax { background-position: -420px -140px; }
+.emoji-fearful { background-position: -420px -160px; }
+.emoji-feet { background-position: -420px -180px; }
+.emoji-fencer { background-position: -420px -200px; }
+.emoji-ferris_wheel { background-position: -420px -220px; }
+.emoji-ferry { background-position: -420px -240px; }
+.emoji-field_hockey { background-position: -420px -260px; }
+.emoji-file_cabinet { background-position: -420px -280px; }
+.emoji-file_folder { background-position: -420px -300px; }
+.emoji-film_frames { background-position: -420px -320px; }
+.emoji-fingers_crossed { background-position: -420px -340px; }
+.emoji-fingers_crossed_tone1 { background-position: -420px -360px; }
+.emoji-fingers_crossed_tone2 { background-position: -420px -380px; }
+.emoji-fingers_crossed_tone3 { background-position: -420px -400px; }
+.emoji-fingers_crossed_tone4 { background-position: 0 -420px; }
+.emoji-fingers_crossed_tone5 { background-position: -20px -420px; }
+.emoji-fire { background-position: -40px -420px; }
+.emoji-fire_engine { background-position: -60px -420px; }
+.emoji-fireworks { background-position: -80px -420px; }
+.emoji-first_place { background-position: -100px -420px; }
+.emoji-first_quarter_moon { background-position: -120px -420px; }
+.emoji-first_quarter_moon_with_face { background-position: -140px -420px; }
+.emoji-fish { background-position: -160px -420px; }
+.emoji-fish_cake { background-position: -180px -420px; }
+.emoji-fishing_pole_and_fish { background-position: -200px -420px; }
+.emoji-fist { background-position: -220px -420px; }
+.emoji-fist_tone1 { background-position: -240px -420px; }
+.emoji-fist_tone2 { background-position: -260px -420px; }
+.emoji-fist_tone3 { background-position: -280px -420px; }
+.emoji-fist_tone4 { background-position: -300px -420px; }
+.emoji-fist_tone5 { background-position: -320px -420px; }
+.emoji-five { background-position: -340px -420px; }
+.emoji-flag_ac { background-position: -360px -420px; }
+.emoji-flag_ad { background-position: -380px -420px; }
+.emoji-flag_ae { background-position: -400px -420px; }
+.emoji-flag_af { background-position: -420px -420px; }
+.emoji-flag_ag { background-position: -440px 0; }
+.emoji-flag_ai { background-position: -440px -20px; }
+.emoji-flag_al { background-position: -440px -40px; }
+.emoji-flag_am { background-position: -440px -60px; }
+.emoji-flag_ao { background-position: -440px -80px; }
+.emoji-flag_aq { background-position: -440px -100px; }
+.emoji-flag_ar { background-position: -440px -120px; }
+.emoji-flag_as { background-position: -440px -140px; }
+.emoji-flag_at { background-position: -440px -160px; }
+.emoji-flag_au { background-position: -440px -180px; }
+.emoji-flag_aw { background-position: -440px -200px; }
+.emoji-flag_ax { background-position: -440px -220px; }
+.emoji-flag_az { background-position: -440px -240px; }
+.emoji-flag_ba { background-position: -440px -260px; }
+.emoji-flag_bb { background-position: -440px -280px; }
+.emoji-flag_bd { background-position: -440px -300px; }
+.emoji-flag_be { background-position: -440px -320px; }
+.emoji-flag_bf { background-position: -440px -340px; }
+.emoji-flag_bg { background-position: -440px -360px; }
+.emoji-flag_bh { background-position: -440px -380px; }
+.emoji-flag_bi { background-position: -440px -400px; }
+.emoji-flag_bj { background-position: -440px -420px; }
+.emoji-flag_bl { background-position: 0 -440px; }
+.emoji-flag_black { background-position: -20px -440px; }
+.emoji-flag_bm { background-position: -40px -440px; }
+.emoji-flag_bn { background-position: -60px -440px; }
+.emoji-flag_bo { background-position: -80px -440px; }
+.emoji-flag_bq { background-position: -100px -440px; }
+.emoji-flag_br { background-position: -120px -440px; }
+.emoji-flag_bs { background-position: -140px -440px; }
+.emoji-flag_bt { background-position: -160px -440px; }
+.emoji-flag_bv { background-position: -180px -440px; }
+.emoji-flag_bw { background-position: -200px -440px; }
+.emoji-flag_by { background-position: -220px -440px; }
+.emoji-flag_bz { background-position: -240px -440px; }
+.emoji-flag_ca { background-position: -260px -440px; }
+.emoji-flag_cc { background-position: -280px -440px; }
+.emoji-flag_cd { background-position: -300px -440px; }
+.emoji-flag_cf { background-position: -320px -440px; }
+.emoji-flag_cg { background-position: -340px -440px; }
+.emoji-flag_ch { background-position: -360px -440px; }
+.emoji-flag_ci { background-position: -380px -440px; }
+.emoji-flag_ck { background-position: -400px -440px; }
+.emoji-flag_cl { background-position: -420px -440px; }
+.emoji-flag_cm { background-position: -440px -440px; }
+.emoji-flag_cn { background-position: -460px 0; }
+.emoji-flag_co { background-position: -460px -20px; }
+.emoji-flag_cp { background-position: -460px -40px; }
+.emoji-flag_cr { background-position: -460px -60px; }
+.emoji-flag_cu { background-position: -460px -80px; }
+.emoji-flag_cv { background-position: -460px -100px; }
+.emoji-flag_cw { background-position: -460px -120px; }
+.emoji-flag_cx { background-position: -460px -140px; }
+.emoji-flag_cy { background-position: -460px -160px; }
+.emoji-flag_cz { background-position: -460px -180px; }
+.emoji-flag_de { background-position: -460px -200px; }
+.emoji-flag_dg { background-position: -460px -220px; }
+.emoji-flag_dj { background-position: -460px -240px; }
+.emoji-flag_dk { background-position: -460px -260px; }
+.emoji-flag_dm { background-position: -460px -280px; }
+.emoji-flag_do { background-position: -460px -300px; }
+.emoji-flag_dz { background-position: -460px -320px; }
+.emoji-flag_ea { background-position: -460px -340px; }
+.emoji-flag_ec { background-position: -460px -360px; }
+.emoji-flag_ee { background-position: -460px -380px; }
+.emoji-flag_eg { background-position: -460px -400px; }
+.emoji-flag_eh { background-position: -460px -420px; }
+.emoji-flag_er { background-position: -460px -440px; }
+.emoji-flag_es { background-position: 0 -460px; }
+.emoji-flag_et { background-position: -20px -460px; }
+.emoji-flag_eu { background-position: -40px -460px; }
+.emoji-flag_fi { background-position: -60px -460px; }
+.emoji-flag_fj { background-position: -80px -460px; }
+.emoji-flag_fk { background-position: -100px -460px; }
+.emoji-flag_fm { background-position: -120px -460px; }
+.emoji-flag_fo { background-position: -140px -460px; }
+.emoji-flag_fr { background-position: -160px -460px; }
+.emoji-flag_ga { background-position: -180px -460px; }
+.emoji-flag_gb { background-position: -200px -460px; }
+.emoji-flag_gd { background-position: -220px -460px; }
+.emoji-flag_ge { background-position: -240px -460px; }
+.emoji-flag_gf { background-position: -260px -460px; }
+.emoji-flag_gg { background-position: -280px -460px; }
+.emoji-flag_gh { background-position: -300px -460px; }
+.emoji-flag_gi { background-position: -320px -460px; }
+.emoji-flag_gl { background-position: -340px -460px; }
+.emoji-flag_gm { background-position: -360px -460px; }
+.emoji-flag_gn { background-position: -380px -460px; }
+.emoji-flag_gp { background-position: -400px -460px; }
+.emoji-flag_gq { background-position: -420px -460px; }
+.emoji-flag_gr { background-position: -440px -460px; }
+.emoji-flag_gs { background-position: -460px -460px; }
+.emoji-flag_gt { background-position: -480px 0; }
+.emoji-flag_gu { background-position: -480px -20px; }
+.emoji-flag_gw { background-position: -480px -40px; }
+.emoji-flag_gy { background-position: -480px -60px; }
+.emoji-flag_hk { background-position: -480px -80px; }
+.emoji-flag_hm { background-position: -480px -100px; }
+.emoji-flag_hn { background-position: -480px -120px; }
+.emoji-flag_hr { background-position: -480px -140px; }
+.emoji-flag_ht { background-position: -480px -160px; }
+.emoji-flag_hu { background-position: -480px -180px; }
+.emoji-flag_ic { background-position: -480px -200px; }
+.emoji-flag_id { background-position: -480px -220px; }
+.emoji-flag_ie { background-position: -480px -240px; }
+.emoji-flag_il { background-position: -480px -260px; }
+.emoji-flag_im { background-position: -480px -280px; }
+.emoji-flag_in { background-position: -480px -300px; }
+.emoji-flag_io { background-position: -480px -320px; }
+.emoji-flag_iq { background-position: -480px -340px; }
+.emoji-flag_ir { background-position: -480px -360px; }
+.emoji-flag_is { background-position: -480px -380px; }
+.emoji-flag_it { background-position: -480px -400px; }
+.emoji-flag_je { background-position: -480px -420px; }
+.emoji-flag_jm { background-position: -480px -440px; }
+.emoji-flag_jo { background-position: -480px -460px; }
+.emoji-flag_jp { background-position: 0 -480px; }
+.emoji-flag_ke { background-position: -20px -480px; }
+.emoji-flag_kg { background-position: -40px -480px; }
+.emoji-flag_kh { background-position: -60px -480px; }
+.emoji-flag_ki { background-position: -80px -480px; }
+.emoji-flag_km { background-position: -100px -480px; }
+.emoji-flag_kn { background-position: -120px -480px; }
+.emoji-flag_kp { background-position: -140px -480px; }
+.emoji-flag_kr { background-position: -160px -480px; }
+.emoji-flag_kw { background-position: -180px -480px; }
+.emoji-flag_ky { background-position: -200px -480px; }
+.emoji-flag_kz { background-position: -220px -480px; }
+.emoji-flag_la { background-position: -240px -480px; }
+.emoji-flag_lb { background-position: -260px -480px; }
+.emoji-flag_lc { background-position: -280px -480px; }
+.emoji-flag_li { background-position: -300px -480px; }
+.emoji-flag_lk { background-position: -320px -480px; }
+.emoji-flag_lr { background-position: -340px -480px; }
+.emoji-flag_ls { background-position: -360px -480px; }
+.emoji-flag_lt { background-position: -380px -480px; }
+.emoji-flag_lu { background-position: -400px -480px; }
+.emoji-flag_lv { background-position: -420px -480px; }
+.emoji-flag_ly { background-position: -440px -480px; }
+.emoji-flag_ma { background-position: -460px -480px; }
+.emoji-flag_mc { background-position: -480px -480px; }
+.emoji-flag_md { background-position: -500px 0; }
+.emoji-flag_me { background-position: -500px -20px; }
+.emoji-flag_mf { background-position: -500px -40px; }
+.emoji-flag_mg { background-position: -500px -60px; }
+.emoji-flag_mh { background-position: -500px -80px; }
+.emoji-flag_mk { background-position: -500px -100px; }
+.emoji-flag_ml { background-position: -500px -120px; }
+.emoji-flag_mm { background-position: -500px -140px; }
+.emoji-flag_mn { background-position: -500px -160px; }
+.emoji-flag_mo { background-position: -500px -180px; }
+.emoji-flag_mp { background-position: -500px -200px; }
+.emoji-flag_mq { background-position: -500px -220px; }
+.emoji-flag_mr { background-position: -500px -240px; }
+.emoji-flag_ms { background-position: -500px -260px; }
+.emoji-flag_mt { background-position: -500px -280px; }
+.emoji-flag_mu { background-position: -500px -300px; }
+.emoji-flag_mv { background-position: -500px -320px; }
+.emoji-flag_mw { background-position: -500px -340px; }
+.emoji-flag_mx { background-position: -500px -360px; }
+.emoji-flag_my { background-position: -500px -380px; }
+.emoji-flag_mz { background-position: -500px -400px; }
+.emoji-flag_na { background-position: -500px -420px; }
+.emoji-flag_nc { background-position: -500px -440px; }
+.emoji-flag_ne { background-position: -500px -460px; }
+.emoji-flag_nf { background-position: -500px -480px; }
+.emoji-flag_ng { background-position: 0 -500px; }
+.emoji-flag_ni { background-position: -20px -500px; }
+.emoji-flag_nl { background-position: -40px -500px; }
+.emoji-flag_no { background-position: -60px -500px; }
+.emoji-flag_np { background-position: -80px -500px; }
+.emoji-flag_nr { background-position: -100px -500px; }
+.emoji-flag_nu { background-position: -120px -500px; }
+.emoji-flag_nz { background-position: -140px -500px; }
+.emoji-flag_om { background-position: -160px -500px; }
+.emoji-flag_pa { background-position: -180px -500px; }
+.emoji-flag_pe { background-position: -200px -500px; }
+.emoji-flag_pf { background-position: -220px -500px; }
+.emoji-flag_pg { background-position: -240px -500px; }
+.emoji-flag_ph { background-position: -260px -500px; }
+.emoji-flag_pk { background-position: -280px -500px; }
+.emoji-flag_pl { background-position: -300px -500px; }
+.emoji-flag_pm { background-position: -320px -500px; }
+.emoji-flag_pn { background-position: -340px -500px; }
+.emoji-flag_pr { background-position: -360px -500px; }
+.emoji-flag_ps { background-position: -380px -500px; }
+.emoji-flag_pt { background-position: -400px -500px; }
+.emoji-flag_pw { background-position: -420px -500px; }
+.emoji-flag_py { background-position: -440px -500px; }
+.emoji-flag_qa { background-position: -460px -500px; }
+.emoji-flag_re { background-position: -480px -500px; }
+.emoji-flag_ro { background-position: -500px -500px; }
+.emoji-flag_rs { background-position: -520px 0; }
+.emoji-flag_ru { background-position: -520px -20px; }
+.emoji-flag_rw { background-position: -520px -40px; }
+.emoji-flag_sa { background-position: -520px -60px; }
+.emoji-flag_sb { background-position: -520px -80px; }
+.emoji-flag_sc { background-position: -520px -100px; }
+.emoji-flag_sd { background-position: -520px -120px; }
+.emoji-flag_se { background-position: -520px -140px; }
+.emoji-flag_sg { background-position: -520px -160px; }
+.emoji-flag_sh { background-position: -520px -180px; }
+.emoji-flag_si { background-position: -520px -200px; }
+.emoji-flag_sj { background-position: -520px -220px; }
+.emoji-flag_sk { background-position: -520px -240px; }
+.emoji-flag_sl { background-position: -520px -260px; }
+.emoji-flag_sm { background-position: -520px -280px; }
+.emoji-flag_sn { background-position: -520px -300px; }
+.emoji-flag_so { background-position: -520px -320px; }
+.emoji-flag_sr { background-position: -520px -340px; }
+.emoji-flag_ss { background-position: -520px -360px; }
+.emoji-flag_st { background-position: -520px -380px; }
+.emoji-flag_sv { background-position: -520px -400px; }
+.emoji-flag_sx { background-position: -520px -420px; }
+.emoji-flag_sy { background-position: -520px -440px; }
+.emoji-flag_sz { background-position: -520px -460px; }
+.emoji-flag_ta { background-position: -520px -480px; }
+.emoji-flag_tc { background-position: -520px -500px; }
+.emoji-flag_td { background-position: 0 -520px; }
+.emoji-flag_tf { background-position: -20px -520px; }
+.emoji-flag_tg { background-position: -40px -520px; }
+.emoji-flag_th { background-position: -60px -520px; }
+.emoji-flag_tj { background-position: -80px -520px; }
+.emoji-flag_tk { background-position: -100px -520px; }
+.emoji-flag_tl { background-position: -120px -520px; }
+.emoji-flag_tm { background-position: -140px -520px; }
+.emoji-flag_tn { background-position: -160px -520px; }
+.emoji-flag_to { background-position: -180px -520px; }
+.emoji-flag_tr { background-position: -200px -520px; }
+.emoji-flag_tt { background-position: -220px -520px; }
+.emoji-flag_tv { background-position: -240px -520px; }
+.emoji-flag_tw { background-position: -260px -520px; }
+.emoji-flag_tz { background-position: -280px -520px; }
+.emoji-flag_ua { background-position: -300px -520px; }
+.emoji-flag_ug { background-position: -320px -520px; }
+.emoji-flag_um { background-position: -340px -520px; }
+.emoji-flag_us { background-position: -360px -520px; }
+.emoji-flag_uy { background-position: -380px -520px; }
+.emoji-flag_uz { background-position: -400px -520px; }
+.emoji-flag_va { background-position: -420px -520px; }
+.emoji-flag_vc { background-position: -440px -520px; }
+.emoji-flag_ve { background-position: -460px -520px; }
+.emoji-flag_vg { background-position: -480px -520px; }
+.emoji-flag_vi { background-position: -500px -520px; }
+.emoji-flag_vn { background-position: -520px -520px; }
+.emoji-flag_vu { background-position: -540px 0; }
+.emoji-flag_wf { background-position: -540px -20px; }
+.emoji-flag_white { background-position: -540px -40px; }
+.emoji-flag_ws { background-position: -540px -60px; }
+.emoji-flag_xk { background-position: -540px -80px; }
+.emoji-flag_ye { background-position: -540px -100px; }
+.emoji-flag_yt { background-position: -540px -120px; }
+.emoji-flag_za { background-position: -540px -140px; }
+.emoji-flag_zm { background-position: -540px -160px; }
+.emoji-flag_zw { background-position: -540px -180px; }
+.emoji-flags { background-position: -540px -200px; }
+.emoji-flashlight { background-position: -540px -220px; }
+.emoji-fleur-de-lis { background-position: -540px -240px; }
+.emoji-floppy_disk { background-position: -540px -260px; }
+.emoji-flower_playing_cards { background-position: -540px -280px; }
+.emoji-flushed { background-position: -540px -300px; }
+.emoji-fog { background-position: -540px -320px; }
+.emoji-foggy { background-position: -540px -340px; }
+.emoji-football { background-position: -540px -360px; }
+.emoji-footprints { background-position: -540px -380px; }
+.emoji-fork_and_knife { background-position: -540px -400px; }
+.emoji-fork_knife_plate { background-position: -540px -420px; }
+.emoji-fountain { background-position: -540px -440px; }
+.emoji-four { background-position: -540px -460px; }
+.emoji-four_leaf_clover { background-position: -540px -480px; }
+.emoji-fox { background-position: -540px -500px; }
+.emoji-frame_photo { background-position: -540px -520px; }
+.emoji-free { background-position: 0 -540px; }
+.emoji-french_bread { background-position: -20px -540px; }
+.emoji-fried_shrimp { background-position: -40px -540px; }
+.emoji-fries { background-position: -60px -540px; }
+.emoji-frog { background-position: -80px -540px; }
+.emoji-frowning { background-position: -100px -540px; }
+.emoji-frowning2 { background-position: -120px -540px; }
+.emoji-fuelpump { background-position: -140px -540px; }
+.emoji-full_moon { background-position: -160px -540px; }
+.emoji-full_moon_with_face { background-position: -180px -540px; }
+.emoji-game_die { background-position: -200px -540px; }
+.emoji-gear { background-position: -220px -540px; }
+.emoji-gem { background-position: -240px -540px; }
+.emoji-gemini { background-position: -260px -540px; }
+.emoji-ghost { background-position: -280px -540px; }
+.emoji-gift { background-position: -300px -540px; }
+.emoji-gift_heart { background-position: -320px -540px; }
+.emoji-girl { background-position: -340px -540px; }
+.emoji-girl_tone1 { background-position: -360px -540px; }
+.emoji-girl_tone2 { background-position: -380px -540px; }
+.emoji-girl_tone3 { background-position: -400px -540px; }
+.emoji-girl_tone4 { background-position: -420px -540px; }
+.emoji-girl_tone5 { background-position: -440px -540px; }
+.emoji-globe_with_meridians { background-position: -460px -540px; }
+.emoji-goal { background-position: -480px -540px; }
+.emoji-goat { background-position: -500px -540px; }
+.emoji-golf { background-position: -520px -540px; }
+.emoji-golfer { background-position: -540px -540px; }
+.emoji-gorilla { background-position: -560px 0; }
+.emoji-grapes { background-position: -560px -20px; }
+.emoji-green_apple { background-position: -560px -40px; }
+.emoji-green_book { background-position: -560px -60px; }
+.emoji-green_heart { background-position: -560px -80px; }
+.emoji-grey_exclamation { background-position: -560px -100px; }
+.emoji-grey_question { background-position: -560px -120px; }
+.emoji-grimacing { background-position: -560px -140px; }
+.emoji-grin { background-position: -560px -160px; }
+.emoji-grinning { background-position: -560px -180px; }
+.emoji-guardsman { background-position: -560px -200px; }
+.emoji-guardsman_tone1 { background-position: -560px -220px; }
+.emoji-guardsman_tone2 { background-position: -560px -240px; }
+.emoji-guardsman_tone3 { background-position: -560px -260px; }
+.emoji-guardsman_tone4 { background-position: -560px -280px; }
+.emoji-guardsman_tone5 { background-position: -560px -300px; }
+.emoji-guitar { background-position: -560px -320px; }
+.emoji-gun { background-position: -560px -340px; }
+.emoji-haircut { background-position: -560px -360px; }
+.emoji-haircut_tone1 { background-position: -560px -380px; }
+.emoji-haircut_tone2 { background-position: -560px -400px; }
+.emoji-haircut_tone3 { background-position: -560px -420px; }
+.emoji-haircut_tone4 { background-position: -560px -440px; }
+.emoji-haircut_tone5 { background-position: -560px -460px; }
+.emoji-hamburger { background-position: -560px -480px; }
+.emoji-hammer { background-position: -560px -500px; }
+.emoji-hammer_pick { background-position: -560px -520px; }
+.emoji-hamster { background-position: -560px -540px; }
+.emoji-hand_splayed { background-position: 0 -560px; }
+.emoji-hand_splayed_tone1 { background-position: -20px -560px; }
+.emoji-hand_splayed_tone2 { background-position: -40px -560px; }
+.emoji-hand_splayed_tone3 { background-position: -60px -560px; }
+.emoji-hand_splayed_tone4 { background-position: -80px -560px; }
+.emoji-hand_splayed_tone5 { background-position: -100px -560px; }
+.emoji-handbag { background-position: -120px -560px; }
+.emoji-handball { background-position: -140px -560px; }
+.emoji-handball_tone1 { background-position: -160px -560px; }
+.emoji-handball_tone2 { background-position: -180px -560px; }
+.emoji-handball_tone3 { background-position: -200px -560px; }
+.emoji-handball_tone4 { background-position: -220px -560px; }
+.emoji-handball_tone5 { background-position: -240px -560px; }
+.emoji-handshake { background-position: -260px -560px; }
+.emoji-handshake_tone1 { background-position: -280px -560px; }
+.emoji-handshake_tone2 { background-position: -300px -560px; }
+.emoji-handshake_tone3 { background-position: -320px -560px; }
+.emoji-handshake_tone4 { background-position: -340px -560px; }
+.emoji-handshake_tone5 { background-position: -360px -560px; }
+.emoji-hash { background-position: -380px -560px; }
+.emoji-hatched_chick { background-position: -400px -560px; }
+.emoji-hatching_chick { background-position: -420px -560px; }
+.emoji-head_bandage { background-position: -440px -560px; }
+.emoji-headphones { background-position: -460px -560px; }
+.emoji-hear_no_evil { background-position: -480px -560px; }
+.emoji-heart { background-position: -500px -560px; }
+.emoji-heart_decoration { background-position: -520px -560px; }
+.emoji-heart_exclamation { background-position: -540px -560px; }
+.emoji-heart_eyes { background-position: -560px -560px; }
+.emoji-heart_eyes_cat { background-position: -580px 0; }
+.emoji-heartbeat { background-position: -580px -20px; }
+.emoji-heartpulse { background-position: -580px -40px; }
+.emoji-hearts { background-position: -580px -60px; }
+.emoji-heavy_check_mark { background-position: -580px -80px; }
+.emoji-heavy_division_sign { background-position: -580px -100px; }
+.emoji-heavy_dollar_sign { background-position: -580px -120px; }
+.emoji-heavy_minus_sign { background-position: -580px -140px; }
+.emoji-heavy_multiplication_x { background-position: -580px -160px; }
+.emoji-heavy_plus_sign { background-position: -580px -180px; }
+.emoji-helicopter { background-position: -580px -200px; }
+.emoji-helmet_with_cross { background-position: -580px -220px; }
+.emoji-herb { background-position: -580px -240px; }
+.emoji-hibiscus { background-position: -580px -260px; }
+.emoji-high_brightness { background-position: -580px -280px; }
+.emoji-high_heel { background-position: -580px -300px; }
+.emoji-hockey { background-position: -580px -320px; }
+.emoji-hole { background-position: -580px -340px; }
+.emoji-homes { background-position: -580px -360px; }
+.emoji-honey_pot { background-position: -580px -380px; }
+.emoji-horse { background-position: -580px -400px; }
+.emoji-horse_racing { background-position: -580px -420px; }
+.emoji-horse_racing_tone1 { background-position: -580px -440px; }
+.emoji-horse_racing_tone2 { background-position: -580px -460px; }
+.emoji-horse_racing_tone3 { background-position: -580px -480px; }
+.emoji-horse_racing_tone4 { background-position: -580px -500px; }
+.emoji-horse_racing_tone5 { background-position: -580px -520px; }
+.emoji-hospital { background-position: -580px -540px; }
+.emoji-hot_pepper { background-position: -580px -560px; }
+.emoji-hotdog { background-position: 0 -580px; }
+.emoji-hotel { background-position: -20px -580px; }
+.emoji-hotsprings { background-position: -40px -580px; }
+.emoji-hourglass { background-position: -60px -580px; }
+.emoji-hourglass_flowing_sand { background-position: -80px -580px; }
+.emoji-house { background-position: -100px -580px; }
+.emoji-house_abandoned { background-position: -120px -580px; }
+.emoji-house_with_garden { background-position: -140px -580px; }
+.emoji-hugging { background-position: -160px -580px; }
+.emoji-hushed { background-position: -180px -580px; }
+.emoji-ice_cream { background-position: -200px -580px; }
+.emoji-ice_skate { background-position: -220px -580px; }
+.emoji-icecream { background-position: -240px -580px; }
+.emoji-id { background-position: -260px -580px; }
+.emoji-ideograph_advantage { background-position: -280px -580px; }
+.emoji-imp { background-position: -300px -580px; }
+.emoji-inbox_tray { background-position: -320px -580px; }
+.emoji-incoming_envelope { background-position: -340px -580px; }
+.emoji-information_desk_person { background-position: -360px -580px; }
+.emoji-information_desk_person_tone1 { background-position: -380px -580px; }
+.emoji-information_desk_person_tone2 { background-position: -400px -580px; }
+.emoji-information_desk_person_tone3 { background-position: -420px -580px; }
+.emoji-information_desk_person_tone4 { background-position: -440px -580px; }
+.emoji-information_desk_person_tone5 { background-position: -460px -580px; }
+.emoji-information_source { background-position: -480px -580px; }
+.emoji-innocent { background-position: -500px -580px; }
+.emoji-interrobang { background-position: -520px -580px; }
+.emoji-iphone { background-position: -540px -580px; }
+.emoji-island { background-position: -560px -580px; }
+.emoji-izakaya_lantern { background-position: -580px -580px; }
+.emoji-jack_o_lantern { background-position: -600px 0; }
+.emoji-japan { background-position: -600px -20px; }
+.emoji-japanese_castle { background-position: -600px -40px; }
+.emoji-japanese_goblin { background-position: -600px -60px; }
+.emoji-japanese_ogre { background-position: -600px -80px; }
+.emoji-jeans { background-position: -600px -100px; }
+.emoji-joy { background-position: -600px -120px; }
+.emoji-joy_cat { background-position: -600px -140px; }
+.emoji-joystick { background-position: -600px -160px; }
+.emoji-juggling { background-position: -600px -180px; }
+.emoji-juggling_tone1 { background-position: -600px -200px; }
+.emoji-juggling_tone2 { background-position: -600px -220px; }
+.emoji-juggling_tone3 { background-position: -600px -240px; }
+.emoji-juggling_tone4 { background-position: -600px -260px; }
+.emoji-juggling_tone5 { background-position: -600px -280px; }
+.emoji-kaaba { background-position: -600px -300px; }
+.emoji-key { background-position: -600px -320px; }
+.emoji-key2 { background-position: -600px -340px; }
+.emoji-keyboard { background-position: -600px -360px; }
+.emoji-kimono { background-position: -600px -380px; }
+.emoji-kiss { background-position: -600px -400px; }
+.emoji-kiss_mm { background-position: -600px -420px; }
+.emoji-kiss_ww { background-position: -600px -440px; }
+.emoji-kissing { background-position: -600px -460px; }
+.emoji-kissing_cat { background-position: -600px -480px; }
+.emoji-kissing_closed_eyes { background-position: -600px -500px; }
+.emoji-kissing_heart { background-position: -600px -520px; }
+.emoji-kissing_smiling_eyes { background-position: -600px -540px; }
+.emoji-kiwi { background-position: -600px -560px; }
+.emoji-knife { background-position: -600px -580px; }
+.emoji-koala { background-position: 0 -600px; }
+.emoji-koko { background-position: -20px -600px; }
+.emoji-label { background-position: -40px -600px; }
+.emoji-large_blue_circle { background-position: -60px -600px; }
+.emoji-large_blue_diamond { background-position: -80px -600px; }
+.emoji-large_orange_diamond { background-position: -100px -600px; }
+.emoji-last_quarter_moon { background-position: -120px -600px; }
+.emoji-last_quarter_moon_with_face { background-position: -140px -600px; }
+.emoji-laughing { background-position: -160px -600px; }
+.emoji-leaves { background-position: -180px -600px; }
+.emoji-ledger { background-position: -200px -600px; }
+.emoji-left_facing_fist { background-position: -220px -600px; }
+.emoji-left_facing_fist_tone1 { background-position: -240px -600px; }
+.emoji-left_facing_fist_tone2 { background-position: -260px -600px; }
+.emoji-left_facing_fist_tone3 { background-position: -280px -600px; }
+.emoji-left_facing_fist_tone4 { background-position: -300px -600px; }
+.emoji-left_facing_fist_tone5 { background-position: -320px -600px; }
+.emoji-left_luggage { background-position: -340px -600px; }
+.emoji-left_right_arrow { background-position: -360px -600px; }
+.emoji-leftwards_arrow_with_hook { background-position: -380px -600px; }
+.emoji-lemon { background-position: -400px -600px; }
+.emoji-leo { background-position: -420px -600px; }
+.emoji-leopard { background-position: -440px -600px; }
+.emoji-level_slider { background-position: -460px -600px; }
+.emoji-levitate { background-position: -480px -600px; }
+.emoji-libra { background-position: -500px -600px; }
+.emoji-lifter { background-position: -520px -600px; }
+.emoji-lifter_tone1 { background-position: -540px -600px; }
+.emoji-lifter_tone2 { background-position: -560px -600px; }
+.emoji-lifter_tone3 { background-position: -580px -600px; }
+.emoji-lifter_tone4 { background-position: -600px -600px; }
+.emoji-lifter_tone5 { background-position: -620px 0; }
+.emoji-light_rail { background-position: -620px -20px; }
+.emoji-link { background-position: -620px -40px; }
+.emoji-lion_face { background-position: -620px -60px; }
+.emoji-lips { background-position: -620px -80px; }
+.emoji-lipstick { background-position: -620px -100px; }
+.emoji-lizard { background-position: -620px -120px; }
+.emoji-lock { background-position: -620px -140px; }
+.emoji-lock_with_ink_pen { background-position: -620px -160px; }
+.emoji-lollipop { background-position: -620px -180px; }
+.emoji-loop { background-position: -620px -200px; }
+.emoji-loud_sound { background-position: -620px -220px; }
+.emoji-loudspeaker { background-position: -620px -240px; }
+.emoji-love_hotel { background-position: -620px -260px; }
+.emoji-love_letter { background-position: -620px -280px; }
+.emoji-low_brightness { background-position: -620px -300px; }
+.emoji-lying_face { background-position: -620px -320px; }
+.emoji-m { background-position: -620px -340px; }
+.emoji-mag { background-position: -620px -360px; }
+.emoji-mag_right { background-position: -620px -380px; }
+.emoji-mahjong { background-position: -620px -400px; }
+.emoji-mailbox { background-position: -620px -420px; }
+.emoji-mailbox_closed { background-position: -620px -440px; }
+.emoji-mailbox_with_mail { background-position: -620px -460px; }
+.emoji-mailbox_with_no_mail { background-position: -620px -480px; }
+.emoji-man { background-position: -620px -500px; }
+.emoji-man_dancing { background-position: -620px -520px; }
+.emoji-man_dancing_tone1 { background-position: -620px -540px; }
+.emoji-man_dancing_tone2 { background-position: -620px -560px; }
+.emoji-man_dancing_tone3 { background-position: -620px -580px; }
+.emoji-man_dancing_tone4 { background-position: -620px -600px; }
+.emoji-man_dancing_tone5 { background-position: 0 -620px; }
+.emoji-man_in_tuxedo { background-position: -20px -620px; }
+.emoji-man_in_tuxedo_tone1 { background-position: -40px -620px; }
+.emoji-man_in_tuxedo_tone2 { background-position: -60px -620px; }
+.emoji-man_in_tuxedo_tone3 { background-position: -80px -620px; }
+.emoji-man_in_tuxedo_tone4 { background-position: -100px -620px; }
+.emoji-man_in_tuxedo_tone5 { background-position: -120px -620px; }
+.emoji-man_tone1 { background-position: -140px -620px; }
+.emoji-man_tone2 { background-position: -160px -620px; }
+.emoji-man_tone3 { background-position: -180px -620px; }
+.emoji-man_tone4 { background-position: -200px -620px; }
+.emoji-man_tone5 { background-position: -220px -620px; }
+.emoji-man_with_gua_pi_mao { background-position: -240px -620px; }
+.emoji-man_with_gua_pi_mao_tone1 { background-position: -260px -620px; }
+.emoji-man_with_gua_pi_mao_tone2 { background-position: -280px -620px; }
+.emoji-man_with_gua_pi_mao_tone3 { background-position: -300px -620px; }
+.emoji-man_with_gua_pi_mao_tone4 { background-position: -320px -620px; }
+.emoji-man_with_gua_pi_mao_tone5 { background-position: -340px -620px; }
+.emoji-man_with_turban { background-position: -360px -620px; }
+.emoji-man_with_turban_tone1 { background-position: -380px -620px; }
+.emoji-man_with_turban_tone2 { background-position: -400px -620px; }
+.emoji-man_with_turban_tone3 { background-position: -420px -620px; }
+.emoji-man_with_turban_tone4 { background-position: -440px -620px; }
+.emoji-man_with_turban_tone5 { background-position: -460px -620px; }
+.emoji-mans_shoe { background-position: -480px -620px; }
+.emoji-map { background-position: -500px -620px; }
+.emoji-maple_leaf { background-position: -520px -620px; }
+.emoji-martial_arts_uniform { background-position: -540px -620px; }
+.emoji-mask { background-position: -560px -620px; }
+.emoji-massage { background-position: -580px -620px; }
+.emoji-massage_tone1 { background-position: -600px -620px; }
+.emoji-massage_tone2 { background-position: -620px -620px; }
+.emoji-massage_tone3 { background-position: -640px 0; }
+.emoji-massage_tone4 { background-position: -640px -20px; }
+.emoji-massage_tone5 { background-position: -640px -40px; }
+.emoji-meat_on_bone { background-position: -640px -60px; }
+.emoji-medal { background-position: -640px -80px; }
+.emoji-mega { background-position: -640px -100px; }
+.emoji-melon { background-position: -640px -120px; }
+.emoji-menorah { background-position: -640px -140px; }
+.emoji-mens { background-position: -640px -160px; }
+.emoji-metal { background-position: -640px -180px; }
+.emoji-metal_tone1 { background-position: -640px -200px; }
+.emoji-metal_tone2 { background-position: -640px -220px; }
+.emoji-metal_tone3 { background-position: -640px -240px; }
+.emoji-metal_tone4 { background-position: -640px -260px; }
+.emoji-metal_tone5 { background-position: -640px -280px; }
+.emoji-metro { background-position: -640px -300px; }
+.emoji-microphone { background-position: -640px -320px; }
+.emoji-microphone2 { background-position: -640px -340px; }
+.emoji-microscope { background-position: -640px -360px; }
+.emoji-middle_finger { background-position: -640px -380px; }
+.emoji-middle_finger_tone1 { background-position: -640px -400px; }
+.emoji-middle_finger_tone2 { background-position: -640px -420px; }
+.emoji-middle_finger_tone3 { background-position: -640px -440px; }
+.emoji-middle_finger_tone4 { background-position: -640px -460px; }
+.emoji-middle_finger_tone5 { background-position: -640px -480px; }
+.emoji-military_medal { background-position: -640px -500px; }
+.emoji-milk { background-position: -640px -520px; }
+.emoji-milky_way { background-position: -640px -540px; }
+.emoji-minibus { background-position: -640px -560px; }
+.emoji-minidisc { background-position: -640px -580px; }
+.emoji-mobile_phone_off { background-position: -640px -600px; }
+.emoji-money_mouth { background-position: -640px -620px; }
+.emoji-money_with_wings { background-position: 0 -640px; }
+.emoji-moneybag { background-position: -20px -640px; }
+.emoji-monkey { background-position: -40px -640px; }
+.emoji-monkey_face { background-position: -60px -640px; }
+.emoji-monorail { background-position: -80px -640px; }
+.emoji-mortar_board { background-position: -100px -640px; }
+.emoji-mosque { background-position: -120px -640px; }
+.emoji-motor_scooter { background-position: -140px -640px; }
+.emoji-motorboat { background-position: -160px -640px; }
+.emoji-motorcycle { background-position: -180px -640px; }
+.emoji-motorway { background-position: -200px -640px; }
+.emoji-mount_fuji { background-position: -220px -640px; }
+.emoji-mountain { background-position: -240px -640px; }
+.emoji-mountain_bicyclist { background-position: -260px -640px; }
+.emoji-mountain_bicyclist_tone1 { background-position: -280px -640px; }
+.emoji-mountain_bicyclist_tone2 { background-position: -300px -640px; }
+.emoji-mountain_bicyclist_tone3 { background-position: -320px -640px; }
+.emoji-mountain_bicyclist_tone4 { background-position: -340px -640px; }
+.emoji-mountain_bicyclist_tone5 { background-position: -360px -640px; }
+.emoji-mountain_cableway { background-position: -380px -640px; }
+.emoji-mountain_railway { background-position: -400px -640px; }
+.emoji-mountain_snow { background-position: -420px -640px; }
+.emoji-mouse { background-position: -440px -640px; }
+.emoji-mouse2 { background-position: -460px -640px; }
+.emoji-mouse_three_button { background-position: -480px -640px; }
+.emoji-movie_camera { background-position: -500px -640px; }
+.emoji-moyai { background-position: -520px -640px; }
+.emoji-mrs_claus { background-position: -540px -640px; }
+.emoji-mrs_claus_tone1 { background-position: -560px -640px; }
+.emoji-mrs_claus_tone2 { background-position: -580px -640px; }
+.emoji-mrs_claus_tone3 { background-position: -600px -640px; }
+.emoji-mrs_claus_tone4 { background-position: -620px -640px; }
+.emoji-mrs_claus_tone5 { background-position: -640px -640px; }
+.emoji-muscle { background-position: -660px 0; }
+.emoji-muscle_tone1 { background-position: -660px -20px; }
+.emoji-muscle_tone2 { background-position: -660px -40px; }
+.emoji-muscle_tone3 { background-position: -660px -60px; }
+.emoji-muscle_tone4 { background-position: -660px -80px; }
+.emoji-muscle_tone5 { background-position: -660px -100px; }
+.emoji-mushroom { background-position: -660px -120px; }
+.emoji-musical_keyboard { background-position: -660px -140px; }
+.emoji-musical_note { background-position: -660px -160px; }
+.emoji-musical_score { background-position: -660px -180px; }
+.emoji-mute { background-position: -660px -200px; }
+.emoji-nail_care { background-position: -660px -220px; }
+.emoji-nail_care_tone1 { background-position: -660px -240px; }
+.emoji-nail_care_tone2 { background-position: -660px -260px; }
+.emoji-nail_care_tone3 { background-position: -660px -280px; }
+.emoji-nail_care_tone4 { background-position: -660px -300px; }
+.emoji-nail_care_tone5 { background-position: -660px -320px; }
+.emoji-name_badge { background-position: -660px -340px; }
+.emoji-nauseated_face { background-position: -660px -360px; }
+.emoji-necktie { background-position: -660px -380px; }
+.emoji-negative_squared_cross_mark { background-position: -660px -400px; }
+.emoji-nerd { background-position: -660px -420px; }
+.emoji-neutral_face { background-position: -660px -440px; }
+.emoji-new { background-position: -660px -460px; }
+.emoji-new_moon { background-position: -660px -480px; }
+.emoji-new_moon_with_face { background-position: -660px -500px; }
+.emoji-newspaper { background-position: -660px -520px; }
+.emoji-newspaper2 { background-position: -660px -540px; }
+.emoji-ng { background-position: -660px -560px; }
+.emoji-night_with_stars { background-position: -660px -580px; }
+.emoji-nine { background-position: -660px -600px; }
+.emoji-no_bell { background-position: -660px -620px; }
+.emoji-no_bicycles { background-position: -660px -640px; }
+.emoji-no_entry { background-position: 0 -660px; }
+.emoji-no_entry_sign { background-position: -20px -660px; }
+.emoji-no_good { background-position: -40px -660px; }
+.emoji-no_good_tone1 { background-position: -60px -660px; }
+.emoji-no_good_tone2 { background-position: -80px -660px; }
+.emoji-no_good_tone3 { background-position: -100px -660px; }
+.emoji-no_good_tone4 { background-position: -120px -660px; }
+.emoji-no_good_tone5 { background-position: -140px -660px; }
+.emoji-no_mobile_phones { background-position: -160px -660px; }
+.emoji-no_mouth { background-position: -180px -660px; }
+.emoji-no_pedestrians { background-position: -200px -660px; }
+.emoji-no_smoking { background-position: -220px -660px; }
+.emoji-non-potable_water { background-position: -240px -660px; }
+.emoji-nose { background-position: -260px -660px; }
+.emoji-nose_tone1 { background-position: -280px -660px; }
+.emoji-nose_tone2 { background-position: -300px -660px; }
+.emoji-nose_tone3 { background-position: -320px -660px; }
+.emoji-nose_tone4 { background-position: -340px -660px; }
+.emoji-nose_tone5 { background-position: -360px -660px; }
+.emoji-notebook { background-position: -380px -660px; }
+.emoji-notebook_with_decorative_cover { background-position: -400px -660px; }
+.emoji-notepad_spiral { background-position: -420px -660px; }
+.emoji-notes { background-position: -440px -660px; }
+.emoji-nut_and_bolt { background-position: -460px -660px; }
+.emoji-o { background-position: -480px -660px; }
+.emoji-o2 { background-position: -500px -660px; }
+.emoji-ocean { background-position: -520px -660px; }
+.emoji-octagonal_sign { background-position: -540px -660px; }
+.emoji-octopus { background-position: -560px -660px; }
+.emoji-oden { background-position: -580px -660px; }
+.emoji-office { background-position: -600px -660px; }
+.emoji-oil { background-position: -620px -660px; }
+.emoji-ok { background-position: -640px -660px; }
+.emoji-ok_hand { background-position: -660px -660px; }
+.emoji-ok_hand_tone1 { background-position: -680px 0; }
+.emoji-ok_hand_tone2 { background-position: -680px -20px; }
+.emoji-ok_hand_tone3 { background-position: -680px -40px; }
+.emoji-ok_hand_tone4 { background-position: -680px -60px; }
+.emoji-ok_hand_tone5 { background-position: -680px -80px; }
+.emoji-ok_woman { background-position: -680px -100px; }
+.emoji-ok_woman_tone1 { background-position: -680px -120px; }
+.emoji-ok_woman_tone2 { background-position: -680px -140px; }
+.emoji-ok_woman_tone3 { background-position: -680px -160px; }
+.emoji-ok_woman_tone4 { background-position: -680px -180px; }
+.emoji-ok_woman_tone5 { background-position: -680px -200px; }
+.emoji-older_man { background-position: -680px -220px; }
+.emoji-older_man_tone1 { background-position: -680px -240px; }
+.emoji-older_man_tone2 { background-position: -680px -260px; }
+.emoji-older_man_tone3 { background-position: -680px -280px; }
+.emoji-older_man_tone4 { background-position: -680px -300px; }
+.emoji-older_man_tone5 { background-position: -680px -320px; }
+.emoji-older_woman { background-position: -680px -340px; }
+.emoji-older_woman_tone1 { background-position: -680px -360px; }
+.emoji-older_woman_tone2 { background-position: -680px -380px; }
+.emoji-older_woman_tone3 { background-position: -680px -400px; }
+.emoji-older_woman_tone4 { background-position: -680px -420px; }
+.emoji-older_woman_tone5 { background-position: -680px -440px; }
+.emoji-om_symbol { background-position: -680px -460px; }
+.emoji-on { background-position: -680px -480px; }
+.emoji-oncoming_automobile { background-position: -680px -500px; }
+.emoji-oncoming_bus { background-position: -680px -520px; }
+.emoji-oncoming_police_car { background-position: -680px -540px; }
+.emoji-oncoming_taxi { background-position: -680px -560px; }
+.emoji-one { background-position: -680px -580px; }
+.emoji-open_file_folder { background-position: -680px -600px; }
+.emoji-open_hands { background-position: -680px -620px; }
+.emoji-open_hands_tone1 { background-position: -680px -640px; }
+.emoji-open_hands_tone2 { background-position: -680px -660px; }
+.emoji-open_hands_tone3 { background-position: 0 -680px; }
+.emoji-open_hands_tone4 { background-position: -20px -680px; }
+.emoji-open_hands_tone5 { background-position: -40px -680px; }
+.emoji-open_mouth { background-position: -60px -680px; }
+.emoji-ophiuchus { background-position: -80px -680px; }
+.emoji-orange_book { background-position: -100px -680px; }
+.emoji-orthodox_cross { background-position: -120px -680px; }
+.emoji-outbox_tray { background-position: -140px -680px; }
+.emoji-owl { background-position: -160px -680px; }
+.emoji-ox { background-position: -180px -680px; }
+.emoji-package { background-position: -200px -680px; }
+.emoji-page_facing_up { background-position: -220px -680px; }
+.emoji-page_with_curl { background-position: -240px -680px; }
+.emoji-pager { background-position: -260px -680px; }
+.emoji-paintbrush { background-position: -280px -680px; }
+.emoji-palm_tree { background-position: -300px -680px; }
+.emoji-pancakes { background-position: -320px -680px; }
+.emoji-panda_face { background-position: -340px -680px; }
+.emoji-paperclip { background-position: -360px -680px; }
+.emoji-paperclips { background-position: -380px -680px; }
+.emoji-park { background-position: -400px -680px; }
+.emoji-parking { background-position: -420px -680px; }
+.emoji-part_alternation_mark { background-position: -440px -680px; }
+.emoji-partly_sunny { background-position: -460px -680px; }
+.emoji-passport_control { background-position: -480px -680px; }
+.emoji-pause_button { background-position: -500px -680px; }
+.emoji-peace { background-position: -520px -680px; }
+.emoji-peach { background-position: -540px -680px; }
+.emoji-peanuts { background-position: -560px -680px; }
+.emoji-pear { background-position: -580px -680px; }
+.emoji-pen_ballpoint { background-position: -600px -680px; }
+.emoji-pen_fountain { background-position: -620px -680px; }
+.emoji-pencil { background-position: -640px -680px; }
+.emoji-pencil2 { background-position: -660px -680px; }
+.emoji-penguin { background-position: -680px -680px; }
+.emoji-pensive { background-position: -700px 0; }
+.emoji-performing_arts { background-position: -700px -20px; }
+.emoji-persevere { background-position: -700px -40px; }
+.emoji-person_frowning { background-position: -700px -60px; }
+.emoji-person_frowning_tone1 { background-position: -700px -80px; }
+.emoji-person_frowning_tone2 { background-position: -700px -100px; }
+.emoji-person_frowning_tone3 { background-position: -700px -120px; }
+.emoji-person_frowning_tone4 { background-position: -700px -140px; }
+.emoji-person_frowning_tone5 { background-position: -700px -160px; }
+.emoji-person_with_blond_hair { background-position: -700px -180px; }
+.emoji-person_with_blond_hair_tone1 { background-position: -700px -200px; }
+.emoji-person_with_blond_hair_tone2 { background-position: -700px -220px; }
+.emoji-person_with_blond_hair_tone3 { background-position: -700px -240px; }
+.emoji-person_with_blond_hair_tone4 { background-position: -700px -260px; }
+.emoji-person_with_blond_hair_tone5 { background-position: -700px -280px; }
+.emoji-person_with_pouting_face { background-position: -700px -300px; }
+.emoji-person_with_pouting_face_tone1 { background-position: -700px -320px; }
+.emoji-person_with_pouting_face_tone2 { background-position: -700px -340px; }
+.emoji-person_with_pouting_face_tone3 { background-position: -700px -360px; }
+.emoji-person_with_pouting_face_tone4 { background-position: -700px -380px; }
+.emoji-person_with_pouting_face_tone5 { background-position: -700px -400px; }
+.emoji-pick { background-position: -700px -420px; }
+.emoji-pig { background-position: -700px -440px; }
+.emoji-pig2 { background-position: -700px -460px; }
+.emoji-pig_nose { background-position: -700px -480px; }
+.emoji-pill { background-position: -700px -500px; }
+.emoji-pineapple { background-position: -700px -520px; }
+.emoji-ping_pong { background-position: -700px -540px; }
+.emoji-pisces { background-position: -700px -560px; }
+.emoji-pizza { background-position: -700px -580px; }
+.emoji-place_of_worship { background-position: -700px -600px; }
+.emoji-play_pause { background-position: -700px -620px; }
+.emoji-point_down { background-position: -700px -640px; }
+.emoji-point_down_tone1 { background-position: -700px -660px; }
+.emoji-point_down_tone2 { background-position: -700px -680px; }
+.emoji-point_down_tone3 { background-position: 0 -700px; }
+.emoji-point_down_tone4 { background-position: -20px -700px; }
+.emoji-point_down_tone5 { background-position: -40px -700px; }
+.emoji-point_left { background-position: -60px -700px; }
+.emoji-point_left_tone1 { background-position: -80px -700px; }
+.emoji-point_left_tone2 { background-position: -100px -700px; }
+.emoji-point_left_tone3 { background-position: -120px -700px; }
+.emoji-point_left_tone4 { background-position: -140px -700px; }
+.emoji-point_left_tone5 { background-position: -160px -700px; }
+.emoji-point_right { background-position: -180px -700px; }
+.emoji-point_right_tone1 { background-position: -200px -700px; }
+.emoji-point_right_tone2 { background-position: -220px -700px; }
+.emoji-point_right_tone3 { background-position: -240px -700px; }
+.emoji-point_right_tone4 { background-position: -260px -700px; }
+.emoji-point_right_tone5 { background-position: -280px -700px; }
+.emoji-point_up { background-position: -300px -700px; }
+.emoji-point_up_2 { background-position: -320px -700px; }
+.emoji-point_up_2_tone1 { background-position: -340px -700px; }
+.emoji-point_up_2_tone2 { background-position: -360px -700px; }
+.emoji-point_up_2_tone3 { background-position: -380px -700px; }
+.emoji-point_up_2_tone4 { background-position: -400px -700px; }
+.emoji-point_up_2_tone5 { background-position: -420px -700px; }
+.emoji-point_up_tone1 { background-position: -440px -700px; }
+.emoji-point_up_tone2 { background-position: -460px -700px; }
+.emoji-point_up_tone3 { background-position: -480px -700px; }
+.emoji-point_up_tone4 { background-position: -500px -700px; }
+.emoji-point_up_tone5 { background-position: -520px -700px; }
+.emoji-police_car { background-position: -540px -700px; }
+.emoji-poodle { background-position: -560px -700px; }
+.emoji-poop { background-position: -580px -700px; }
+.emoji-popcorn { background-position: -600px -700px; }
+.emoji-post_office { background-position: -620px -700px; }
+.emoji-postal_horn { background-position: -640px -700px; }
+.emoji-postbox { background-position: -660px -700px; }
+.emoji-potable_water { background-position: -680px -700px; }
+.emoji-potato { background-position: -700px -700px; }
+.emoji-pouch { background-position: -720px 0; }
+.emoji-poultry_leg { background-position: -720px -20px; }
+.emoji-pound { background-position: -720px -40px; }
+.emoji-pouting_cat { background-position: -720px -60px; }
+.emoji-pray { background-position: -720px -80px; }
+.emoji-pray_tone1 { background-position: -720px -100px; }
+.emoji-pray_tone2 { background-position: -720px -120px; }
+.emoji-pray_tone3 { background-position: -720px -140px; }
+.emoji-pray_tone4 { background-position: -720px -160px; }
+.emoji-pray_tone5 { background-position: -720px -180px; }
+.emoji-prayer_beads { background-position: -720px -200px; }
+.emoji-pregnant_woman { background-position: -720px -220px; }
+.emoji-pregnant_woman_tone1 { background-position: -720px -240px; }
+.emoji-pregnant_woman_tone2 { background-position: -720px -260px; }
+.emoji-pregnant_woman_tone3 { background-position: -720px -280px; }
+.emoji-pregnant_woman_tone4 { background-position: -720px -300px; }
+.emoji-pregnant_woman_tone5 { background-position: -720px -320px; }
+.emoji-prince { background-position: -720px -340px; }
+.emoji-prince_tone1 { background-position: -720px -360px; }
+.emoji-prince_tone2 { background-position: -720px -380px; }
+.emoji-prince_tone3 { background-position: -720px -400px; }
+.emoji-prince_tone4 { background-position: -720px -420px; }
+.emoji-prince_tone5 { background-position: -720px -440px; }
+.emoji-princess { background-position: -720px -460px; }
+.emoji-princess_tone1 { background-position: -720px -480px; }
+.emoji-princess_tone2 { background-position: -720px -500px; }
+.emoji-princess_tone3 { background-position: -720px -520px; }
+.emoji-princess_tone4 { background-position: -720px -540px; }
+.emoji-princess_tone5 { background-position: -720px -560px; }
+.emoji-printer { background-position: -720px -580px; }
+.emoji-projector { background-position: -720px -600px; }
+.emoji-punch { background-position: -720px -620px; }
+.emoji-punch_tone1 { background-position: -720px -640px; }
+.emoji-punch_tone2 { background-position: -720px -660px; }
+.emoji-punch_tone3 { background-position: -720px -680px; }
+.emoji-punch_tone4 { background-position: -720px -700px; }
+.emoji-punch_tone5 { background-position: 0 -720px; }
+.emoji-purple_heart { background-position: -20px -720px; }
+.emoji-purse { background-position: -40px -720px; }
+.emoji-pushpin { background-position: -60px -720px; }
+.emoji-put_litter_in_its_place { background-position: -80px -720px; }
+.emoji-question { background-position: -100px -720px; }
+.emoji-rabbit { background-position: -120px -720px; }
+.emoji-rabbit2 { background-position: -140px -720px; }
+.emoji-race_car { background-position: -160px -720px; }
+.emoji-racehorse { background-position: -180px -720px; }
+.emoji-radio { background-position: -200px -720px; }
+.emoji-radio_button { background-position: -220px -720px; }
+.emoji-radioactive { background-position: -240px -720px; }
+.emoji-rage { background-position: -260px -720px; }
+.emoji-railway_car { background-position: -280px -720px; }
+.emoji-railway_track { background-position: -300px -720px; }
+.emoji-rainbow { background-position: -320px -720px; }
+.emoji-raised_back_of_hand { background-position: -340px -720px; }
+.emoji-raised_back_of_hand_tone1 { background-position: -360px -720px; }
+.emoji-raised_back_of_hand_tone2 { background-position: -380px -720px; }
+.emoji-raised_back_of_hand_tone3 { background-position: -400px -720px; }
+.emoji-raised_back_of_hand_tone4 { background-position: -420px -720px; }
+.emoji-raised_back_of_hand_tone5 { background-position: -440px -720px; }
+.emoji-raised_hand { background-position: -460px -720px; }
+.emoji-raised_hand_tone1 { background-position: -480px -720px; }
+.emoji-raised_hand_tone2 { background-position: -500px -720px; }
+.emoji-raised_hand_tone3 { background-position: -520px -720px; }
+.emoji-raised_hand_tone4 { background-position: -540px -720px; }
+.emoji-raised_hand_tone5 { background-position: -560px -720px; }
+.emoji-raised_hands { background-position: -580px -720px; }
+.emoji-raised_hands_tone1 { background-position: -600px -720px; }
+.emoji-raised_hands_tone2 { background-position: -620px -720px; }
+.emoji-raised_hands_tone3 { background-position: -640px -720px; }
+.emoji-raised_hands_tone4 { background-position: -660px -720px; }
+.emoji-raised_hands_tone5 { background-position: -680px -720px; }
+.emoji-raising_hand { background-position: -700px -720px; }
+.emoji-raising_hand_tone1 { background-position: -720px -720px; }
+.emoji-raising_hand_tone2 { background-position: -740px 0; }
+.emoji-raising_hand_tone3 { background-position: -740px -20px; }
+.emoji-raising_hand_tone4 { background-position: -740px -40px; }
+.emoji-raising_hand_tone5 { background-position: -740px -60px; }
+.emoji-ram { background-position: -740px -80px; }
+.emoji-ramen { background-position: -740px -100px; }
+.emoji-rat { background-position: -740px -120px; }
+.emoji-record_button { background-position: -740px -140px; }
+.emoji-recycle { background-position: -740px -160px; }
+.emoji-red_car { background-position: -740px -180px; }
+.emoji-red_circle { background-position: -740px -200px; }
+.emoji-registered { background-position: -740px -220px; }
+.emoji-relaxed { background-position: -740px -240px; }
+.emoji-relieved { background-position: -740px -260px; }
+.emoji-reminder_ribbon { background-position: -740px -280px; }
+.emoji-repeat { background-position: -740px -300px; }
+.emoji-repeat_one { background-position: -740px -320px; }
+.emoji-restroom { background-position: -740px -340px; }
+.emoji-revolving_hearts { background-position: -740px -360px; }
+.emoji-rewind { background-position: -740px -380px; }
+.emoji-rhino { background-position: -740px -400px; }
+.emoji-ribbon { background-position: -740px -420px; }
+.emoji-rice { background-position: -740px -440px; }
+.emoji-rice_ball { background-position: -740px -460px; }
+.emoji-rice_cracker { background-position: -740px -480px; }
+.emoji-rice_scene { background-position: -740px -500px; }
+.emoji-right_facing_fist { background-position: -740px -520px; }
+.emoji-right_facing_fist_tone1 { background-position: -740px -540px; }
+.emoji-right_facing_fist_tone2 { background-position: -740px -560px; }
+.emoji-right_facing_fist_tone3 { background-position: -740px -580px; }
+.emoji-right_facing_fist_tone4 { background-position: -740px -600px; }
+.emoji-right_facing_fist_tone5 { background-position: -740px -620px; }
+.emoji-ring { background-position: -740px -640px; }
+.emoji-robot { background-position: -740px -660px; }
+.emoji-rocket { background-position: -740px -680px; }
+.emoji-rofl { background-position: -740px -700px; }
+.emoji-roller_coaster { background-position: -740px -720px; }
+.emoji-rolling_eyes { background-position: 0 -740px; }
+.emoji-rooster { background-position: -20px -740px; }
+.emoji-rose { background-position: -40px -740px; }
+.emoji-rosette { background-position: -60px -740px; }
+.emoji-rotating_light { background-position: -80px -740px; }
+.emoji-round_pushpin { background-position: -100px -740px; }
+.emoji-rowboat { background-position: -120px -740px; }
+.emoji-rowboat_tone1 { background-position: -140px -740px; }
+.emoji-rowboat_tone2 { background-position: -160px -740px; }
+.emoji-rowboat_tone3 { background-position: -180px -740px; }
+.emoji-rowboat_tone4 { background-position: -200px -740px; }
+.emoji-rowboat_tone5 { background-position: -220px -740px; }
+.emoji-rugby_football { background-position: -240px -740px; }
+.emoji-runner { background-position: -260px -740px; }
+.emoji-runner_tone1 { background-position: -280px -740px; }
+.emoji-runner_tone2 { background-position: -300px -740px; }
+.emoji-runner_tone3 { background-position: -320px -740px; }
+.emoji-runner_tone4 { background-position: -340px -740px; }
+.emoji-runner_tone5 { background-position: -360px -740px; }
+.emoji-running_shirt_with_sash { background-position: -380px -740px; }
+.emoji-sa { background-position: -400px -740px; }
+.emoji-sagittarius { background-position: -420px -740px; }
+.emoji-sailboat { background-position: -440px -740px; }
+.emoji-sake { background-position: -460px -740px; }
+.emoji-salad { background-position: -480px -740px; }
+.emoji-sandal { background-position: -500px -740px; }
+.emoji-santa { background-position: -520px -740px; }
+.emoji-santa_tone1 { background-position: -540px -740px; }
+.emoji-santa_tone2 { background-position: -560px -740px; }
+.emoji-santa_tone3 { background-position: -580px -740px; }
+.emoji-santa_tone4 { background-position: -600px -740px; }
+.emoji-santa_tone5 { background-position: -620px -740px; }
+.emoji-satellite { background-position: -640px -740px; }
+.emoji-satellite_orbital { background-position: -660px -740px; }
+.emoji-saxophone { background-position: -680px -740px; }
+.emoji-scales { background-position: -700px -740px; }
+.emoji-school { background-position: -720px -740px; }
+.emoji-school_satchel { background-position: -740px -740px; }
+.emoji-scissors { background-position: -760px 0; }
+.emoji-scooter { background-position: -760px -20px; }
+.emoji-scorpion { background-position: -760px -40px; }
+.emoji-scorpius { background-position: -760px -60px; }
+.emoji-scream { background-position: -760px -80px; }
+.emoji-scream_cat { background-position: -760px -100px; }
+.emoji-scroll { background-position: -760px -120px; }
+.emoji-seat { background-position: -760px -140px; }
+.emoji-second_place { background-position: -760px -160px; }
+.emoji-secret { background-position: -760px -180px; }
+.emoji-see_no_evil { background-position: -760px -200px; }
+.emoji-seedling { background-position: -760px -220px; }
+.emoji-selfie { background-position: -760px -240px; }
+.emoji-selfie_tone1 { background-position: -760px -260px; }
+.emoji-selfie_tone2 { background-position: -760px -280px; }
+.emoji-selfie_tone3 { background-position: -760px -300px; }
+.emoji-selfie_tone4 { background-position: -760px -320px; }
+.emoji-selfie_tone5 { background-position: -760px -340px; }
+.emoji-seven { background-position: -760px -360px; }
+.emoji-shallow_pan_of_food { background-position: -760px -380px; }
+.emoji-shamrock { background-position: -760px -400px; }
+.emoji-shark { background-position: -760px -420px; }
+.emoji-shaved_ice { background-position: -760px -440px; }
+.emoji-sheep { background-position: -760px -460px; }
+.emoji-shell { background-position: -760px -480px; }
+.emoji-shield { background-position: -760px -500px; }
+.emoji-shinto_shrine { background-position: -760px -520px; }
+.emoji-ship { background-position: -760px -540px; }
+.emoji-shirt { background-position: -760px -560px; }
+.emoji-shopping_bags { background-position: -760px -580px; }
+.emoji-shopping_cart { background-position: -760px -600px; }
+.emoji-shower { background-position: -760px -620px; }
+.emoji-shrimp { background-position: -760px -640px; }
+.emoji-shrug { background-position: -760px -660px; }
+.emoji-shrug_tone1 { background-position: -760px -680px; }
+.emoji-shrug_tone2 { background-position: -760px -700px; }
+.emoji-shrug_tone3 { background-position: -760px -720px; }
+.emoji-shrug_tone4 { background-position: -760px -740px; }
+.emoji-shrug_tone5 { background-position: 0 -760px; }
+.emoji-signal_strength { background-position: -20px -760px; }
+.emoji-six { background-position: -40px -760px; }
+.emoji-six_pointed_star { background-position: -60px -760px; }
+.emoji-ski { background-position: -80px -760px; }
+.emoji-skier { background-position: -100px -760px; }
+.emoji-skull { background-position: -120px -760px; }
+.emoji-skull_crossbones { background-position: -140px -760px; }
+.emoji-sleeping { background-position: -160px -760px; }
+.emoji-sleeping_accommodation { background-position: -180px -760px; }
+.emoji-sleepy { background-position: -200px -760px; }
+.emoji-slight_frown { background-position: -220px -760px; }
+.emoji-slight_smile { background-position: -240px -760px; }
+.emoji-slot_machine { background-position: -260px -760px; }
+.emoji-small_blue_diamond { background-position: -280px -760px; }
+.emoji-small_orange_diamond { background-position: -300px -760px; }
+.emoji-small_red_triangle { background-position: -320px -760px; }
+.emoji-small_red_triangle_down { background-position: -340px -760px; }
+.emoji-smile { background-position: -360px -760px; }
+.emoji-smile_cat { background-position: -380px -760px; }
+.emoji-smiley { background-position: -400px -760px; }
+.emoji-smiley_cat { background-position: -420px -760px; }
+.emoji-smiling_imp { background-position: -440px -760px; }
+.emoji-smirk { background-position: -460px -760px; }
+.emoji-smirk_cat { background-position: -480px -760px; }
+.emoji-smoking { background-position: -500px -760px; }
+.emoji-snail { background-position: -520px -760px; }
+.emoji-snake { background-position: -540px -760px; }
+.emoji-sneezing_face { background-position: -560px -760px; }
+.emoji-snowboarder { background-position: -580px -760px; }
+.emoji-snowflake { background-position: -600px -760px; }
+.emoji-snowman { background-position: -620px -760px; }
+.emoji-snowman2 { background-position: -640px -760px; }
+.emoji-sob { background-position: -660px -760px; }
+.emoji-soccer { background-position: -680px -760px; }
+.emoji-soon { background-position: -700px -760px; }
+.emoji-sos { background-position: -720px -760px; }
+.emoji-sound { background-position: -740px -760px; }
+.emoji-space_invader { background-position: -760px -760px; }
+.emoji-spades { background-position: -780px 0; }
+.emoji-spaghetti { background-position: -780px -20px; }
+.emoji-sparkle { background-position: -780px -40px; }
+.emoji-sparkler { background-position: -780px -60px; }
+.emoji-sparkles { background-position: -780px -80px; }
+.emoji-sparkling_heart { background-position: -780px -100px; }
+.emoji-speak_no_evil { background-position: -780px -120px; }
+.emoji-speaker { background-position: -780px -140px; }
+.emoji-speaking_head { background-position: -780px -160px; }
+.emoji-speech_balloon { background-position: -780px -180px; }
+.emoji-speedboat { background-position: -780px -200px; }
+.emoji-spider { background-position: -780px -220px; }
+.emoji-spider_web { background-position: -780px -240px; }
+.emoji-spoon { background-position: -780px -260px; }
+.emoji-spy { background-position: -780px -280px; }
+.emoji-spy_tone1 { background-position: -780px -300px; }
+.emoji-spy_tone2 { background-position: -780px -320px; }
+.emoji-spy_tone3 { background-position: -780px -340px; }
+.emoji-spy_tone4 { background-position: -780px -360px; }
+.emoji-spy_tone5 { background-position: -780px -380px; }
+.emoji-squid { background-position: -780px -400px; }
+.emoji-stadium { background-position: -780px -420px; }
+.emoji-star { background-position: -780px -440px; }
+.emoji-star2 { background-position: -780px -460px; }
+.emoji-star_and_crescent { background-position: -780px -480px; }
+.emoji-star_of_david { background-position: -780px -500px; }
+.emoji-stars { background-position: -780px -520px; }
+.emoji-station { background-position: -780px -540px; }
+.emoji-statue_of_liberty { background-position: -780px -560px; }
+.emoji-steam_locomotive { background-position: -780px -580px; }
+.emoji-stew { background-position: -780px -600px; }
+.emoji-stop_button { background-position: -780px -620px; }
+.emoji-stopwatch { background-position: -780px -640px; }
+.emoji-straight_ruler { background-position: -780px -660px; }
+.emoji-strawberry { background-position: -780px -680px; }
+.emoji-stuck_out_tongue { background-position: -780px -700px; }
+.emoji-stuck_out_tongue_closed_eyes { background-position: -780px -720px; }
+.emoji-stuck_out_tongue_winking_eye { background-position: -780px -740px; }
+.emoji-stuffed_flatbread { background-position: -780px -760px; }
+.emoji-sun_with_face { background-position: 0 -780px; }
+.emoji-sunflower { background-position: -20px -780px; }
+.emoji-sunglasses { background-position: -40px -780px; }
+.emoji-sunny { background-position: -60px -780px; }
+.emoji-sunrise { background-position: -80px -780px; }
+.emoji-sunrise_over_mountains { background-position: -100px -780px; }
+.emoji-surfer { background-position: -120px -780px; }
+.emoji-surfer_tone1 { background-position: -140px -780px; }
+.emoji-surfer_tone2 { background-position: -160px -780px; }
+.emoji-surfer_tone3 { background-position: -180px -780px; }
+.emoji-surfer_tone4 { background-position: -200px -780px; }
+.emoji-surfer_tone5 { background-position: -220px -780px; }
+.emoji-sushi { background-position: -240px -780px; }
+.emoji-suspension_railway { background-position: -260px -780px; }
+.emoji-sweat { background-position: -280px -780px; }
+.emoji-sweat_drops { background-position: -300px -780px; }
+.emoji-sweat_smile { background-position: -320px -780px; }
+.emoji-sweet_potato { background-position: -340px -780px; }
+.emoji-swimmer { background-position: -360px -780px; }
+.emoji-swimmer_tone1 { background-position: -380px -780px; }
+.emoji-swimmer_tone2 { background-position: -400px -780px; }
+.emoji-swimmer_tone3 { background-position: -420px -780px; }
+.emoji-swimmer_tone4 { background-position: -440px -780px; }
+.emoji-swimmer_tone5 { background-position: -460px -780px; }
+.emoji-symbols { background-position: -480px -780px; }
+.emoji-synagogue { background-position: -500px -780px; }
+.emoji-syringe { background-position: -520px -780px; }
+.emoji-taco { background-position: -540px -780px; }
+.emoji-tada { background-position: -560px -780px; }
+.emoji-tanabata_tree { background-position: -580px -780px; }
+.emoji-tangerine { background-position: -600px -780px; }
+.emoji-taurus { background-position: -620px -780px; }
+.emoji-taxi { background-position: -640px -780px; }
+.emoji-tea { background-position: -660px -780px; }
+.emoji-telephone { background-position: -680px -780px; }
+.emoji-telephone_receiver { background-position: -700px -780px; }
+.emoji-telescope { background-position: -720px -780px; }
+.emoji-ten { background-position: -740px -780px; }
+.emoji-tennis { background-position: -760px -780px; }
+.emoji-tent { background-position: -780px -780px; }
+.emoji-thermometer { background-position: -800px 0; }
+.emoji-thermometer_face { background-position: -800px -20px; }
+.emoji-thinking { background-position: -800px -40px; }
+.emoji-third_place { background-position: -800px -60px; }
+.emoji-thought_balloon { background-position: -800px -80px; }
+.emoji-three { background-position: -800px -100px; }
+.emoji-thumbsdown { background-position: -800px -120px; }
+.emoji-thumbsdown_tone1 { background-position: -800px -140px; }
+.emoji-thumbsdown_tone2 { background-position: -800px -160px; }
+.emoji-thumbsdown_tone3 { background-position: -800px -180px; }
+.emoji-thumbsdown_tone4 { background-position: -800px -200px; }
+.emoji-thumbsdown_tone5 { background-position: -800px -220px; }
+.emoji-thumbsup { background-position: -800px -240px; }
+.emoji-thumbsup_tone1 { background-position: -800px -260px; }
+.emoji-thumbsup_tone2 { background-position: -800px -280px; }
+.emoji-thumbsup_tone3 { background-position: -800px -300px; }
+.emoji-thumbsup_tone4 { background-position: -800px -320px; }
+.emoji-thumbsup_tone5 { background-position: -800px -340px; }
+.emoji-thunder_cloud_rain { background-position: -800px -360px; }
+.emoji-ticket { background-position: -800px -380px; }
+.emoji-tickets { background-position: -800px -400px; }
+.emoji-tiger { background-position: -800px -420px; }
+.emoji-tiger2 { background-position: -800px -440px; }
+.emoji-timer { background-position: -800px -460px; }
+.emoji-tired_face { background-position: -800px -480px; }
+.emoji-tm { background-position: -800px -500px; }
+.emoji-toilet { background-position: -800px -520px; }
+.emoji-tokyo_tower { background-position: -800px -540px; }
+.emoji-tomato { background-position: -800px -560px; }
+.emoji-tone1 { background-position: -800px -580px; }
+.emoji-tone2 { background-position: -800px -600px; }
+.emoji-tone3 { background-position: -800px -620px; }
+.emoji-tone4 { background-position: -800px -640px; }
+.emoji-tone5 { background-position: -800px -660px; }
+.emoji-tongue { background-position: -800px -680px; }
+.emoji-tools { background-position: -800px -700px; }
+.emoji-top { background-position: -800px -720px; }
+.emoji-tophat { background-position: -800px -740px; }
+.emoji-track_next { background-position: -800px -760px; }
+.emoji-track_previous { background-position: -800px -780px; }
+.emoji-trackball { background-position: 0 -800px; }
+.emoji-tractor { background-position: -20px -800px; }
+.emoji-traffic_light { background-position: -40px -800px; }
+.emoji-train { background-position: -60px -800px; }
+.emoji-train2 { background-position: -80px -800px; }
+.emoji-tram { background-position: -100px -800px; }
+.emoji-triangular_flag_on_post { background-position: -120px -800px; }
+.emoji-triangular_ruler { background-position: -140px -800px; }
+.emoji-trident { background-position: -160px -800px; }
+.emoji-triumph { background-position: -180px -800px; }
+.emoji-trolleybus { background-position: -200px -800px; }
+.emoji-trophy { background-position: -220px -800px; }
+.emoji-tropical_drink { background-position: -240px -800px; }
+.emoji-tropical_fish { background-position: -260px -800px; }
+.emoji-truck { background-position: -280px -800px; }
+.emoji-trumpet { background-position: -300px -800px; }
+.emoji-tulip { background-position: -320px -800px; }
+.emoji-tumbler_glass { background-position: -340px -800px; }
+.emoji-turkey { background-position: -360px -800px; }
+.emoji-turtle { background-position: -380px -800px; }
+.emoji-tv { background-position: -400px -800px; }
+.emoji-twisted_rightwards_arrows { background-position: -420px -800px; }
+.emoji-two { background-position: -440px -800px; }
+.emoji-two_hearts { background-position: -460px -800px; }
+.emoji-two_men_holding_hands { background-position: -480px -800px; }
+.emoji-two_women_holding_hands { background-position: -500px -800px; }
+.emoji-u5272 { background-position: -520px -800px; }
+.emoji-u5408 { background-position: -540px -800px; }
+.emoji-u55b6 { background-position: -560px -800px; }
+.emoji-u6307 { background-position: -580px -800px; }
+.emoji-u6708 { background-position: -600px -800px; }
+.emoji-u6709 { background-position: -620px -800px; }
+.emoji-u6e80 { background-position: -640px -800px; }
+.emoji-u7121 { background-position: -660px -800px; }
+.emoji-u7533 { background-position: -680px -800px; }
+.emoji-u7981 { background-position: -700px -800px; }
+.emoji-u7a7a { background-position: -720px -800px; }
+.emoji-umbrella { background-position: -740px -800px; }
+.emoji-umbrella2 { background-position: -760px -800px; }
+.emoji-unamused { background-position: -780px -800px; }
+.emoji-underage { background-position: -800px -800px; }
+.emoji-unicorn { background-position: -820px 0; }
+.emoji-unlock { background-position: -820px -20px; }
+.emoji-up { background-position: -820px -40px; }
+.emoji-upside_down { background-position: -820px -60px; }
+.emoji-urn { background-position: -820px -80px; }
+.emoji-v { background-position: -820px -100px; }
+.emoji-v_tone1 { background-position: -820px -120px; }
+.emoji-v_tone2 { background-position: -820px -140px; }
+.emoji-v_tone3 { background-position: -820px -160px; }
+.emoji-v_tone4 { background-position: -820px -180px; }
+.emoji-v_tone5 { background-position: -820px -200px; }
+.emoji-vertical_traffic_light { background-position: -820px -220px; }
+.emoji-vhs { background-position: -820px -240px; }
+.emoji-vibration_mode { background-position: -820px -260px; }
+.emoji-video_camera { background-position: -820px -280px; }
+.emoji-video_game { background-position: -820px -300px; }
+.emoji-violin { background-position: -820px -320px; }
+.emoji-virgo { background-position: -820px -340px; }
+.emoji-volcano { background-position: -820px -360px; }
+.emoji-volleyball { background-position: -820px -380px; }
+.emoji-vs { background-position: -820px -400px; }
+.emoji-vulcan { background-position: -820px -420px; }
+.emoji-vulcan_tone1 { background-position: -820px -440px; }
+.emoji-vulcan_tone2 { background-position: -820px -460px; }
+.emoji-vulcan_tone3 { background-position: -820px -480px; }
+.emoji-vulcan_tone4 { background-position: -820px -500px; }
+.emoji-vulcan_tone5 { background-position: -820px -520px; }
+.emoji-walking { background-position: -820px -540px; }
+.emoji-walking_tone1 { background-position: -820px -560px; }
+.emoji-walking_tone2 { background-position: -820px -580px; }
+.emoji-walking_tone3 { background-position: -820px -600px; }
+.emoji-walking_tone4 { background-position: -820px -620px; }
+.emoji-walking_tone5 { background-position: -820px -640px; }
+.emoji-waning_crescent_moon { background-position: -820px -660px; }
+.emoji-waning_gibbous_moon { background-position: -820px -680px; }
+.emoji-warning { background-position: -820px -700px; }
+.emoji-wastebasket { background-position: -820px -720px; }
+.emoji-watch { background-position: -820px -740px; }
+.emoji-water_buffalo { background-position: -820px -760px; }
+.emoji-water_polo { background-position: -820px -780px; }
+.emoji-water_polo_tone1 { background-position: -820px -800px; }
+.emoji-water_polo_tone2 { background-position: 0 -820px; }
+.emoji-water_polo_tone3 { background-position: -20px -820px; }
+.emoji-water_polo_tone4 { background-position: -40px -820px; }
+.emoji-water_polo_tone5 { background-position: -60px -820px; }
+.emoji-watermelon { background-position: -80px -820px; }
+.emoji-wave { background-position: -100px -820px; }
+.emoji-wave_tone1 { background-position: -120px -820px; }
+.emoji-wave_tone2 { background-position: -140px -820px; }
+.emoji-wave_tone3 { background-position: -160px -820px; }
+.emoji-wave_tone4 { background-position: -180px -820px; }
+.emoji-wave_tone5 { background-position: -200px -820px; }
+.emoji-wavy_dash { background-position: -220px -820px; }
+.emoji-waxing_crescent_moon { background-position: -240px -820px; }
+.emoji-waxing_gibbous_moon { background-position: -260px -820px; }
+.emoji-wc { background-position: -280px -820px; }
+.emoji-weary { background-position: -300px -820px; }
+.emoji-wedding { background-position: -320px -820px; }
+.emoji-whale { background-position: -340px -820px; }
+.emoji-whale2 { background-position: -360px -820px; }
+.emoji-wheel_of_dharma { background-position: -380px -820px; }
+.emoji-wheelchair { background-position: -400px -820px; }
+.emoji-white_check_mark { background-position: -420px -820px; }
+.emoji-white_circle { background-position: -440px -820px; }
+.emoji-white_flower { background-position: -460px -820px; }
+.emoji-white_large_square { background-position: -480px -820px; }
+.emoji-white_medium_small_square { background-position: -500px -820px; }
+.emoji-white_medium_square { background-position: -520px -820px; }
+.emoji-white_small_square { background-position: -540px -820px; }
+.emoji-white_square_button { background-position: -560px -820px; }
+.emoji-white_sun_cloud { background-position: -580px -820px; }
+.emoji-white_sun_rain_cloud { background-position: -600px -820px; }
+.emoji-white_sun_small_cloud { background-position: -620px -820px; }
+.emoji-wilted_rose { background-position: -640px -820px; }
+.emoji-wind_blowing_face { background-position: -660px -820px; }
+.emoji-wind_chime { background-position: -680px -820px; }
+.emoji-wine_glass { background-position: -700px -820px; }
+.emoji-wink { background-position: -720px -820px; }
+.emoji-wolf { background-position: -740px -820px; }
+.emoji-woman { background-position: -760px -820px; }
+.emoji-woman_tone1 { background-position: -780px -820px; }
+.emoji-woman_tone2 { background-position: -800px -820px; }
+.emoji-woman_tone3 { background-position: -820px -820px; }
+.emoji-woman_tone4 { background-position: -840px 0; }
+.emoji-woman_tone5 { background-position: -840px -20px; }
+.emoji-womans_clothes { background-position: -840px -40px; }
+.emoji-womans_hat { background-position: -840px -60px; }
+.emoji-womens { background-position: -840px -80px; }
+.emoji-worried { background-position: -840px -100px; }
+.emoji-wrench { background-position: -840px -120px; }
+.emoji-wrestlers { background-position: -840px -140px; }
+.emoji-wrestlers_tone1 { background-position: -840px -160px; }
+.emoji-wrestlers_tone2 { background-position: -840px -180px; }
+.emoji-wrestlers_tone3 { background-position: -840px -200px; }
+.emoji-wrestlers_tone4 { background-position: -840px -220px; }
+.emoji-wrestlers_tone5 { background-position: -840px -240px; }
+.emoji-writing_hand { background-position: -840px -260px; }
+.emoji-writing_hand_tone1 { background-position: -840px -280px; }
+.emoji-writing_hand_tone2 { background-position: -840px -300px; }
+.emoji-writing_hand_tone3 { background-position: -840px -320px; }
+.emoji-writing_hand_tone4 { background-position: -840px -340px; }
+.emoji-writing_hand_tone5 { background-position: -840px -360px; }
+.emoji-x { background-position: -840px -380px; }
+.emoji-yellow_heart { background-position: -840px -400px; }
+.emoji-yen { background-position: -840px -420px; }
+.emoji-yin_yang { background-position: -840px -440px; }
+.emoji-yum { background-position: -840px -460px; }
+.emoji-zap { background-position: -840px -480px; }
+.emoji-zero { background-position: -840px -500px; }
+.emoji-zipper_mouth { background-position: -840px -520px; }
+.emoji-100 { background-position: -840px -540px; }
+
+.emoji-icon {
+ background-image: image-url('emoji.png');
+ background-repeat: no-repeat;
+ color: transparent;
+ text-indent: -99em;
+ height: 20px;
+ width: 20px;
+
+ @media only screen and (-webkit-min-device-pixel-ratio: 2),
+ only screen and (min--moz-device-pixel-ratio: 2),
+ only screen and (-o-min-device-pixel-ratio: 2/1),
+ only screen and (min-device-pixel-ratio: 2),
+ only screen and (min-resolution: 192dpi),
+ only screen and (min-resolution: 2dppx) {
+ background-image: image-url('emoji@2x.png');
+ background-size: 860px 840px;
+ }
+}
diff --git a/app/assets/stylesheets/framework/emojis.scss b/app/assets/stylesheets/framework/emojis.scss
index 7158de65143..0a8bc95590e 100644
--- a/app/assets/stylesheets/framework/emojis.scss
+++ b/app/assets/stylesheets/framework/emojis.scss
@@ -1,1809 +1,6 @@
-.emoji-0023-20E3 { background-position: 0 0; }
-.emoji-002A-20E3 { background-position: -20px 0; }
-.emoji-0030-20E3 { background-position: 0 -20px; }
-.emoji-0031-20E3 { background-position: -20px -20px; }
-.emoji-0032-20E3 { background-position: -40px 0; }
-.emoji-0033-20E3 { background-position: -40px -20px; }
-.emoji-0034-20E3 { background-position: 0 -40px; }
-.emoji-0035-20E3 { background-position: -20px -40px; }
-.emoji-0036-20E3 { background-position: -40px -40px; }
-.emoji-0037-20E3 { background-position: -60px 0; }
-.emoji-0038-20E3 { background-position: -60px -20px; }
-.emoji-0039-20E3 { background-position: -60px -40px; }
-.emoji-00A9 { background-position: 0 -60px; }
-.emoji-00AE { background-position: -20px -60px; }
-.emoji-1F004 { background-position: -40px -60px; }
-.emoji-1F0CF { background-position: -60px -60px; }
-.emoji-1F170 { background-position: -80px 0; }
-.emoji-1F171 { background-position: -80px -20px; }
-.emoji-1F17E { background-position: -80px -40px; }
-.emoji-1F17F { background-position: -80px -60px; }
-.emoji-1F18E { background-position: 0 -80px; }
-.emoji-1F191 { background-position: -20px -80px; }
-.emoji-1F192 { background-position: -40px -80px; }
-.emoji-1F193 { background-position: -60px -80px; }
-.emoji-1F194 { background-position: -80px -80px; }
-.emoji-1F195 { background-position: -100px 0; }
-.emoji-1F196 { background-position: -100px -20px; }
-.emoji-1F197 { background-position: -100px -40px; }
-.emoji-1F198 { background-position: -100px -60px; }
-.emoji-1F199 { background-position: -100px -80px; }
-.emoji-1F19A { background-position: 0 -100px; }
-.emoji-1F1E6-1F1E8 { background-position: -20px -100px; }
-.emoji-1F1E6-1F1E9 { background-position: -40px -100px; }
-.emoji-1F1E6-1F1EA { background-position: -60px -100px; }
-.emoji-1F1E6-1F1EB { background-position: -80px -100px; }
-.emoji-1F1E6-1F1EC { background-position: -100px -100px; }
-.emoji-1F1E6-1F1EE { background-position: -120px 0; }
-.emoji-1F1E6-1F1F1 { background-position: -120px -20px; }
-.emoji-1F1E6-1F1F2 { background-position: -120px -40px; }
-.emoji-1F1E6-1F1F4 { background-position: -120px -60px; }
-.emoji-1F1E6-1F1F6 { background-position: -120px -80px; }
-.emoji-1F1E6-1F1F7 { background-position: -120px -100px; }
-.emoji-1F1E6-1F1F8 { background-position: 0 -120px; }
-.emoji-1F1E6-1F1F9 { background-position: -20px -120px; }
-.emoji-1F1E6-1F1FA { background-position: -40px -120px; }
-.emoji-1F1E6-1F1FC { background-position: -60px -120px; }
-.emoji-1F1E6-1F1FD { background-position: -80px -120px; }
-.emoji-1F1E6-1F1FF { background-position: -100px -120px; }
-.emoji-1F1E7-1F1E6 { background-position: -120px -120px; }
-.emoji-1F1E7-1F1E7 { background-position: -140px 0; }
-.emoji-1F1E7-1F1E9 { background-position: -140px -20px; }
-.emoji-1F1E7-1F1EA { background-position: -140px -40px; }
-.emoji-1F1E7-1F1EB { background-position: -140px -60px; }
-.emoji-1F1E7-1F1EC { background-position: -140px -80px; }
-.emoji-1F1E7-1F1ED { background-position: -140px -100px; }
-.emoji-1F1E7-1F1EE { background-position: -140px -120px; }
-.emoji-1F1E7-1F1EF { background-position: 0 -140px; }
-.emoji-1F1E7-1F1F1 { background-position: -20px -140px; }
-.emoji-1F1E7-1F1F2 { background-position: -40px -140px; }
-.emoji-1F1E7-1F1F3 { background-position: -60px -140px; }
-.emoji-1F1E7-1F1F4 { background-position: -80px -140px; }
-.emoji-1F1E7-1F1F6 { background-position: -100px -140px; }
-.emoji-1F1E7-1F1F7 { background-position: -120px -140px; }
-.emoji-1F1E7-1F1F8 { background-position: -140px -140px; }
-.emoji-1F1E7-1F1F9 { background-position: -160px 0; }
-.emoji-1F1E7-1F1FB { background-position: -160px -20px; }
-.emoji-1F1E7-1F1FC { background-position: -160px -40px; }
-.emoji-1F1E7-1F1FE { background-position: -160px -60px; }
-.emoji-1F1E7-1F1FF { background-position: -160px -80px; }
-.emoji-1F1E8-1F1E6 { background-position: -160px -100px; }
-.emoji-1F1E8-1F1E8 { background-position: -160px -120px; }
-.emoji-1F1E8-1F1E9 { background-position: -160px -140px; }
-.emoji-1F1E8-1F1EB { background-position: 0 -160px; }
-.emoji-1F1E8-1F1EC { background-position: -20px -160px; }
-.emoji-1F1E8-1F1ED { background-position: -40px -160px; }
-.emoji-1F1E8-1F1EE { background-position: -60px -160px; }
-.emoji-1F1E8-1F1F0 { background-position: -80px -160px; }
-.emoji-1F1E8-1F1F1 { background-position: -100px -160px; }
-.emoji-1F1E8-1F1F2 { background-position: -120px -160px; }
-.emoji-1F1E8-1F1F3 { background-position: -140px -160px; }
-.emoji-1F1E8-1F1F4 { background-position: -160px -160px; }
-.emoji-1F1E8-1F1F5 { background-position: -180px 0; }
-.emoji-1F1E8-1F1F7 { background-position: -180px -20px; }
-.emoji-1F1E8-1F1FA { background-position: -180px -40px; }
-.emoji-1F1E8-1F1FB { background-position: -180px -60px; }
-.emoji-1F1E8-1F1FC { background-position: -180px -80px; }
-.emoji-1F1E8-1F1FD { background-position: -180px -100px; }
-.emoji-1F1E8-1F1FE { background-position: -180px -120px; }
-.emoji-1F1E8-1F1FF { background-position: -180px -140px; }
-.emoji-1F1E9-1F1EA { background-position: -180px -160px; }
-.emoji-1F1E9-1F1EC { background-position: 0 -180px; }
-.emoji-1F1E9-1F1EF { background-position: -20px -180px; }
-.emoji-1F1E9-1F1F0 { background-position: -40px -180px; }
-.emoji-1F1E9-1F1F2 { background-position: -60px -180px; }
-.emoji-1F1E9-1F1F4 { background-position: -80px -180px; }
-.emoji-1F1E9-1F1FF { background-position: -100px -180px; }
-.emoji-1F1EA-1F1E6 { background-position: -120px -180px; }
-.emoji-1F1EA-1F1E8 { background-position: -140px -180px; }
-.emoji-1F1EA-1F1EA { background-position: -160px -180px; }
-.emoji-1F1EA-1F1EC { background-position: -180px -180px; }
-.emoji-1F1EA-1F1ED { background-position: -200px 0; }
-.emoji-1F1EA-1F1F7 { background-position: -200px -20px; }
-.emoji-1F1EA-1F1F8 { background-position: -200px -40px; }
-.emoji-1F1EA-1F1F9 { background-position: -200px -60px; }
-.emoji-1F1EA-1F1FA { background-position: -200px -80px; }
-.emoji-1F1EB-1F1EE { background-position: -200px -100px; }
-.emoji-1F1EB-1F1EF { background-position: -200px -120px; }
-.emoji-1F1EB-1F1F0 { background-position: -200px -140px; }
-.emoji-1F1EB-1F1F2 { background-position: -200px -160px; }
-.emoji-1F1EB-1F1F4 { background-position: -200px -180px; }
-.emoji-1F1EB-1F1F7 { background-position: 0 -200px; }
-.emoji-1F1EC-1F1E6 { background-position: -20px -200px; }
-.emoji-1F1EC-1F1E7 { background-position: -40px -200px; }
-.emoji-1F1EC-1F1E9 { background-position: -60px -200px; }
-.emoji-1F1EC-1F1EA { background-position: -80px -200px; }
-.emoji-1F1EC-1F1EB { background-position: -100px -200px; }
-.emoji-1F1EC-1F1EC { background-position: -120px -200px; }
-.emoji-1F1EC-1F1ED { background-position: -140px -200px; }
-.emoji-1F1EC-1F1EE { background-position: -160px -200px; }
-.emoji-1F1EC-1F1F1 { background-position: -180px -200px; }
-.emoji-1F1EC-1F1F2 { background-position: -200px -200px; }
-.emoji-1F1EC-1F1F3 { background-position: -220px 0; }
-.emoji-1F1EC-1F1F5 { background-position: -220px -20px; }
-.emoji-1F1EC-1F1F6 { background-position: -220px -40px; }
-.emoji-1F1EC-1F1F7 { background-position: -220px -60px; }
-.emoji-1F1EC-1F1F8 { background-position: -220px -80px; }
-.emoji-1F1EC-1F1F9 { background-position: -220px -100px; }
-.emoji-1F1EC-1F1FA { background-position: -220px -120px; }
-.emoji-1F1EC-1F1FC { background-position: -220px -140px; }
-.emoji-1F1EC-1F1FE { background-position: -220px -160px; }
-.emoji-1F1ED-1F1F0 { background-position: -220px -180px; }
-.emoji-1F1ED-1F1F2 { background-position: -220px -200px; }
-.emoji-1F1ED-1F1F3 { background-position: 0 -220px; }
-.emoji-1F1ED-1F1F7 { background-position: -20px -220px; }
-.emoji-1F1ED-1F1F9 { background-position: -40px -220px; }
-.emoji-1F1ED-1F1FA { background-position: -60px -220px; }
-.emoji-1F1EE-1F1E8 { background-position: -80px -220px; }
-.emoji-1F1EE-1F1E9 { background-position: -100px -220px; }
-.emoji-1F1EE-1F1EA { background-position: -120px -220px; }
-.emoji-1F1EE-1F1F1 { background-position: -140px -220px; }
-.emoji-1F1EE-1F1F2 { background-position: -160px -220px; }
-.emoji-1F1EE-1F1F3 { background-position: -180px -220px; }
-.emoji-1F1EE-1F1F4 { background-position: -200px -220px; }
-.emoji-1F1EE-1F1F6 { background-position: -220px -220px; }
-.emoji-1F1EE-1F1F7 { background-position: -240px 0; }
-.emoji-1F1EE-1F1F8 { background-position: -240px -20px; }
-.emoji-1F1EE-1F1F9 { background-position: -240px -40px; }
-.emoji-1F1EF-1F1EA { background-position: -240px -60px; }
-.emoji-1F1EF-1F1F2 { background-position: -240px -80px; }
-.emoji-1F1EF-1F1F4 { background-position: -240px -100px; }
-.emoji-1F1EF-1F1F5 { background-position: -240px -120px; }
-.emoji-1F1F0-1F1EA { background-position: -240px -140px; }
-.emoji-1F1F0-1F1EC { background-position: -240px -160px; }
-.emoji-1F1F0-1F1ED { background-position: -240px -180px; }
-.emoji-1F1F0-1F1EE { background-position: -240px -200px; }
-.emoji-1F1F0-1F1F2 { background-position: -240px -220px; }
-.emoji-1F1F0-1F1F3 { background-position: 0 -240px; }
-.emoji-1F1F0-1F1F5 { background-position: -20px -240px; }
-.emoji-1F1F0-1F1F7 { background-position: -40px -240px; }
-.emoji-1F1F0-1F1FC { background-position: -60px -240px; }
-.emoji-1F1F0-1F1FE { background-position: -80px -240px; }
-.emoji-1F1F0-1F1FF { background-position: -100px -240px; }
-.emoji-1F1F1-1F1E6 { background-position: -120px -240px; }
-.emoji-1F1F1-1F1E7 { background-position: -140px -240px; }
-.emoji-1F1F1-1F1E8 { background-position: -160px -240px; }
-.emoji-1F1F1-1F1EE { background-position: -180px -240px; }
-.emoji-1F1F1-1F1F0 { background-position: -200px -240px; }
-.emoji-1F1F1-1F1F7 { background-position: -220px -240px; }
-.emoji-1F1F1-1F1F8 { background-position: -240px -240px; }
-.emoji-1F1F1-1F1F9 { background-position: -260px 0; }
-.emoji-1F1F1-1F1FA { background-position: -260px -20px; }
-.emoji-1F1F1-1F1FB { background-position: -260px -40px; }
-.emoji-1F1F1-1F1FE { background-position: -260px -60px; }
-.emoji-1F1F2-1F1E6 { background-position: -260px -80px; }
-.emoji-1F1F2-1F1E8 { background-position: -260px -100px; }
-.emoji-1F1F2-1F1E9 { background-position: -260px -120px; }
-.emoji-1F1F2-1F1EA { background-position: -260px -140px; }
-.emoji-1F1F2-1F1EB { background-position: -260px -160px; }
-.emoji-1F1F2-1F1EC { background-position: -260px -180px; }
-.emoji-1F1F2-1F1ED { background-position: -260px -200px; }
-.emoji-1F1F2-1F1F0 { background-position: -260px -220px; }
-.emoji-1F1F2-1F1F1 { background-position: -260px -240px; }
-.emoji-1F1F2-1F1F2 { background-position: 0 -260px; }
-.emoji-1F1F2-1F1F3 { background-position: -20px -260px; }
-.emoji-1F1F2-1F1F4 { background-position: -40px -260px; }
-.emoji-1F1F2-1F1F5 { background-position: -60px -260px; }
-.emoji-1F1F2-1F1F6 { background-position: -80px -260px; }
-.emoji-1F1F2-1F1F7 { background-position: -100px -260px; }
-.emoji-1F1F2-1F1F8 { background-position: -120px -260px; }
-.emoji-1F1F2-1F1F9 { background-position: -140px -260px; }
-.emoji-1F1F2-1F1FA { background-position: -160px -260px; }
-.emoji-1F1F2-1F1FB { background-position: -180px -260px; }
-.emoji-1F1F2-1F1FC { background-position: -200px -260px; }
-.emoji-1F1F2-1F1FD { background-position: -220px -260px; }
-.emoji-1F1F2-1F1FE { background-position: -240px -260px; }
-.emoji-1F1F2-1F1FF { background-position: -260px -260px; }
-.emoji-1F1F3-1F1E6 { background-position: -280px 0; }
-.emoji-1F1F3-1F1E8 { background-position: -280px -20px; }
-.emoji-1F1F3-1F1EA { background-position: -280px -40px; }
-.emoji-1F1F3-1F1EB { background-position: -280px -60px; }
-.emoji-1F1F3-1F1EC { background-position: -280px -80px; }
-.emoji-1F1F3-1F1EE { background-position: -280px -100px; }
-.emoji-1F1F3-1F1F1 { background-position: -280px -120px; }
-.emoji-1F1F3-1F1F4 { background-position: -280px -140px; }
-.emoji-1F1F3-1F1F5 { background-position: -280px -160px; }
-.emoji-1F1F3-1F1F7 { background-position: -280px -180px; }
-.emoji-1F1F3-1F1FA { background-position: -280px -200px; }
-.emoji-1F1F3-1F1FF { background-position: -280px -220px; }
-.emoji-1F1F4-1F1F2 { background-position: -280px -240px; }
-.emoji-1F1F5-1F1E6 { background-position: -280px -260px; }
-.emoji-1F1F5-1F1EA { background-position: 0 -280px; }
-.emoji-1F1F5-1F1EB { background-position: -20px -280px; }
-.emoji-1F1F5-1F1EC { background-position: -40px -280px; }
-.emoji-1F1F5-1F1ED { background-position: -60px -280px; }
-.emoji-1F1F5-1F1F0 { background-position: -80px -280px; }
-.emoji-1F1F5-1F1F1 { background-position: -100px -280px; }
-.emoji-1F1F5-1F1F2 { background-position: -120px -280px; }
-.emoji-1F1F5-1F1F3 { background-position: -140px -280px; }
-.emoji-1F1F5-1F1F7 { background-position: -160px -280px; }
-.emoji-1F1F5-1F1F8 { background-position: -180px -280px; }
-.emoji-1F1F5-1F1F9 { background-position: -200px -280px; }
-.emoji-1F1F5-1F1FC { background-position: -220px -280px; }
-.emoji-1F1F5-1F1FE { background-position: -240px -280px; }
-.emoji-1F1F6-1F1E6 { background-position: -260px -280px; }
-.emoji-1F1F7-1F1EA { background-position: -280px -280px; }
-.emoji-1F1F7-1F1F4 { background-position: -300px 0; }
-.emoji-1F1F7-1F1F8 { background-position: -300px -20px; }
-.emoji-1F1F7-1F1FA { background-position: -300px -40px; }
-.emoji-1F1F7-1F1FC { background-position: -300px -60px; }
-.emoji-1F1F8-1F1E6 { background-position: -300px -80px; }
-.emoji-1F1F8-1F1E7 { background-position: -300px -100px; }
-.emoji-1F1F8-1F1E8 { background-position: -300px -120px; }
-.emoji-1F1F8-1F1E9 { background-position: -300px -140px; }
-.emoji-1F1F8-1F1EA { background-position: -300px -160px; }
-.emoji-1F1F8-1F1EC { background-position: -300px -180px; }
-.emoji-1F1F8-1F1ED { background-position: -300px -200px; }
-.emoji-1F1F8-1F1EE { background-position: -300px -220px; }
-.emoji-1F1F8-1F1EF { background-position: -300px -240px; }
-.emoji-1F1F8-1F1F0 { background-position: -300px -260px; }
-.emoji-1F1F8-1F1F1 { background-position: -300px -280px; }
-.emoji-1F1F8-1F1F2 { background-position: 0 -300px; }
-.emoji-1F1F8-1F1F3 { background-position: -20px -300px; }
-.emoji-1F1F8-1F1F4 { background-position: -40px -300px; }
-.emoji-1F1F8-1F1F7 { background-position: -60px -300px; }
-.emoji-1F1F8-1F1F8 { background-position: -80px -300px; }
-.emoji-1F1F8-1F1F9 { background-position: -100px -300px; }
-.emoji-1F1F8-1F1FB { background-position: -120px -300px; }
-.emoji-1F1F8-1F1FD { background-position: -140px -300px; }
-.emoji-1F1F8-1F1FE { background-position: -160px -300px; }
-.emoji-1F1F8-1F1FF { background-position: -180px -300px; }
-.emoji-1F1F9-1F1E6 { background-position: -200px -300px; }
-.emoji-1F1F9-1F1E8 { background-position: -220px -300px; }
-.emoji-1F1F9-1F1E9 { background-position: -240px -300px; }
-.emoji-1F1F9-1F1EB { background-position: -260px -300px; }
-.emoji-1F1F9-1F1EC { background-position: -280px -300px; }
-.emoji-1F1F9-1F1ED { background-position: -300px -300px; }
-.emoji-1F1F9-1F1EF { background-position: -320px 0; }
-.emoji-1F1F9-1F1F0 { background-position: -320px -20px; }
-.emoji-1F1F9-1F1F1 { background-position: -320px -40px; }
-.emoji-1F1F9-1F1F2 { background-position: -320px -60px; }
-.emoji-1F1F9-1F1F3 { background-position: -320px -80px; }
-.emoji-1F1F9-1F1F4 { background-position: -320px -100px; }
-.emoji-1F1F9-1F1F7 { background-position: -320px -120px; }
-.emoji-1F1F9-1F1F9 { background-position: -320px -140px; }
-.emoji-1F1F9-1F1FB { background-position: -320px -160px; }
-.emoji-1F1F9-1F1FC { background-position: -320px -180px; }
-.emoji-1F1F9-1F1FF { background-position: -320px -200px; }
-.emoji-1F1FA-1F1E6 { background-position: -320px -220px; }
-.emoji-1F1FA-1F1EC { background-position: -320px -240px; }
-.emoji-1F1FA-1F1F2 { background-position: -320px -260px; }
-.emoji-1F1FA-1F1F8 { background-position: -320px -280px; }
-.emoji-1F1FA-1F1FE { background-position: -320px -300px; }
-.emoji-1F1FA-1F1FF { background-position: 0 -320px; }
-.emoji-1F1FB-1F1E6 { background-position: -20px -320px; }
-.emoji-1F1FB-1F1E8 { background-position: -40px -320px; }
-.emoji-1F1FB-1F1EA { background-position: -60px -320px; }
-.emoji-1F1FB-1F1EC { background-position: -80px -320px; }
-.emoji-1F1FB-1F1EE { background-position: -100px -320px; }
-.emoji-1F1FB-1F1F3 { background-position: -120px -320px; }
-.emoji-1F1FB-1F1FA { background-position: -140px -320px; }
-.emoji-1F1FC-1F1EB { background-position: -160px -320px; }
-.emoji-1F1FC-1F1F8 { background-position: -180px -320px; }
-.emoji-1F1FD-1F1F0 { background-position: -200px -320px; }
-.emoji-1F1FE-1F1EA { background-position: -220px -320px; }
-.emoji-1F1FE-1F1F9 { background-position: -240px -320px; }
-.emoji-1F1FF-1F1E6 { background-position: -260px -320px; }
-.emoji-1F1FF-1F1F2 { background-position: -280px -320px; }
-.emoji-1F1FF-1F1FC { background-position: -300px -320px; }
-.emoji-1F201 { background-position: -320px -320px; }
-.emoji-1F202 { background-position: -340px 0; }
-.emoji-1F21A { background-position: -340px -20px; }
-.emoji-1F22F { background-position: -340px -40px; }
-.emoji-1F232 { background-position: -340px -60px; }
-.emoji-1F233 { background-position: -340px -80px; }
-.emoji-1F234 { background-position: -340px -100px; }
-.emoji-1F235 { background-position: -340px -120px; }
-.emoji-1F236 { background-position: -340px -140px; }
-.emoji-1F237 { background-position: -340px -160px; }
-.emoji-1F238 { background-position: -340px -180px; }
-.emoji-1F239 { background-position: -340px -200px; }
-.emoji-1F23A { background-position: -340px -220px; }
-.emoji-1F250 { background-position: -340px -240px; }
-.emoji-1F251 { background-position: -340px -260px; }
-.emoji-1F300 { background-position: -340px -280px; }
-.emoji-1F301 { background-position: -340px -300px; }
-.emoji-1F302 { background-position: -340px -320px; }
-.emoji-1F303 { background-position: 0 -340px; }
-.emoji-1F304 { background-position: -20px -340px; }
-.emoji-1F305 { background-position: -40px -340px; }
-.emoji-1F306 { background-position: -60px -340px; }
-.emoji-1F307 { background-position: -80px -340px; }
-.emoji-1F308 { background-position: -100px -340px; }
-.emoji-1F309 { background-position: -120px -340px; }
-.emoji-1F30A { background-position: -140px -340px; }
-.emoji-1F30B { background-position: -160px -340px; }
-.emoji-1F30C { background-position: -180px -340px; }
-.emoji-1F30D { background-position: -200px -340px; }
-.emoji-1F30E { background-position: -220px -340px; }
-.emoji-1F30F { background-position: -240px -340px; }
-.emoji-1F310 { background-position: -260px -340px; }
-.emoji-1F311 { background-position: -280px -340px; }
-.emoji-1F312 { background-position: -300px -340px; }
-.emoji-1F313 { background-position: -320px -340px; }
-.emoji-1F314 { background-position: -340px -340px; }
-.emoji-1F315 { background-position: -360px 0; }
-.emoji-1F316 { background-position: -360px -20px; }
-.emoji-1F317 { background-position: -360px -40px; }
-.emoji-1F318 { background-position: -360px -60px; }
-.emoji-1F319 { background-position: -360px -80px; }
-.emoji-1F31A { background-position: -360px -100px; }
-.emoji-1F31B { background-position: -360px -120px; }
-.emoji-1F31C { background-position: -360px -140px; }
-.emoji-1F31D { background-position: -360px -160px; }
-.emoji-1F31E { background-position: -360px -180px; }
-.emoji-1F31F { background-position: -360px -200px; }
-.emoji-1F320 { background-position: -360px -220px; }
-.emoji-1F321 { background-position: -360px -240px; }
-.emoji-1F324 { background-position: -360px -260px; }
-.emoji-1F325 { background-position: -360px -280px; }
-.emoji-1F326 { background-position: -360px -300px; }
-.emoji-1F327 { background-position: -360px -320px; }
-.emoji-1F328 { background-position: -360px -340px; }
-.emoji-1F329 { background-position: 0 -360px; }
-.emoji-1F32A { background-position: -20px -360px; }
-.emoji-1F32B { background-position: -40px -360px; }
-.emoji-1F32C { background-position: -60px -360px; }
-.emoji-1F32D { background-position: -80px -360px; }
-.emoji-1F32E { background-position: -100px -360px; }
-.emoji-1F32F { background-position: -120px -360px; }
-.emoji-1F330 { background-position: -140px -360px; }
-.emoji-1F331 { background-position: -160px -360px; }
-.emoji-1F332 { background-position: -180px -360px; }
-.emoji-1F333 { background-position: -200px -360px; }
-.emoji-1F334 { background-position: -220px -360px; }
-.emoji-1F335 { background-position: -240px -360px; }
-.emoji-1F336 { background-position: -260px -360px; }
-.emoji-1F337 { background-position: -280px -360px; }
-.emoji-1F338 { background-position: -300px -360px; }
-.emoji-1F339 { background-position: -320px -360px; }
-.emoji-1F33A { background-position: -340px -360px; }
-.emoji-1F33B { background-position: -360px -360px; }
-.emoji-1F33C { background-position: -380px 0; }
-.emoji-1F33D { background-position: -380px -20px; }
-.emoji-1F33E { background-position: -380px -40px; }
-.emoji-1F33F { background-position: -380px -60px; }
-.emoji-1F340 { background-position: -380px -80px; }
-.emoji-1F341 { background-position: -380px -100px; }
-.emoji-1F342 { background-position: -380px -120px; }
-.emoji-1F343 { background-position: -380px -140px; }
-.emoji-1F344 { background-position: -380px -160px; }
-.emoji-1F345 { background-position: -380px -180px; }
-.emoji-1F346 { background-position: -380px -200px; }
-.emoji-1F347 { background-position: -380px -220px; }
-.emoji-1F348 { background-position: -380px -240px; }
-.emoji-1F349 { background-position: -380px -260px; }
-.emoji-1F34A { background-position: -380px -280px; }
-.emoji-1F34B { background-position: -380px -300px; }
-.emoji-1F34C { background-position: -380px -320px; }
-.emoji-1F34D { background-position: -380px -340px; }
-.emoji-1F34E { background-position: -380px -360px; }
-.emoji-1F34F { background-position: 0 -380px; }
-.emoji-1F350 { background-position: -20px -380px; }
-.emoji-1F351 { background-position: -40px -380px; }
-.emoji-1F352 { background-position: -60px -380px; }
-.emoji-1F353 { background-position: -80px -380px; }
-.emoji-1F354 { background-position: -100px -380px; }
-.emoji-1F355 { background-position: -120px -380px; }
-.emoji-1F356 { background-position: -140px -380px; }
-.emoji-1F357 { background-position: -160px -380px; }
-.emoji-1F358 { background-position: -180px -380px; }
-.emoji-1F359 { background-position: -200px -380px; }
-.emoji-1F35A { background-position: -220px -380px; }
-.emoji-1F35B { background-position: -240px -380px; }
-.emoji-1F35C { background-position: -260px -380px; }
-.emoji-1F35D { background-position: -280px -380px; }
-.emoji-1F35E { background-position: -300px -380px; }
-.emoji-1F35F { background-position: -320px -380px; }
-.emoji-1F360 { background-position: -340px -380px; }
-.emoji-1F361 { background-position: -360px -380px; }
-.emoji-1F362 { background-position: -380px -380px; }
-.emoji-1F363 { background-position: -400px 0; }
-.emoji-1F364 { background-position: -400px -20px; }
-.emoji-1F365 { background-position: -400px -40px; }
-.emoji-1F366 { background-position: -400px -60px; }
-.emoji-1F367 { background-position: -400px -80px; }
-.emoji-1F368 { background-position: -400px -100px; }
-.emoji-1F369 { background-position: -400px -120px; }
-.emoji-1F36A { background-position: -400px -140px; }
-.emoji-1F36B { background-position: -400px -160px; }
-.emoji-1F36C { background-position: -400px -180px; }
-.emoji-1F36D { background-position: -400px -200px; }
-.emoji-1F36E { background-position: -400px -220px; }
-.emoji-1F36F { background-position: -400px -240px; }
-.emoji-1F370 { background-position: -400px -260px; }
-.emoji-1F371 { background-position: -400px -280px; }
-.emoji-1F372 { background-position: -400px -300px; }
-.emoji-1F373 { background-position: -400px -320px; }
-.emoji-1F374 { background-position: -400px -340px; }
-.emoji-1F375 { background-position: -400px -360px; }
-.emoji-1F376 { background-position: -400px -380px; }
-.emoji-1F377 { background-position: 0 -400px; }
-.emoji-1F378 { background-position: -20px -400px; }
-.emoji-1F379 { background-position: -40px -400px; }
-.emoji-1F37A { background-position: -60px -400px; }
-.emoji-1F37B { background-position: -80px -400px; }
-.emoji-1F37C { background-position: -100px -400px; }
-.emoji-1F37D { background-position: -120px -400px; }
-.emoji-1F37E { background-position: -140px -400px; }
-.emoji-1F37F { background-position: -160px -400px; }
-.emoji-1F380 { background-position: -180px -400px; }
-.emoji-1F381 { background-position: -200px -400px; }
-.emoji-1F382 { background-position: -220px -400px; }
-.emoji-1F383 { background-position: -240px -400px; }
-.emoji-1F384 { background-position: -260px -400px; }
-.emoji-1F385 { background-position: -280px -400px; }
-.emoji-1F385-1F3FB { background-position: -300px -400px; }
-.emoji-1F385-1F3FC { background-position: -320px -400px; }
-.emoji-1F385-1F3FD { background-position: -340px -400px; }
-.emoji-1F385-1F3FE { background-position: -360px -400px; }
-.emoji-1F385-1F3FF { background-position: -380px -400px; }
-.emoji-1F386 { background-position: -400px -400px; }
-.emoji-1F387 { background-position: -420px 0; }
-.emoji-1F388 { background-position: -420px -20px; }
-.emoji-1F389 { background-position: -420px -40px; }
-.emoji-1F38A { background-position: -420px -60px; }
-.emoji-1F38B { background-position: -420px -80px; }
-.emoji-1F38C { background-position: -420px -100px; }
-.emoji-1F38D { background-position: -420px -120px; }
-.emoji-1F38E { background-position: -420px -140px; }
-.emoji-1F38F { background-position: -420px -160px; }
-.emoji-1F390 { background-position: -420px -180px; }
-.emoji-1F391 { background-position: -420px -200px; }
-.emoji-1F392 { background-position: -420px -220px; }
-.emoji-1F393 { background-position: -420px -240px; }
-.emoji-1F396 { background-position: -420px -260px; }
-.emoji-1F397 { background-position: -420px -280px; }
-.emoji-1F399 { background-position: -420px -300px; }
-.emoji-1F39A { background-position: -420px -320px; }
-.emoji-1F39B { background-position: -420px -340px; }
-.emoji-1F39E { background-position: -420px -360px; }
-.emoji-1F39F { background-position: -420px -380px; }
-.emoji-1F3A0 { background-position: -420px -400px; }
-.emoji-1F3A1 { background-position: 0 -420px; }
-.emoji-1F3A2 { background-position: -20px -420px; }
-.emoji-1F3A3 { background-position: -40px -420px; }
-.emoji-1F3A4 { background-position: -60px -420px; }
-.emoji-1F3A5 { background-position: -80px -420px; }
-.emoji-1F3A6 { background-position: -100px -420px; }
-.emoji-1F3A7 { background-position: -120px -420px; }
-.emoji-1F3A8 { background-position: -140px -420px; }
-.emoji-1F3A9 { background-position: -160px -420px; }
-.emoji-1F3AA { background-position: -180px -420px; }
-.emoji-1F3AB { background-position: -200px -420px; }
-.emoji-1F3AC { background-position: -220px -420px; }
-.emoji-1F3AD { background-position: -240px -420px; }
-.emoji-1F3AE { background-position: -260px -420px; }
-.emoji-1F3AF { background-position: -280px -420px; }
-.emoji-1F3B0 { background-position: -300px -420px; }
-.emoji-1F3B1 { background-position: -320px -420px; }
-.emoji-1F3B2 { background-position: -340px -420px; }
-.emoji-1F3B3 { background-position: -360px -420px; }
-.emoji-1F3B4 { background-position: -380px -420px; }
-.emoji-1F3B5 { background-position: -400px -420px; }
-.emoji-1F3B6 { background-position: -420px -420px; }
-.emoji-1F3B7 { background-position: -440px 0; }
-.emoji-1F3B8 { background-position: -440px -20px; }
-.emoji-1F3B9 { background-position: -440px -40px; }
-.emoji-1F3BA { background-position: -440px -60px; }
-.emoji-1F3BB { background-position: -440px -80px; }
-.emoji-1F3BC { background-position: -440px -100px; }
-.emoji-1F3BD { background-position: -440px -120px; }
-.emoji-1F3BE { background-position: -440px -140px; }
-.emoji-1F3BF { background-position: -440px -160px; }
-.emoji-1F3C0 { background-position: -440px -180px; }
-.emoji-1F3C1 { background-position: -440px -200px; }
-.emoji-1F3C2 { background-position: -440px -220px; }
-.emoji-1F3C3 { background-position: -440px -240px; }
-.emoji-1F3C3-1F3FB { background-position: -440px -260px; }
-.emoji-1F3C3-1F3FC { background-position: -440px -280px; }
-.emoji-1F3C3-1F3FD { background-position: -440px -300px; }
-.emoji-1F3C3-1F3FE { background-position: -440px -320px; }
-.emoji-1F3C3-1F3FF { background-position: -440px -340px; }
-.emoji-1F3C4 { background-position: -440px -360px; }
-.emoji-1F3C4-1F3FB { background-position: -440px -380px; }
-.emoji-1F3C4-1F3FC { background-position: -440px -400px; }
-.emoji-1F3C4-1F3FD { background-position: -440px -420px; }
-.emoji-1F3C4-1F3FE { background-position: 0 -440px; }
-.emoji-1F3C4-1F3FF { background-position: -20px -440px; }
-.emoji-1F3C5 { background-position: -40px -440px; }
-.emoji-1F3C6 { background-position: -60px -440px; }
-.emoji-1F3C7 { background-position: -80px -440px; }
-.emoji-1F3C7-1F3FB { background-position: -100px -440px; }
-.emoji-1F3C7-1F3FC { background-position: -120px -440px; }
-.emoji-1F3C7-1F3FD { background-position: -140px -440px; }
-.emoji-1F3C7-1F3FE { background-position: -160px -440px; }
-.emoji-1F3C7-1F3FF { background-position: -180px -440px; }
-.emoji-1F3C8 { background-position: -200px -440px; }
-.emoji-1F3C9 { background-position: -220px -440px; }
-.emoji-1F3CA { background-position: -240px -440px; }
-.emoji-1F3CA-1F3FB { background-position: -260px -440px; }
-.emoji-1F3CA-1F3FC { background-position: -280px -440px; }
-.emoji-1F3CA-1F3FD { background-position: -300px -440px; }
-.emoji-1F3CA-1F3FE { background-position: -320px -440px; }
-.emoji-1F3CA-1F3FF { background-position: -340px -440px; }
-.emoji-1F3CB { background-position: -360px -440px; }
-.emoji-1F3CB-1F3FB { background-position: -380px -440px; }
-.emoji-1F3CB-1F3FC { background-position: -400px -440px; }
-.emoji-1F3CB-1F3FD { background-position: -420px -440px; }
-.emoji-1F3CB-1F3FE { background-position: -440px -440px; }
-.emoji-1F3CB-1F3FF { background-position: -460px 0; }
-.emoji-1F3CC { background-position: -460px -20px; }
-.emoji-1F3CD { background-position: -460px -40px; }
-.emoji-1F3CE { background-position: -460px -60px; }
-.emoji-1F3CF { background-position: -460px -80px; }
-.emoji-1F3D0 { background-position: -460px -100px; }
-.emoji-1F3D1 { background-position: -460px -120px; }
-.emoji-1F3D2 { background-position: -460px -140px; }
-.emoji-1F3D3 { background-position: -460px -160px; }
-.emoji-1F3D4 { background-position: -460px -180px; }
-.emoji-1F3D5 { background-position: -460px -200px; }
-.emoji-1F3D6 { background-position: -460px -220px; }
-.emoji-1F3D7 { background-position: -460px -240px; }
-.emoji-1F3D8 { background-position: -460px -260px; }
-.emoji-1F3D9 { background-position: -460px -280px; }
-.emoji-1F3DA { background-position: -460px -300px; }
-.emoji-1F3DB { background-position: -460px -320px; }
-.emoji-1F3DC { background-position: -460px -340px; }
-.emoji-1F3DD { background-position: -460px -360px; }
-.emoji-1F3DE { background-position: -460px -380px; }
-.emoji-1F3DF { background-position: -460px -400px; }
-.emoji-1F3E0 { background-position: -460px -420px; }
-.emoji-1F3E1 { background-position: -460px -440px; }
-.emoji-1F3E2 { background-position: 0 -460px; }
-.emoji-1F3E3 { background-position: -20px -460px; }
-.emoji-1F3E4 { background-position: -40px -460px; }
-.emoji-1F3E5 { background-position: -60px -460px; }
-.emoji-1F3E6 { background-position: -80px -460px; }
-.emoji-1F3E7 { background-position: -100px -460px; }
-.emoji-1F3E8 { background-position: -120px -460px; }
-.emoji-1F3E9 { background-position: -140px -460px; }
-.emoji-1F3EA { background-position: -160px -460px; }
-.emoji-1F3EB { background-position: -180px -460px; }
-.emoji-1F3EC { background-position: -200px -460px; }
-.emoji-1F3ED { background-position: -220px -460px; }
-.emoji-1F3EE { background-position: -240px -460px; }
-.emoji-1F3EF { background-position: -260px -460px; }
-.emoji-1F3F0 { background-position: -280px -460px; }
-.emoji-1F3F3 { background-position: -300px -460px; }
-.emoji-1F3F4 { background-position: -320px -460px; }
-.emoji-1F3F5 { background-position: -340px -460px; }
-.emoji-1F3F7 { background-position: -360px -460px; }
-.emoji-1F3F8 { background-position: -380px -460px; }
-.emoji-1F3F9 { background-position: -400px -460px; }
-.emoji-1F3FA { background-position: -420px -460px; }
-.emoji-1F3FB { background-position: -440px -460px; }
-.emoji-1F3FC { background-position: -460px -460px; }
-.emoji-1F3FD { background-position: -480px 0; }
-.emoji-1F3FE { background-position: -480px -20px; }
-.emoji-1F3FF { background-position: -480px -40px; }
-.emoji-1F400 { background-position: -480px -60px; }
-.emoji-1F401 { background-position: -480px -80px; }
-.emoji-1F402 { background-position: -480px -100px; }
-.emoji-1F403 { background-position: -480px -120px; }
-.emoji-1F404 { background-position: -480px -140px; }
-.emoji-1F405 { background-position: -480px -160px; }
-.emoji-1F406 { background-position: -480px -180px; }
-.emoji-1F407 { background-position: -480px -200px; }
-.emoji-1F408 { background-position: -480px -220px; }
-.emoji-1F409 { background-position: -480px -240px; }
-.emoji-1F40A { background-position: -480px -260px; }
-.emoji-1F40B { background-position: -480px -280px; }
-.emoji-1F40C { background-position: -480px -300px; }
-.emoji-1F40D { background-position: -480px -320px; }
-.emoji-1F40E { background-position: -480px -340px; }
-.emoji-1F40F { background-position: -480px -360px; }
-.emoji-1F410 { background-position: -480px -380px; }
-.emoji-1F411 { background-position: -480px -400px; }
-.emoji-1F412 { background-position: -480px -420px; }
-.emoji-1F413 { background-position: -480px -440px; }
-.emoji-1F414 { background-position: -480px -460px; }
-.emoji-1F415 { background-position: 0 -480px; }
-.emoji-1F416 { background-position: -20px -480px; }
-.emoji-1F417 { background-position: -40px -480px; }
-.emoji-1F418 { background-position: -60px -480px; }
-.emoji-1F419 { background-position: -80px -480px; }
-.emoji-1F41A { background-position: -100px -480px; }
-.emoji-1F41B { background-position: -120px -480px; }
-.emoji-1F41C { background-position: -140px -480px; }
-.emoji-1F41D { background-position: -160px -480px; }
-.emoji-1F41E { background-position: -180px -480px; }
-.emoji-1F41F { background-position: -200px -480px; }
-.emoji-1F420 { background-position: -220px -480px; }
-.emoji-1F421 { background-position: -240px -480px; }
-.emoji-1F422 { background-position: -260px -480px; }
-.emoji-1F423 { background-position: -280px -480px; }
-.emoji-1F424 { background-position: -300px -480px; }
-.emoji-1F425 { background-position: -320px -480px; }
-.emoji-1F426 { background-position: -340px -480px; }
-.emoji-1F427 { background-position: -360px -480px; }
-.emoji-1F428 { background-position: -380px -480px; }
-.emoji-1F429 { background-position: -400px -480px; }
-.emoji-1F42A { background-position: -420px -480px; }
-.emoji-1F42B { background-position: -440px -480px; }
-.emoji-1F42C { background-position: -460px -480px; }
-.emoji-1F42D { background-position: -480px -480px; }
-.emoji-1F42E { background-position: -500px 0; }
-.emoji-1F42F { background-position: -500px -20px; }
-.emoji-1F430 { background-position: -500px -40px; }
-.emoji-1F431 { background-position: -500px -60px; }
-.emoji-1F432 { background-position: -500px -80px; }
-.emoji-1F433 { background-position: -500px -100px; }
-.emoji-1F434 { background-position: -500px -120px; }
-.emoji-1F435 { background-position: -500px -140px; }
-.emoji-1F436 { background-position: -500px -160px; }
-.emoji-1F437 { background-position: -500px -180px; }
-.emoji-1F438 { background-position: -500px -200px; }
-.emoji-1F439 { background-position: -500px -220px; }
-.emoji-1F43A { background-position: -500px -240px; }
-.emoji-1F43B { background-position: -500px -260px; }
-.emoji-1F43C { background-position: -500px -280px; }
-.emoji-1F43D { background-position: -500px -300px; }
-.emoji-1F43E { background-position: -500px -320px; }
-.emoji-1F43F { background-position: -500px -340px; }
-.emoji-1F440 { background-position: -500px -360px; }
-.emoji-1F441 { background-position: -500px -380px; }
-.emoji-1F441-1F5E8 { background-position: -500px -400px; }
-.emoji-1F442 { background-position: -500px -420px; }
-.emoji-1F442-1F3FB { background-position: -500px -440px; }
-.emoji-1F442-1F3FC { background-position: -500px -460px; }
-.emoji-1F442-1F3FD { background-position: -500px -480px; }
-.emoji-1F442-1F3FE { background-position: 0 -500px; }
-.emoji-1F442-1F3FF { background-position: -20px -500px; }
-.emoji-1F443 { background-position: -40px -500px; }
-.emoji-1F443-1F3FB { background-position: -60px -500px; }
-.emoji-1F443-1F3FC { background-position: -80px -500px; }
-.emoji-1F443-1F3FD { background-position: -100px -500px; }
-.emoji-1F443-1F3FE { background-position: -120px -500px; }
-.emoji-1F443-1F3FF { background-position: -140px -500px; }
-.emoji-1F444 { background-position: -160px -500px; }
-.emoji-1F445 { background-position: -180px -500px; }
-.emoji-1F446 { background-position: -200px -500px; }
-.emoji-1F446-1F3FB { background-position: -220px -500px; }
-.emoji-1F446-1F3FC { background-position: -240px -500px; }
-.emoji-1F446-1F3FD { background-position: -260px -500px; }
-.emoji-1F446-1F3FE { background-position: -280px -500px; }
-.emoji-1F446-1F3FF { background-position: -300px -500px; }
-.emoji-1F447 { background-position: -320px -500px; }
-.emoji-1F447-1F3FB { background-position: -340px -500px; }
-.emoji-1F447-1F3FC { background-position: -360px -500px; }
-.emoji-1F447-1F3FD { background-position: -380px -500px; }
-.emoji-1F447-1F3FE { background-position: -400px -500px; }
-.emoji-1F447-1F3FF { background-position: -420px -500px; }
-.emoji-1F448 { background-position: -440px -500px; }
-.emoji-1F448-1F3FB { background-position: -460px -500px; }
-.emoji-1F448-1F3FC { background-position: -480px -500px; }
-.emoji-1F448-1F3FD { background-position: -500px -500px; }
-.emoji-1F448-1F3FE { background-position: -520px 0; }
-.emoji-1F448-1F3FF { background-position: -520px -20px; }
-.emoji-1F449 { background-position: -520px -40px; }
-.emoji-1F449-1F3FB { background-position: -520px -60px; }
-.emoji-1F449-1F3FC { background-position: -520px -80px; }
-.emoji-1F449-1F3FD { background-position: -520px -100px; }
-.emoji-1F449-1F3FE { background-position: -520px -120px; }
-.emoji-1F449-1F3FF { background-position: -520px -140px; }
-.emoji-1F44A { background-position: -520px -160px; }
-.emoji-1F44A-1F3FB { background-position: -520px -180px; }
-.emoji-1F44A-1F3FC { background-position: -520px -200px; }
-.emoji-1F44A-1F3FD { background-position: -520px -220px; }
-.emoji-1F44A-1F3FE { background-position: -520px -240px; }
-.emoji-1F44A-1F3FF { background-position: -520px -260px; }
-.emoji-1F44B { background-position: -520px -280px; }
-.emoji-1F44B-1F3FB { background-position: -520px -300px; }
-.emoji-1F44B-1F3FC { background-position: -520px -320px; }
-.emoji-1F44B-1F3FD { background-position: -520px -340px; }
-.emoji-1F44B-1F3FE { background-position: -520px -360px; }
-.emoji-1F44B-1F3FF { background-position: -520px -380px; }
-.emoji-1F44C { background-position: -520px -400px; }
-.emoji-1F44C-1F3FB { background-position: -520px -420px; }
-.emoji-1F44C-1F3FC { background-position: -520px -440px; }
-.emoji-1F44C-1F3FD { background-position: -520px -460px; }
-.emoji-1F44C-1F3FE { background-position: -520px -480px; }
-.emoji-1F44C-1F3FF { background-position: -520px -500px; }
-.emoji-1F44D { background-position: 0 -520px; }
-.emoji-1F44D-1F3FB { background-position: -20px -520px; }
-.emoji-1F44D-1F3FC { background-position: -40px -520px; }
-.emoji-1F44D-1F3FD { background-position: -60px -520px; }
-.emoji-1F44D-1F3FE { background-position: -80px -520px; }
-.emoji-1F44D-1F3FF { background-position: -100px -520px; }
-.emoji-1F44E { background-position: -120px -520px; }
-.emoji-1F44E-1F3FB { background-position: -140px -520px; }
-.emoji-1F44E-1F3FC { background-position: -160px -520px; }
-.emoji-1F44E-1F3FD { background-position: -180px -520px; }
-.emoji-1F44E-1F3FE { background-position: -200px -520px; }
-.emoji-1F44E-1F3FF { background-position: -220px -520px; }
-.emoji-1F44F { background-position: -240px -520px; }
-.emoji-1F44F-1F3FB { background-position: -260px -520px; }
-.emoji-1F44F-1F3FC { background-position: -280px -520px; }
-.emoji-1F44F-1F3FD { background-position: -300px -520px; }
-.emoji-1F44F-1F3FE { background-position: -320px -520px; }
-.emoji-1F44F-1F3FF { background-position: -340px -520px; }
-.emoji-1F450 { background-position: -360px -520px; }
-.emoji-1F450-1F3FB { background-position: -380px -520px; }
-.emoji-1F450-1F3FC { background-position: -400px -520px; }
-.emoji-1F450-1F3FD { background-position: -420px -520px; }
-.emoji-1F450-1F3FE { background-position: -440px -520px; }
-.emoji-1F450-1F3FF { background-position: -460px -520px; }
-.emoji-1F451 { background-position: -480px -520px; }
-.emoji-1F452 { background-position: -500px -520px; }
-.emoji-1F453 { background-position: -520px -520px; }
-.emoji-1F454 { background-position: -540px 0; }
-.emoji-1F455 { background-position: -540px -20px; }
-.emoji-1F456 { background-position: -540px -40px; }
-.emoji-1F457 { background-position: -540px -60px; }
-.emoji-1F458 { background-position: -540px -80px; }
-.emoji-1F459 { background-position: -540px -100px; }
-.emoji-1F45A { background-position: -540px -120px; }
-.emoji-1F45B { background-position: -540px -140px; }
-.emoji-1F45C { background-position: -540px -160px; }
-.emoji-1F45D { background-position: -540px -180px; }
-.emoji-1F45E { background-position: -540px -200px; }
-.emoji-1F45F { background-position: -540px -220px; }
-.emoji-1F460 { background-position: -540px -240px; }
-.emoji-1F461 { background-position: -540px -260px; }
-.emoji-1F462 { background-position: -540px -280px; }
-.emoji-1F463 { background-position: -540px -300px; }
-.emoji-1F464 { background-position: -540px -320px; }
-.emoji-1F465 { background-position: -540px -340px; }
-.emoji-1F466 { background-position: -540px -360px; }
-.emoji-1F466-1F3FB { background-position: -540px -380px; }
-.emoji-1F466-1F3FC { background-position: -540px -400px; }
-.emoji-1F466-1F3FD { background-position: -540px -420px; }
-.emoji-1F466-1F3FE { background-position: -540px -440px; }
-.emoji-1F466-1F3FF { background-position: -540px -460px; }
-.emoji-1F467 { background-position: -540px -480px; }
-.emoji-1F467-1F3FB { background-position: -540px -500px; }
-.emoji-1F467-1F3FC { background-position: -540px -520px; }
-.emoji-1F467-1F3FD { background-position: 0 -540px; }
-.emoji-1F467-1F3FE { background-position: -20px -540px; }
-.emoji-1F467-1F3FF { background-position: -40px -540px; }
-.emoji-1F468 { background-position: -60px -540px; }
-.emoji-1F468-1F3FB { background-position: -80px -540px; }
-.emoji-1F468-1F3FC { background-position: -100px -540px; }
-.emoji-1F468-1F3FD { background-position: -120px -540px; }
-.emoji-1F468-1F3FE { background-position: -140px -540px; }
-.emoji-1F468-1F3FF { background-position: -160px -540px; }
-.emoji-1F468-1F468-1F466 { background-position: -180px -540px; }
-.emoji-1F468-1F468-1F466-1F466 { background-position: -200px -540px; }
-.emoji-1F468-1F468-1F467 { background-position: -220px -540px; }
-.emoji-1F468-1F468-1F467-1F466 { background-position: -240px -540px; }
-.emoji-1F468-1F468-1F467-1F467 { background-position: -260px -540px; }
-.emoji-1F468-1F469-1F466-1F466 { background-position: -280px -540px; }
-.emoji-1F468-1F469-1F467 { background-position: -300px -540px; }
-.emoji-1F468-1F469-1F467-1F466 { background-position: -320px -540px; }
-.emoji-1F468-1F469-1F467-1F467 { background-position: -340px -540px; }
-.emoji-1F468-2764-1F468 { background-position: -360px -540px; }
-.emoji-1F468-2764-1F48B-1F468 { background-position: -380px -540px; }
-.emoji-1F469 { background-position: -400px -540px; }
-.emoji-1F469-1F3FB { background-position: -420px -540px; }
-.emoji-1F469-1F3FC { background-position: -440px -540px; }
-.emoji-1F469-1F3FD { background-position: -460px -540px; }
-.emoji-1F469-1F3FE { background-position: -480px -540px; }
-.emoji-1F469-1F3FF { background-position: -500px -540px; }
-.emoji-1F469-1F469-1F466 { background-position: -520px -540px; }
-.emoji-1F469-1F469-1F466-1F466 { background-position: -540px -540px; }
-.emoji-1F469-1F469-1F467 { background-position: -560px 0; }
-.emoji-1F469-1F469-1F467-1F466 { background-position: -560px -20px; }
-.emoji-1F469-1F469-1F467-1F467 { background-position: -560px -40px; }
-.emoji-1F469-2764-1F469 { background-position: -560px -60px; }
-.emoji-1F469-2764-1F48B-1F469 { background-position: -560px -80px; }
-.emoji-1F46A { background-position: -560px -100px; }
-.emoji-1F46B { background-position: -560px -120px; }
-.emoji-1F46C { background-position: -560px -140px; }
-.emoji-1F46D { background-position: -560px -160px; }
-.emoji-1F46E { background-position: -560px -180px; }
-.emoji-1F46E-1F3FB { background-position: -560px -200px; }
-.emoji-1F46E-1F3FC { background-position: -560px -220px; }
-.emoji-1F46E-1F3FD { background-position: -560px -240px; }
-.emoji-1F46E-1F3FE { background-position: -560px -260px; }
-.emoji-1F46E-1F3FF { background-position: -560px -280px; }
-.emoji-1F46F { background-position: -560px -300px; }
-.emoji-1F470 { background-position: -560px -320px; }
-.emoji-1F470-1F3FB { background-position: -560px -340px; }
-.emoji-1F470-1F3FC { background-position: -560px -360px; }
-.emoji-1F470-1F3FD { background-position: -560px -380px; }
-.emoji-1F470-1F3FE { background-position: -560px -400px; }
-.emoji-1F470-1F3FF { background-position: -560px -420px; }
-.emoji-1F471 { background-position: -560px -440px; }
-.emoji-1F471-1F3FB { background-position: -560px -460px; }
-.emoji-1F471-1F3FC { background-position: -560px -480px; }
-.emoji-1F471-1F3FD { background-position: -560px -500px; }
-.emoji-1F471-1F3FE { background-position: -560px -520px; }
-.emoji-1F471-1F3FF { background-position: -560px -540px; }
-.emoji-1F472 { background-position: 0 -560px; }
-.emoji-1F472-1F3FB { background-position: -20px -560px; }
-.emoji-1F472-1F3FC { background-position: -40px -560px; }
-.emoji-1F472-1F3FD { background-position: -60px -560px; }
-.emoji-1F472-1F3FE { background-position: -80px -560px; }
-.emoji-1F472-1F3FF { background-position: -100px -560px; }
-.emoji-1F473 { background-position: -120px -560px; }
-.emoji-1F473-1F3FB { background-position: -140px -560px; }
-.emoji-1F473-1F3FC { background-position: -160px -560px; }
-.emoji-1F473-1F3FD { background-position: -180px -560px; }
-.emoji-1F473-1F3FE { background-position: -200px -560px; }
-.emoji-1F473-1F3FF { background-position: -220px -560px; }
-.emoji-1F474 { background-position: -240px -560px; }
-.emoji-1F474-1F3FB { background-position: -260px -560px; }
-.emoji-1F474-1F3FC { background-position: -280px -560px; }
-.emoji-1F474-1F3FD { background-position: -300px -560px; }
-.emoji-1F474-1F3FE { background-position: -320px -560px; }
-.emoji-1F474-1F3FF { background-position: -340px -560px; }
-.emoji-1F475 { background-position: -360px -560px; }
-.emoji-1F475-1F3FB { background-position: -380px -560px; }
-.emoji-1F475-1F3FC { background-position: -400px -560px; }
-.emoji-1F475-1F3FD { background-position: -420px -560px; }
-.emoji-1F475-1F3FE { background-position: -440px -560px; }
-.emoji-1F475-1F3FF { background-position: -460px -560px; }
-.emoji-1F476 { background-position: -480px -560px; }
-.emoji-1F476-1F3FB { background-position: -500px -560px; }
-.emoji-1F476-1F3FC { background-position: -520px -560px; }
-.emoji-1F476-1F3FD { background-position: -540px -560px; }
-.emoji-1F476-1F3FE { background-position: -560px -560px; }
-.emoji-1F476-1F3FF { background-position: -580px 0; }
-.emoji-1F477 { background-position: -580px -20px; }
-.emoji-1F477-1F3FB { background-position: -580px -40px; }
-.emoji-1F477-1F3FC { background-position: -580px -60px; }
-.emoji-1F477-1F3FD { background-position: -580px -80px; }
-.emoji-1F477-1F3FE { background-position: -580px -100px; }
-.emoji-1F477-1F3FF { background-position: -580px -120px; }
-.emoji-1F478 { background-position: -580px -140px; }
-.emoji-1F478-1F3FB { background-position: -580px -160px; }
-.emoji-1F478-1F3FC { background-position: -580px -180px; }
-.emoji-1F478-1F3FD { background-position: -580px -200px; }
-.emoji-1F478-1F3FE { background-position: -580px -220px; }
-.emoji-1F478-1F3FF { background-position: -580px -240px; }
-.emoji-1F479 { background-position: -580px -260px; }
-.emoji-1F47A { background-position: -580px -280px; }
-.emoji-1F47B { background-position: -580px -300px; }
-.emoji-1F47C { background-position: -580px -320px; }
-.emoji-1F47C-1F3FB { background-position: -580px -340px; }
-.emoji-1F47C-1F3FC { background-position: -580px -360px; }
-.emoji-1F47C-1F3FD { background-position: -580px -380px; }
-.emoji-1F47C-1F3FE { background-position: -580px -400px; }
-.emoji-1F47C-1F3FF { background-position: -580px -420px; }
-.emoji-1F47D { background-position: -580px -440px; }
-.emoji-1F47E { background-position: -580px -460px; }
-.emoji-1F47F { background-position: -580px -480px; }
-.emoji-1F480 { background-position: -580px -500px; }
-.emoji-1F481 { background-position: -580px -520px; }
-.emoji-1F481-1F3FB { background-position: -580px -540px; }
-.emoji-1F481-1F3FC { background-position: -580px -560px; }
-.emoji-1F481-1F3FD { background-position: 0 -580px; }
-.emoji-1F481-1F3FE { background-position: -20px -580px; }
-.emoji-1F481-1F3FF { background-position: -40px -580px; }
-.emoji-1F482 { background-position: -60px -580px; }
-.emoji-1F482-1F3FB { background-position: -80px -580px; }
-.emoji-1F482-1F3FC { background-position: -100px -580px; }
-.emoji-1F482-1F3FD { background-position: -120px -580px; }
-.emoji-1F482-1F3FE { background-position: -140px -580px; }
-.emoji-1F482-1F3FF { background-position: -160px -580px; }
-.emoji-1F483 { background-position: -180px -580px; }
-.emoji-1F483-1F3FB { background-position: -200px -580px; }
-.emoji-1F483-1F3FC { background-position: -220px -580px; }
-.emoji-1F483-1F3FD { background-position: -240px -580px; }
-.emoji-1F483-1F3FE { background-position: -260px -580px; }
-.emoji-1F483-1F3FF { background-position: -280px -580px; }
-.emoji-1F484 { background-position: -300px -580px; }
-.emoji-1F485 { background-position: -320px -580px; }
-.emoji-1F485-1F3FB { background-position: -340px -580px; }
-.emoji-1F485-1F3FC { background-position: -360px -580px; }
-.emoji-1F485-1F3FD { background-position: -380px -580px; }
-.emoji-1F485-1F3FE { background-position: -400px -580px; }
-.emoji-1F485-1F3FF { background-position: -420px -580px; }
-.emoji-1F486 { background-position: -440px -580px; }
-.emoji-1F486-1F3FB { background-position: -460px -580px; }
-.emoji-1F486-1F3FC { background-position: -480px -580px; }
-.emoji-1F486-1F3FD { background-position: -500px -580px; }
-.emoji-1F486-1F3FE { background-position: -520px -580px; }
-.emoji-1F486-1F3FF { background-position: -540px -580px; }
-.emoji-1F487 { background-position: -560px -580px; }
-.emoji-1F487-1F3FB { background-position: -580px -580px; }
-.emoji-1F487-1F3FC { background-position: -600px 0; }
-.emoji-1F487-1F3FD { background-position: -600px -20px; }
-.emoji-1F487-1F3FE { background-position: -600px -40px; }
-.emoji-1F487-1F3FF { background-position: -600px -60px; }
-.emoji-1F488 { background-position: -600px -80px; }
-.emoji-1F489 { background-position: -600px -100px; }
-.emoji-1F48A { background-position: -600px -120px; }
-.emoji-1F48B { background-position: -600px -140px; }
-.emoji-1F48C { background-position: -600px -160px; }
-.emoji-1F48D { background-position: -600px -180px; }
-.emoji-1F48E { background-position: -600px -200px; }
-.emoji-1F48F { background-position: -600px -220px; }
-.emoji-1F490 { background-position: -600px -240px; }
-.emoji-1F491 { background-position: -600px -260px; }
-.emoji-1F492 { background-position: -600px -280px; }
-.emoji-1F493 { background-position: -600px -300px; }
-.emoji-1F494 { background-position: -600px -320px; }
-.emoji-1F495 { background-position: -600px -340px; }
-.emoji-1F496 { background-position: -600px -360px; }
-.emoji-1F497 { background-position: -600px -380px; }
-.emoji-1F498 { background-position: -600px -400px; }
-.emoji-1F499 { background-position: -600px -420px; }
-.emoji-1F49A { background-position: -600px -440px; }
-.emoji-1F49B { background-position: -600px -460px; }
-.emoji-1F49C { background-position: -600px -480px; }
-.emoji-1F49D { background-position: -600px -500px; }
-.emoji-1F49E { background-position: -600px -520px; }
-.emoji-1F49F { background-position: -600px -540px; }
-.emoji-1F4A0 { background-position: -600px -560px; }
-.emoji-1F4A1 { background-position: -600px -580px; }
-.emoji-1F4A2 { background-position: 0 -600px; }
-.emoji-1F4A3 { background-position: -20px -600px; }
-.emoji-1F4A4 { background-position: -40px -600px; }
-.emoji-1F4A5 { background-position: -60px -600px; }
-.emoji-1F4A6 { background-position: -80px -600px; }
-.emoji-1F4A7 { background-position: -100px -600px; }
-.emoji-1F4A8 { background-position: -120px -600px; }
-.emoji-1F4A9 { background-position: -140px -600px; }
-.emoji-1F4AA { background-position: -160px -600px; }
-.emoji-1F4AA-1F3FB { background-position: -180px -600px; }
-.emoji-1F4AA-1F3FC { background-position: -200px -600px; }
-.emoji-1F4AA-1F3FD { background-position: -220px -600px; }
-.emoji-1F4AA-1F3FE { background-position: -240px -600px; }
-.emoji-1F4AA-1F3FF { background-position: -260px -600px; }
-.emoji-1F4AB { background-position: -280px -600px; }
-.emoji-1F4AC { background-position: -300px -600px; }
-.emoji-1F4AD { background-position: -320px -600px; }
-.emoji-1F4AE { background-position: -340px -600px; }
-.emoji-1F4AF { background-position: -360px -600px; }
-.emoji-1F4B0 { background-position: -380px -600px; }
-.emoji-1F4B1 { background-position: -400px -600px; }
-.emoji-1F4B2 { background-position: -420px -600px; }
-.emoji-1F4B3 { background-position: -440px -600px; }
-.emoji-1F4B4 { background-position: -460px -600px; }
-.emoji-1F4B5 { background-position: -480px -600px; }
-.emoji-1F4B6 { background-position: -500px -600px; }
-.emoji-1F4B7 { background-position: -520px -600px; }
-.emoji-1F4B8 { background-position: -540px -600px; }
-.emoji-1F4B9 { background-position: -560px -600px; }
-.emoji-1F4BA { background-position: -580px -600px; }
-.emoji-1F4BB { background-position: -600px -600px; }
-.emoji-1F4BC { background-position: -620px 0; }
-.emoji-1F4BD { background-position: -620px -20px; }
-.emoji-1F4BE { background-position: -620px -40px; }
-.emoji-1F4BF { background-position: -620px -60px; }
-.emoji-1F4C0 { background-position: -620px -80px; }
-.emoji-1F4C1 { background-position: -620px -100px; }
-.emoji-1F4C2 { background-position: -620px -120px; }
-.emoji-1F4C3 { background-position: -620px -140px; }
-.emoji-1F4C4 { background-position: -620px -160px; }
-.emoji-1F4C5 { background-position: -620px -180px; }
-.emoji-1F4C6 { background-position: -620px -200px; }
-.emoji-1F4C7 { background-position: -620px -220px; }
-.emoji-1F4C8 { background-position: -620px -240px; }
-.emoji-1F4C9 { background-position: -620px -260px; }
-.emoji-1F4CA { background-position: -620px -280px; }
-.emoji-1F4CB { background-position: -620px -300px; }
-.emoji-1F4CC { background-position: -620px -320px; }
-.emoji-1F4CD { background-position: -620px -340px; }
-.emoji-1F4CE { background-position: -620px -360px; }
-.emoji-1F4CF { background-position: -620px -380px; }
-.emoji-1F4D0 { background-position: -620px -400px; }
-.emoji-1F4D1 { background-position: -620px -420px; }
-.emoji-1F4D2 { background-position: -620px -440px; }
-.emoji-1F4D3 { background-position: -620px -460px; }
-.emoji-1F4D4 { background-position: -620px -480px; }
-.emoji-1F4D5 { background-position: -620px -500px; }
-.emoji-1F4D6 { background-position: -620px -520px; }
-.emoji-1F4D7 { background-position: -620px -540px; }
-.emoji-1F4D8 { background-position: -620px -560px; }
-.emoji-1F4D9 { background-position: -620px -580px; }
-.emoji-1F4DA { background-position: -620px -600px; }
-.emoji-1F4DB { background-position: 0 -620px; }
-.emoji-1F4DC { background-position: -20px -620px; }
-.emoji-1F4DD { background-position: -40px -620px; }
-.emoji-1F4DE { background-position: -60px -620px; }
-.emoji-1F4DF { background-position: -80px -620px; }
-.emoji-1F4E0 { background-position: -100px -620px; }
-.emoji-1F4E1 { background-position: -120px -620px; }
-.emoji-1F4E2 { background-position: -140px -620px; }
-.emoji-1F4E3 { background-position: -160px -620px; }
-.emoji-1F4E4 { background-position: -180px -620px; }
-.emoji-1F4E5 { background-position: -200px -620px; }
-.emoji-1F4E6 { background-position: -220px -620px; }
-.emoji-1F4E7 { background-position: -240px -620px; }
-.emoji-1F4E8 { background-position: -260px -620px; }
-.emoji-1F4E9 { background-position: -280px -620px; }
-.emoji-1F4EA { background-position: -300px -620px; }
-.emoji-1F4EB { background-position: -320px -620px; }
-.emoji-1F4EC { background-position: -340px -620px; }
-.emoji-1F4ED { background-position: -360px -620px; }
-.emoji-1F4EE { background-position: -380px -620px; }
-.emoji-1F4EF { background-position: -400px -620px; }
-.emoji-1F4F0 { background-position: -420px -620px; }
-.emoji-1F4F1 { background-position: -440px -620px; }
-.emoji-1F4F2 { background-position: -460px -620px; }
-.emoji-1F4F3 { background-position: -480px -620px; }
-.emoji-1F4F4 { background-position: -500px -620px; }
-.emoji-1F4F5 { background-position: -520px -620px; }
-.emoji-1F4F6 { background-position: -540px -620px; }
-.emoji-1F4F7 { background-position: -560px -620px; }
-.emoji-1F4F8 { background-position: -580px -620px; }
-.emoji-1F4F9 { background-position: -600px -620px; }
-.emoji-1F4FA { background-position: -620px -620px; }
-.emoji-1F4FB { background-position: -640px 0; }
-.emoji-1F4FC { background-position: -640px -20px; }
-.emoji-1F4FD { background-position: -640px -40px; }
-.emoji-1F4FF { background-position: -640px -60px; }
-.emoji-1F500 { background-position: -640px -80px; }
-.emoji-1F501 { background-position: -640px -100px; }
-.emoji-1F502 { background-position: -640px -120px; }
-.emoji-1F503 { background-position: -640px -140px; }
-.emoji-1F504 { background-position: -640px -160px; }
-.emoji-1F505 { background-position: -640px -180px; }
-.emoji-1F506 { background-position: -640px -200px; }
-.emoji-1F507 { background-position: -640px -220px; }
-.emoji-1F508 { background-position: -640px -240px; }
-.emoji-1F509 { background-position: -640px -260px; }
-.emoji-1F50A { background-position: -640px -280px; }
-.emoji-1F50B { background-position: -640px -300px; }
-.emoji-1F50C { background-position: -640px -320px; }
-.emoji-1F50D { background-position: -640px -340px; }
-.emoji-1F50E { background-position: -640px -360px; }
-.emoji-1F50F { background-position: -640px -380px; }
-.emoji-1F510 { background-position: -640px -400px; }
-.emoji-1F511 { background-position: -640px -420px; }
-.emoji-1F512 { background-position: -640px -440px; }
-.emoji-1F513 { background-position: -640px -460px; }
-.emoji-1F514 { background-position: -640px -480px; }
-.emoji-1F515 { background-position: -640px -500px; }
-.emoji-1F516 { background-position: -640px -520px; }
-.emoji-1F517 { background-position: -640px -540px; }
-.emoji-1F518 { background-position: -640px -560px; }
-.emoji-1F519 { background-position: -640px -580px; }
-.emoji-1F51A { background-position: -640px -600px; }
-.emoji-1F51B { background-position: -640px -620px; }
-.emoji-1F51C { background-position: 0 -640px; }
-.emoji-1F51D { background-position: -20px -640px; }
-.emoji-1F51E { background-position: -40px -640px; }
-.emoji-1F51F { background-position: -60px -640px; }
-.emoji-1F520 { background-position: -80px -640px; }
-.emoji-1F521 { background-position: -100px -640px; }
-.emoji-1F522 { background-position: -120px -640px; }
-.emoji-1F523 { background-position: -140px -640px; }
-.emoji-1F524 { background-position: -160px -640px; }
-.emoji-1F525 { background-position: -180px -640px; }
-.emoji-1F526 { background-position: -200px -640px; }
-.emoji-1F527 { background-position: -220px -640px; }
-.emoji-1F528 { background-position: -240px -640px; }
-.emoji-1F529 { background-position: -260px -640px; }
-.emoji-1F52A { background-position: -280px -640px; }
-.emoji-1F52B { background-position: -300px -640px; }
-.emoji-1F52C { background-position: -320px -640px; }
-.emoji-1F52D { background-position: -340px -640px; }
-.emoji-1F52E { background-position: -360px -640px; }
-.emoji-1F52F { background-position: -380px -640px; }
-.emoji-1F530 { background-position: -400px -640px; }
-.emoji-1F531 { background-position: -420px -640px; }
-.emoji-1F532 { background-position: -440px -640px; }
-.emoji-1F533 { background-position: -460px -640px; }
-.emoji-1F534 { background-position: -480px -640px; }
-.emoji-1F535 { background-position: -500px -640px; }
-.emoji-1F536 { background-position: -520px -640px; }
-.emoji-1F537 { background-position: -540px -640px; }
-.emoji-1F538 { background-position: -560px -640px; }
-.emoji-1F539 { background-position: -580px -640px; }
-.emoji-1F53A { background-position: -600px -640px; }
-.emoji-1F53B { background-position: -620px -640px; }
-.emoji-1F53C { background-position: -640px -640px; }
-.emoji-1F53D { background-position: -660px 0; }
-.emoji-1F549 { background-position: -660px -20px; }
-.emoji-1F54A { background-position: -660px -40px; }
-.emoji-1F54B { background-position: -660px -60px; }
-.emoji-1F54C { background-position: -660px -80px; }
-.emoji-1F54D { background-position: -660px -100px; }
-.emoji-1F54E { background-position: -660px -120px; }
-.emoji-1F550 { background-position: -660px -140px; }
-.emoji-1F551 { background-position: -660px -160px; }
-.emoji-1F552 { background-position: -660px -180px; }
-.emoji-1F553 { background-position: -660px -200px; }
-.emoji-1F554 { background-position: -660px -220px; }
-.emoji-1F555 { background-position: -660px -240px; }
-.emoji-1F556 { background-position: -660px -260px; }
-.emoji-1F557 { background-position: -660px -280px; }
-.emoji-1F558 { background-position: -660px -300px; }
-.emoji-1F559 { background-position: -660px -320px; }
-.emoji-1F55A { background-position: -660px -340px; }
-.emoji-1F55B { background-position: -660px -360px; }
-.emoji-1F55C { background-position: -660px -380px; }
-.emoji-1F55D { background-position: -660px -400px; }
-.emoji-1F55E { background-position: -660px -420px; }
-.emoji-1F55F { background-position: -660px -440px; }
-.emoji-1F560 { background-position: -660px -460px; }
-.emoji-1F561 { background-position: -660px -480px; }
-.emoji-1F562 { background-position: -660px -500px; }
-.emoji-1F563 { background-position: -660px -520px; }
-.emoji-1F564 { background-position: -660px -540px; }
-.emoji-1F565 { background-position: -660px -560px; }
-.emoji-1F566 { background-position: -660px -580px; }
-.emoji-1F567 { background-position: -660px -600px; }
-.emoji-1F56F { background-position: -660px -620px; }
-.emoji-1F570 { background-position: -660px -640px; }
-.emoji-1F573 { background-position: 0 -660px; }
-.emoji-1F574 { background-position: -20px -660px; }
-.emoji-1F575 { background-position: -40px -660px; }
-.emoji-1F575-1F3FB { background-position: -60px -660px; }
-.emoji-1F575-1F3FC { background-position: -80px -660px; }
-.emoji-1F575-1F3FD { background-position: -100px -660px; }
-.emoji-1F575-1F3FE { background-position: -120px -660px; }
-.emoji-1F575-1F3FF { background-position: -140px -660px; }
-.emoji-1F576 { background-position: -160px -660px; }
-.emoji-1F577 { background-position: -180px -660px; }
-.emoji-1F578 { background-position: -200px -660px; }
-.emoji-1F579 { background-position: -220px -660px; }
-.emoji-1F57A { background-position: -240px -660px; }
-.emoji-1F57A-1F3FB { background-position: -260px -660px; }
-.emoji-1F57A-1F3FC { background-position: -280px -660px; }
-.emoji-1F57A-1F3FD { background-position: -300px -660px; }
-.emoji-1F57A-1F3FE { background-position: -320px -660px; }
-.emoji-1F57A-1F3FF { background-position: -340px -660px; }
-.emoji-1F587 { background-position: -360px -660px; }
-.emoji-1F58A { background-position: -380px -660px; }
-.emoji-1F58B { background-position: -400px -660px; }
-.emoji-1F58C { background-position: -420px -660px; }
-.emoji-1F58D { background-position: -440px -660px; }
-.emoji-1F590 { background-position: -460px -660px; }
-.emoji-1F590-1F3FB { background-position: -480px -660px; }
-.emoji-1F590-1F3FC { background-position: -500px -660px; }
-.emoji-1F590-1F3FD { background-position: -520px -660px; }
-.emoji-1F590-1F3FE { background-position: -540px -660px; }
-.emoji-1F590-1F3FF { background-position: -560px -660px; }
-.emoji-1F595 { background-position: -580px -660px; }
-.emoji-1F595-1F3FB { background-position: -600px -660px; }
-.emoji-1F595-1F3FC { background-position: -620px -660px; }
-.emoji-1F595-1F3FD { background-position: -640px -660px; }
-.emoji-1F595-1F3FE { background-position: -660px -660px; }
-.emoji-1F595-1F3FF { background-position: -680px 0; }
-.emoji-1F596 { background-position: -680px -20px; }
-.emoji-1F596-1F3FB { background-position: -680px -40px; }
-.emoji-1F596-1F3FC { background-position: -680px -60px; }
-.emoji-1F596-1F3FD { background-position: -680px -80px; }
-.emoji-1F596-1F3FE { background-position: -680px -100px; }
-.emoji-1F596-1F3FF { background-position: -680px -120px; }
-.emoji-1F5A4 { background-position: -680px -140px; }
-.emoji-1F5A5 { background-position: -680px -160px; }
-.emoji-1F5A8 { background-position: -680px -180px; }
-.emoji-1F5B1 { background-position: -680px -200px; }
-.emoji-1F5B2 { background-position: -680px -220px; }
-.emoji-1F5BC { background-position: -680px -240px; }
-.emoji-1F5C2 { background-position: -680px -260px; }
-.emoji-1F5C3 { background-position: -680px -280px; }
-.emoji-1F5C4 { background-position: -680px -300px; }
-.emoji-1F5D1 { background-position: -680px -320px; }
-.emoji-1F5D2 { background-position: -680px -340px; }
-.emoji-1F5D3 { background-position: -680px -360px; }
-.emoji-1F5DC { background-position: -680px -380px; }
-.emoji-1F5DD { background-position: -680px -400px; }
-.emoji-1F5DE { background-position: -680px -420px; }
-.emoji-1F5E1 { background-position: -680px -440px; }
-.emoji-1F5E3 { background-position: -680px -460px; }
-.emoji-1F5EF { background-position: -680px -480px; }
-.emoji-1F5F3 { background-position: -680px -500px; }
-.emoji-1F5FA { background-position: -680px -520px; }
-.emoji-1F5FB { background-position: -680px -540px; }
-.emoji-1F5FC { background-position: -680px -560px; }
-.emoji-1F5FD { background-position: -680px -580px; }
-.emoji-1F5FE { background-position: -680px -600px; }
-.emoji-1F5FF { background-position: -680px -620px; }
-.emoji-1F600 { background-position: -680px -640px; }
-.emoji-1F601 { background-position: -680px -660px; }
-.emoji-1F602 { background-position: 0 -680px; }
-.emoji-1F603 { background-position: -20px -680px; }
-.emoji-1F604 { background-position: -40px -680px; }
-.emoji-1F605 { background-position: -60px -680px; }
-.emoji-1F606 { background-position: -80px -680px; }
-.emoji-1F607 { background-position: -100px -680px; }
-.emoji-1F608 { background-position: -120px -680px; }
-.emoji-1F609 { background-position: -140px -680px; }
-.emoji-1F60A { background-position: -160px -680px; }
-.emoji-1F60B { background-position: -180px -680px; }
-.emoji-1F60C { background-position: -200px -680px; }
-.emoji-1F60D { background-position: -220px -680px; }
-.emoji-1F60E { background-position: -240px -680px; }
-.emoji-1F60F { background-position: -260px -680px; }
-.emoji-1F610 { background-position: -280px -680px; }
-.emoji-1F611 { background-position: -300px -680px; }
-.emoji-1F612 { background-position: -320px -680px; }
-.emoji-1F613 { background-position: -340px -680px; }
-.emoji-1F614 { background-position: -360px -680px; }
-.emoji-1F615 { background-position: -380px -680px; }
-.emoji-1F616 { background-position: -400px -680px; }
-.emoji-1F617 { background-position: -420px -680px; }
-.emoji-1F618 { background-position: -440px -680px; }
-.emoji-1F619 { background-position: -460px -680px; }
-.emoji-1F61A { background-position: -480px -680px; }
-.emoji-1F61B { background-position: -500px -680px; }
-.emoji-1F61C { background-position: -520px -680px; }
-.emoji-1F61D { background-position: -540px -680px; }
-.emoji-1F61E { background-position: -560px -680px; }
-.emoji-1F61F { background-position: -580px -680px; }
-.emoji-1F620 { background-position: -600px -680px; }
-.emoji-1F621 { background-position: -620px -680px; }
-.emoji-1F622 { background-position: -640px -680px; }
-.emoji-1F623 { background-position: -660px -680px; }
-.emoji-1F624 { background-position: -680px -680px; }
-.emoji-1F625 { background-position: -700px 0; }
-.emoji-1F626 { background-position: -700px -20px; }
-.emoji-1F627 { background-position: -700px -40px; }
-.emoji-1F628 { background-position: -700px -60px; }
-.emoji-1F629 { background-position: -700px -80px; }
-.emoji-1F62A { background-position: -700px -100px; }
-.emoji-1F62B { background-position: -700px -120px; }
-.emoji-1F62C { background-position: -700px -140px; }
-.emoji-1F62D { background-position: -700px -160px; }
-.emoji-1F62E { background-position: -700px -180px; }
-.emoji-1F62F { background-position: -700px -200px; }
-.emoji-1F630 { background-position: -700px -220px; }
-.emoji-1F631 { background-position: -700px -240px; }
-.emoji-1F632 { background-position: -700px -260px; }
-.emoji-1F633 { background-position: -700px -280px; }
-.emoji-1F634 { background-position: -700px -300px; }
-.emoji-1F635 { background-position: -700px -320px; }
-.emoji-1F636 { background-position: -700px -340px; }
-.emoji-1F637 { background-position: -700px -360px; }
-.emoji-1F638 { background-position: -700px -380px; }
-.emoji-1F639 { background-position: -700px -400px; }
-.emoji-1F63A { background-position: -700px -420px; }
-.emoji-1F63B { background-position: -700px -440px; }
-.emoji-1F63C { background-position: -700px -460px; }
-.emoji-1F63D { background-position: -700px -480px; }
-.emoji-1F63E { background-position: -700px -500px; }
-.emoji-1F63F { background-position: -700px -520px; }
-.emoji-1F640 { background-position: -700px -540px; }
-.emoji-1F641 { background-position: -700px -560px; }
-.emoji-1F642 { background-position: -700px -580px; }
-.emoji-1F643 { background-position: -700px -600px; }
-.emoji-1F644 { background-position: -700px -620px; }
-.emoji-1F645 { background-position: -700px -640px; }
-.emoji-1F645-1F3FB { background-position: -700px -660px; }
-.emoji-1F645-1F3FC { background-position: -700px -680px; }
-.emoji-1F645-1F3FD { background-position: 0 -700px; }
-.emoji-1F645-1F3FE { background-position: -20px -700px; }
-.emoji-1F645-1F3FF { background-position: -40px -700px; }
-.emoji-1F646 { background-position: -60px -700px; }
-.emoji-1F646-1F3FB { background-position: -80px -700px; }
-.emoji-1F646-1F3FC { background-position: -100px -700px; }
-.emoji-1F646-1F3FD { background-position: -120px -700px; }
-.emoji-1F646-1F3FE { background-position: -140px -700px; }
-.emoji-1F646-1F3FF { background-position: -160px -700px; }
-.emoji-1F647 { background-position: -180px -700px; }
-.emoji-1F647-1F3FB { background-position: -200px -700px; }
-.emoji-1F647-1F3FC { background-position: -220px -700px; }
-.emoji-1F647-1F3FD { background-position: -240px -700px; }
-.emoji-1F647-1F3FE { background-position: -260px -700px; }
-.emoji-1F647-1F3FF { background-position: -280px -700px; }
-.emoji-1F648 { background-position: -300px -700px; }
-.emoji-1F649 { background-position: -320px -700px; }
-.emoji-1F64A { background-position: -340px -700px; }
-.emoji-1F64B { background-position: -360px -700px; }
-.emoji-1F64B-1F3FB { background-position: -380px -700px; }
-.emoji-1F64B-1F3FC { background-position: -400px -700px; }
-.emoji-1F64B-1F3FD { background-position: -420px -700px; }
-.emoji-1F64B-1F3FE { background-position: -440px -700px; }
-.emoji-1F64B-1F3FF { background-position: -460px -700px; }
-.emoji-1F64C { background-position: -480px -700px; }
-.emoji-1F64C-1F3FB { background-position: -500px -700px; }
-.emoji-1F64C-1F3FC { background-position: -520px -700px; }
-.emoji-1F64C-1F3FD { background-position: -540px -700px; }
-.emoji-1F64C-1F3FE { background-position: -560px -700px; }
-.emoji-1F64C-1F3FF { background-position: -580px -700px; }
-.emoji-1F64D { background-position: -600px -700px; }
-.emoji-1F64D-1F3FB { background-position: -620px -700px; }
-.emoji-1F64D-1F3FC { background-position: -640px -700px; }
-.emoji-1F64D-1F3FD { background-position: -660px -700px; }
-.emoji-1F64D-1F3FE { background-position: -680px -700px; }
-.emoji-1F64D-1F3FF { background-position: -700px -700px; }
-.emoji-1F64E { background-position: -720px 0; }
-.emoji-1F64E-1F3FB { background-position: -720px -20px; }
-.emoji-1F64E-1F3FC { background-position: -720px -40px; }
-.emoji-1F64E-1F3FD { background-position: -720px -60px; }
-.emoji-1F64E-1F3FE { background-position: -720px -80px; }
-.emoji-1F64E-1F3FF { background-position: -720px -100px; }
-.emoji-1F64F { background-position: -720px -120px; }
-.emoji-1F64F-1F3FB { background-position: -720px -140px; }
-.emoji-1F64F-1F3FC { background-position: -720px -160px; }
-.emoji-1F64F-1F3FD { background-position: -720px -180px; }
-.emoji-1F64F-1F3FE { background-position: -720px -200px; }
-.emoji-1F64F-1F3FF { background-position: -720px -220px; }
-.emoji-1F680 { background-position: -720px -240px; }
-.emoji-1F681 { background-position: -720px -260px; }
-.emoji-1F682 { background-position: -720px -280px; }
-.emoji-1F683 { background-position: -720px -300px; }
-.emoji-1F684 { background-position: -720px -320px; }
-.emoji-1F685 { background-position: -720px -340px; }
-.emoji-1F686 { background-position: -720px -360px; }
-.emoji-1F687 { background-position: -720px -380px; }
-.emoji-1F688 { background-position: -720px -400px; }
-.emoji-1F689 { background-position: -720px -420px; }
-.emoji-1F68A { background-position: -720px -440px; }
-.emoji-1F68B { background-position: -720px -460px; }
-.emoji-1F68C { background-position: -720px -480px; }
-.emoji-1F68D { background-position: -720px -500px; }
-.emoji-1F68E { background-position: -720px -520px; }
-.emoji-1F68F { background-position: -720px -540px; }
-.emoji-1F690 { background-position: -720px -560px; }
-.emoji-1F691 { background-position: -720px -580px; }
-.emoji-1F692 { background-position: -720px -600px; }
-.emoji-1F693 { background-position: -720px -620px; }
-.emoji-1F694 { background-position: -720px -640px; }
-.emoji-1F695 { background-position: -720px -660px; }
-.emoji-1F696 { background-position: -720px -680px; }
-.emoji-1F697 { background-position: -720px -700px; }
-.emoji-1F698 { background-position: 0 -720px; }
-.emoji-1F699 { background-position: -20px -720px; }
-.emoji-1F69A { background-position: -40px -720px; }
-.emoji-1F69B { background-position: -60px -720px; }
-.emoji-1F69C { background-position: -80px -720px; }
-.emoji-1F69D { background-position: -100px -720px; }
-.emoji-1F69E { background-position: -120px -720px; }
-.emoji-1F69F { background-position: -140px -720px; }
-.emoji-1F6A0 { background-position: -160px -720px; }
-.emoji-1F6A1 { background-position: -180px -720px; }
-.emoji-1F6A2 { background-position: -200px -720px; }
-.emoji-1F6A3 { background-position: -220px -720px; }
-.emoji-1F6A3-1F3FB { background-position: -240px -720px; }
-.emoji-1F6A3-1F3FC { background-position: -260px -720px; }
-.emoji-1F6A3-1F3FD { background-position: -280px -720px; }
-.emoji-1F6A3-1F3FE { background-position: -300px -720px; }
-.emoji-1F6A3-1F3FF { background-position: -320px -720px; }
-.emoji-1F6A4 { background-position: -340px -720px; }
-.emoji-1F6A5 { background-position: -360px -720px; }
-.emoji-1F6A6 { background-position: -380px -720px; }
-.emoji-1F6A7 { background-position: -400px -720px; }
-.emoji-1F6A8 { background-position: -420px -720px; }
-.emoji-1F6A9 { background-position: -440px -720px; }
-.emoji-1F6AA { background-position: -460px -720px; }
-.emoji-1F6AB { background-position: -480px -720px; }
-.emoji-1F6AC { background-position: -500px -720px; }
-.emoji-1F6AD { background-position: -520px -720px; }
-.emoji-1F6AE { background-position: -540px -720px; }
-.emoji-1F6AF { background-position: -560px -720px; }
-.emoji-1F6B0 { background-position: -580px -720px; }
-.emoji-1F6B1 { background-position: -600px -720px; }
-.emoji-1F6B2 { background-position: -620px -720px; }
-.emoji-1F6B3 { background-position: -640px -720px; }
-.emoji-1F6B4 { background-position: -660px -720px; }
-.emoji-1F6B4-1F3FB { background-position: -680px -720px; }
-.emoji-1F6B4-1F3FC { background-position: -700px -720px; }
-.emoji-1F6B4-1F3FD { background-position: -720px -720px; }
-.emoji-1F6B4-1F3FE { background-position: -740px 0; }
-.emoji-1F6B4-1F3FF { background-position: -740px -20px; }
-.emoji-1F6B5 { background-position: -740px -40px; }
-.emoji-1F6B5-1F3FB { background-position: -740px -60px; }
-.emoji-1F6B5-1F3FC { background-position: -740px -80px; }
-.emoji-1F6B5-1F3FD { background-position: -740px -100px; }
-.emoji-1F6B5-1F3FE { background-position: -740px -120px; }
-.emoji-1F6B5-1F3FF { background-position: -740px -140px; }
-.emoji-1F6B6 { background-position: -740px -160px; }
-.emoji-1F6B6-1F3FB { background-position: -740px -180px; }
-.emoji-1F6B6-1F3FC { background-position: -740px -200px; }
-.emoji-1F6B6-1F3FD { background-position: -740px -220px; }
-.emoji-1F6B6-1F3FE { background-position: -740px -240px; }
-.emoji-1F6B6-1F3FF { background-position: -740px -260px; }
-.emoji-1F6B7 { background-position: -740px -280px; }
-.emoji-1F6B8 { background-position: -740px -300px; }
-.emoji-1F6B9 { background-position: -740px -320px; }
-.emoji-1F6BA { background-position: -740px -340px; }
-.emoji-1F6BB { background-position: -740px -360px; }
-.emoji-1F6BC { background-position: -740px -380px; }
-.emoji-1F6BD { background-position: -740px -400px; }
-.emoji-1F6BE { background-position: -740px -420px; }
-.emoji-1F6BF { background-position: -740px -440px; }
-.emoji-1F6C0 { background-position: -740px -460px; }
-.emoji-1F6C0-1F3FB { background-position: -740px -480px; }
-.emoji-1F6C0-1F3FC { background-position: -740px -500px; }
-.emoji-1F6C0-1F3FD { background-position: -740px -520px; }
-.emoji-1F6C0-1F3FE { background-position: -740px -540px; }
-.emoji-1F6C0-1F3FF { background-position: -740px -560px; }
-.emoji-1F6C1 { background-position: -740px -580px; }
-.emoji-1F6C2 { background-position: -740px -600px; }
-.emoji-1F6C3 { background-position: -740px -620px; }
-.emoji-1F6C4 { background-position: -740px -640px; }
-.emoji-1F6C5 { background-position: -740px -660px; }
-.emoji-1F6CB { background-position: -740px -680px; }
-.emoji-1F6CC { background-position: -740px -700px; }
-.emoji-1F6CD { background-position: -740px -720px; }
-.emoji-1F6CE { background-position: 0 -740px; }
-.emoji-1F6CF { background-position: -20px -740px; }
-.emoji-1F6D0 { background-position: -40px -740px; }
-.emoji-1F6D1 { background-position: -60px -740px; }
-.emoji-1F6D2 { background-position: -80px -740px; }
-.emoji-1F6E0 { background-position: -100px -740px; }
-.emoji-1F6E1 { background-position: -120px -740px; }
-.emoji-1F6E2 { background-position: -140px -740px; }
-.emoji-1F6E3 { background-position: -160px -740px; }
-.emoji-1F6E4 { background-position: -180px -740px; }
-.emoji-1F6E5 { background-position: -200px -740px; }
-.emoji-1F6E9 { background-position: -220px -740px; }
-.emoji-1F6EB { background-position: -240px -740px; }
-.emoji-1F6EC { background-position: -260px -740px; }
-.emoji-1F6F0 { background-position: -280px -740px; }
-.emoji-1F6F3 { background-position: -300px -740px; }
-.emoji-1F6F4 { background-position: -320px -740px; }
-.emoji-1F6F5 { background-position: -340px -740px; }
-.emoji-1F6F6 { background-position: -360px -740px; }
-.emoji-1F910 { background-position: -380px -740px; }
-.emoji-1F911 { background-position: -400px -740px; }
-.emoji-1F912 { background-position: -420px -740px; }
-.emoji-1F913 { background-position: -440px -740px; }
-.emoji-1F914 { background-position: -460px -740px; }
-.emoji-1F915 { background-position: -480px -740px; }
-.emoji-1F916 { background-position: -500px -740px; }
-.emoji-1F917 { background-position: -520px -740px; }
-.emoji-1F918 { background-position: -540px -740px; }
-.emoji-1F918-1F3FB { background-position: -560px -740px; }
-.emoji-1F918-1F3FC { background-position: -580px -740px; }
-.emoji-1F918-1F3FD { background-position: -600px -740px; }
-.emoji-1F918-1F3FE { background-position: -620px -740px; }
-.emoji-1F918-1F3FF { background-position: -640px -740px; }
-.emoji-1F919 { background-position: -660px -740px; }
-.emoji-1F919-1F3FB { background-position: -680px -740px; }
-.emoji-1F919-1F3FC { background-position: -700px -740px; }
-.emoji-1F919-1F3FD { background-position: -720px -740px; }
-.emoji-1F919-1F3FE { background-position: -740px -740px; }
-.emoji-1F919-1F3FF { background-position: -760px 0; }
-.emoji-1F91A { background-position: -760px -20px; }
-.emoji-1F91A-1F3FB { background-position: -760px -40px; }
-.emoji-1F91A-1F3FC { background-position: -760px -60px; }
-.emoji-1F91A-1F3FD { background-position: -760px -80px; }
-.emoji-1F91A-1F3FE { background-position: -760px -100px; }
-.emoji-1F91A-1F3FF { background-position: -760px -120px; }
-.emoji-1F91B { background-position: -760px -140px; }
-.emoji-1F91B-1F3FB { background-position: -760px -160px; }
-.emoji-1F91B-1F3FC { background-position: -760px -180px; }
-.emoji-1F91B-1F3FD { background-position: -760px -200px; }
-.emoji-1F91B-1F3FE { background-position: -760px -220px; }
-.emoji-1F91B-1F3FF { background-position: -760px -240px; }
-.emoji-1F91C { background-position: -760px -260px; }
-.emoji-1F91C-1F3FB { background-position: -760px -280px; }
-.emoji-1F91C-1F3FC { background-position: -760px -300px; }
-.emoji-1F91C-1F3FD { background-position: -760px -320px; }
-.emoji-1F91C-1F3FE { background-position: -760px -340px; }
-.emoji-1F91C-1F3FF { background-position: -760px -360px; }
-.emoji-1F91D { background-position: -760px -380px; }
-.emoji-1F91D-1F3FB { background-position: -760px -400px; }
-.emoji-1F91D-1F3FC { background-position: -760px -420px; }
-.emoji-1F91D-1F3FD { background-position: -760px -440px; }
-.emoji-1F91D-1F3FE { background-position: -760px -460px; }
-.emoji-1F91D-1F3FF { background-position: -760px -480px; }
-.emoji-1F91E { background-position: -760px -500px; }
-.emoji-1F91E-1F3FB { background-position: -760px -520px; }
-.emoji-1F91E-1F3FC { background-position: -760px -540px; }
-.emoji-1F91E-1F3FD { background-position: -760px -560px; }
-.emoji-1F91E-1F3FE { background-position: -760px -580px; }
-.emoji-1F91E-1F3FF { background-position: -760px -600px; }
-.emoji-1F920 { background-position: -760px -620px; }
-.emoji-1F921 { background-position: -760px -640px; }
-.emoji-1F922 { background-position: -760px -660px; }
-.emoji-1F923 { background-position: -760px -680px; }
-.emoji-1F924 { background-position: -760px -700px; }
-.emoji-1F925 { background-position: -760px -720px; }
-.emoji-1F926 { background-position: -760px -740px; }
-.emoji-1F926-1F3FB { background-position: 0 -760px; }
-.emoji-1F926-1F3FC { background-position: -20px -760px; }
-.emoji-1F926-1F3FD { background-position: -40px -760px; }
-.emoji-1F926-1F3FE { background-position: -60px -760px; }
-.emoji-1F926-1F3FF { background-position: -80px -760px; }
-.emoji-1F927 { background-position: -100px -760px; }
-.emoji-1F930 { background-position: -120px -760px; }
-.emoji-1F930-1F3FB { background-position: -140px -760px; }
-.emoji-1F930-1F3FC { background-position: -160px -760px; }
-.emoji-1F930-1F3FD { background-position: -180px -760px; }
-.emoji-1F930-1F3FE { background-position: -200px -760px; }
-.emoji-1F930-1F3FF { background-position: -220px -760px; }
-.emoji-1F933 { background-position: -240px -760px; }
-.emoji-1F933-1F3FB { background-position: -260px -760px; }
-.emoji-1F933-1F3FC { background-position: -280px -760px; }
-.emoji-1F933-1F3FD { background-position: -300px -760px; }
-.emoji-1F933-1F3FE { background-position: -320px -760px; }
-.emoji-1F933-1F3FF { background-position: -340px -760px; }
-.emoji-1F934 { background-position: -360px -760px; }
-.emoji-1F934-1F3FB { background-position: -380px -760px; }
-.emoji-1F934-1F3FC { background-position: -400px -760px; }
-.emoji-1F934-1F3FD { background-position: -420px -760px; }
-.emoji-1F934-1F3FE { background-position: -440px -760px; }
-.emoji-1F934-1F3FF { background-position: -460px -760px; }
-.emoji-1F935 { background-position: -480px -760px; }
-.emoji-1F935-1F3FB { background-position: -500px -760px; }
-.emoji-1F935-1F3FC { background-position: -520px -760px; }
-.emoji-1F935-1F3FD { background-position: -540px -760px; }
-.emoji-1F935-1F3FE { background-position: -560px -760px; }
-.emoji-1F935-1F3FF { background-position: -580px -760px; }
-.emoji-1F936 { background-position: -600px -760px; }
-.emoji-1F936-1F3FB { background-position: -620px -760px; }
-.emoji-1F936-1F3FC { background-position: -640px -760px; }
-.emoji-1F936-1F3FD { background-position: -660px -760px; }
-.emoji-1F936-1F3FE { background-position: -680px -760px; }
-.emoji-1F936-1F3FF { background-position: -700px -760px; }
-.emoji-1F937 { background-position: -720px -760px; }
-.emoji-1F937-1F3FB { background-position: -740px -760px; }
-.emoji-1F937-1F3FC { background-position: -760px -760px; }
-.emoji-1F937-1F3FD { background-position: -780px 0; }
-.emoji-1F937-1F3FE { background-position: -780px -20px; }
-.emoji-1F937-1F3FF { background-position: -780px -40px; }
-.emoji-1F938 { background-position: -780px -60px; }
-.emoji-1F938-1F3FB { background-position: -780px -80px; }
-.emoji-1F938-1F3FC { background-position: -780px -100px; }
-.emoji-1F938-1F3FD { background-position: -780px -120px; }
-.emoji-1F938-1F3FE { background-position: -780px -140px; }
-.emoji-1F938-1F3FF { background-position: -780px -160px; }
-.emoji-1F939 { background-position: -780px -180px; }
-.emoji-1F939-1F3FB { background-position: -780px -200px; }
-.emoji-1F939-1F3FC { background-position: -780px -220px; }
-.emoji-1F939-1F3FD { background-position: -780px -240px; }
-.emoji-1F939-1F3FE { background-position: -780px -260px; }
-.emoji-1F939-1F3FF { background-position: -780px -280px; }
-.emoji-1F93A { background-position: -780px -300px; }
-.emoji-1F93C { background-position: -780px -320px; }
-.emoji-1F93C-1F3FB { background-position: -780px -340px; }
-.emoji-1F93C-1F3FC { background-position: -780px -360px; }
-.emoji-1F93C-1F3FD { background-position: -780px -380px; }
-.emoji-1F93C-1F3FE { background-position: -780px -400px; }
-.emoji-1F93C-1F3FF { background-position: -780px -420px; }
-.emoji-1F93D { background-position: -780px -440px; }
-.emoji-1F93D-1F3FB { background-position: -780px -460px; }
-.emoji-1F93D-1F3FC { background-position: -780px -480px; }
-.emoji-1F93D-1F3FD { background-position: -780px -500px; }
-.emoji-1F93D-1F3FE { background-position: -780px -520px; }
-.emoji-1F93D-1F3FF { background-position: -780px -540px; }
-.emoji-1F93E { background-position: -780px -560px; }
-.emoji-1F93E-1F3FB { background-position: -780px -580px; }
-.emoji-1F93E-1F3FC { background-position: -780px -600px; }
-.emoji-1F93E-1F3FD { background-position: -780px -620px; }
-.emoji-1F93E-1F3FE { background-position: -780px -640px; }
-.emoji-1F93E-1F3FF { background-position: -780px -660px; }
-.emoji-1F940 { background-position: -780px -680px; }
-.emoji-1F941 { background-position: -780px -700px; }
-.emoji-1F942 { background-position: -780px -720px; }
-.emoji-1F943 { background-position: -780px -740px; }
-.emoji-1F944 { background-position: -780px -760px; }
-.emoji-1F945 { background-position: 0 -780px; }
-.emoji-1F947 { background-position: -20px -780px; }
-.emoji-1F948 { background-position: -40px -780px; }
-.emoji-1F949 { background-position: -60px -780px; }
-.emoji-1F94A { background-position: -80px -780px; }
-.emoji-1F94B { background-position: -100px -780px; }
-.emoji-1F950 { background-position: -120px -780px; }
-.emoji-1F951 { background-position: -140px -780px; }
-.emoji-1F952 { background-position: -160px -780px; }
-.emoji-1F953 { background-position: -180px -780px; }
-.emoji-1F954 { background-position: -200px -780px; }
-.emoji-1F955 { background-position: -220px -780px; }
-.emoji-1F956 { background-position: -240px -780px; }
-.emoji-1F957 { background-position: -260px -780px; }
-.emoji-1F958 { background-position: -280px -780px; }
-.emoji-1F959 { background-position: -300px -780px; }
-.emoji-1F95A { background-position: -320px -780px; }
-.emoji-1F95B { background-position: -340px -780px; }
-.emoji-1F95C { background-position: -360px -780px; }
-.emoji-1F95D { background-position: -380px -780px; }
-.emoji-1F95E { background-position: -400px -780px; }
-.emoji-1F980 { background-position: -420px -780px; }
-.emoji-1F981 { background-position: -440px -780px; }
-.emoji-1F982 { background-position: -460px -780px; }
-.emoji-1F983 { background-position: -480px -780px; }
-.emoji-1F984 { background-position: -500px -780px; }
-.emoji-1F985 { background-position: -520px -780px; }
-.emoji-1F986 { background-position: -540px -780px; }
-.emoji-1F987 { background-position: -560px -780px; }
-.emoji-1F988 { background-position: -580px -780px; }
-.emoji-1F989 { background-position: -600px -780px; }
-.emoji-1F98A { background-position: -620px -780px; }
-.emoji-1F98B { background-position: -640px -780px; }
-.emoji-1F98C { background-position: -660px -780px; }
-.emoji-1F98D { background-position: -680px -780px; }
-.emoji-1F98E { background-position: -700px -780px; }
-.emoji-1F98F { background-position: -720px -780px; }
-.emoji-1F990 { background-position: -740px -780px; }
-.emoji-1F991 { background-position: -760px -780px; }
-.emoji-1F9C0 { background-position: -780px -780px; }
-.emoji-203C { background-position: -800px 0; }
-.emoji-2049 { background-position: -800px -20px; }
-.emoji-2122 { background-position: -800px -40px; }
-.emoji-2139 { background-position: -800px -60px; }
-.emoji-2194 { background-position: -800px -80px; }
-.emoji-2195 { background-position: -800px -100px; }
-.emoji-2196 { background-position: -800px -120px; }
-.emoji-2197 { background-position: -800px -140px; }
-.emoji-2198 { background-position: -800px -160px; }
-.emoji-2199 { background-position: -800px -180px; }
-.emoji-21A9 { background-position: -800px -200px; }
-.emoji-21AA { background-position: -800px -220px; }
-.emoji-231A { background-position: -800px -240px; }
-.emoji-231B { background-position: -800px -260px; }
-.emoji-2328 { background-position: -800px -280px; }
-.emoji-23CF { background-position: -800px -300px; }
-.emoji-23E9 { background-position: -800px -320px; }
-.emoji-23EA { background-position: -800px -340px; }
-.emoji-23EB { background-position: -800px -360px; }
-.emoji-23EC { background-position: -800px -380px; }
-.emoji-23ED { background-position: -800px -400px; }
-.emoji-23EE { background-position: -800px -420px; }
-.emoji-23EF { background-position: -800px -440px; }
-.emoji-23F0 { background-position: -800px -460px; }
-.emoji-23F1 { background-position: -800px -480px; }
-.emoji-23F2 { background-position: -800px -500px; }
-.emoji-23F3 { background-position: -800px -520px; }
-.emoji-23F8 { background-position: -800px -540px; }
-.emoji-23F9 { background-position: -800px -560px; }
-.emoji-23FA { background-position: -800px -580px; }
-.emoji-24C2 { background-position: -800px -600px; }
-.emoji-25AA { background-position: -800px -620px; }
-.emoji-25AB { background-position: -800px -640px; }
-.emoji-25B6 { background-position: -800px -660px; }
-.emoji-25C0 { background-position: -800px -680px; }
-.emoji-25FB { background-position: -800px -700px; }
-.emoji-25FC { background-position: -800px -720px; }
-.emoji-25FD { background-position: -800px -740px; }
-.emoji-25FE { background-position: -800px -760px; }
-.emoji-2600 { background-position: -800px -780px; }
-.emoji-2601 { background-position: 0 -800px; }
-.emoji-2602 { background-position: -20px -800px; }
-.emoji-2603 { background-position: -40px -800px; }
-.emoji-2604 { background-position: -60px -800px; }
-.emoji-260E { background-position: -80px -800px; }
-.emoji-2611 { background-position: -100px -800px; }
-.emoji-2614 { background-position: -120px -800px; }
-.emoji-2615 { background-position: -140px -800px; }
-.emoji-2618 { background-position: -160px -800px; }
-.emoji-261D { background-position: -180px -800px; }
-.emoji-261D-1F3FB { background-position: -200px -800px; }
-.emoji-261D-1F3FC { background-position: -220px -800px; }
-.emoji-261D-1F3FD { background-position: -240px -800px; }
-.emoji-261D-1F3FE { background-position: -260px -800px; }
-.emoji-261D-1F3FF { background-position: -280px -800px; }
-.emoji-2620 { background-position: -300px -800px; }
-.emoji-2622 { background-position: -320px -800px; }
-.emoji-2623 { background-position: -340px -800px; }
-.emoji-2626 { background-position: -360px -800px; }
-.emoji-262A { background-position: -380px -800px; }
-.emoji-262E { background-position: -400px -800px; }
-.emoji-262F { background-position: -420px -800px; }
-.emoji-2638 { background-position: -440px -800px; }
-.emoji-2639 { background-position: -460px -800px; }
-.emoji-263A { background-position: -480px -800px; }
-.emoji-2648 { background-position: -500px -800px; }
-.emoji-2649 { background-position: -520px -800px; }
-.emoji-264A { background-position: -540px -800px; }
-.emoji-264B { background-position: -560px -800px; }
-.emoji-264C { background-position: -580px -800px; }
-.emoji-264D { background-position: -600px -800px; }
-.emoji-264E { background-position: -620px -800px; }
-.emoji-264F { background-position: -640px -800px; }
-.emoji-2650 { background-position: -660px -800px; }
-.emoji-2651 { background-position: -680px -800px; }
-.emoji-2652 { background-position: -700px -800px; }
-.emoji-2653 { background-position: -720px -800px; }
-.emoji-2660 { background-position: -740px -800px; }
-.emoji-2663 { background-position: -760px -800px; }
-.emoji-2665 { background-position: -780px -800px; }
-.emoji-2666 { background-position: -800px -800px; }
-.emoji-2668 { background-position: -820px 0; }
-.emoji-267B { background-position: -820px -20px; }
-.emoji-267F { background-position: -820px -40px; }
-.emoji-2692 { background-position: -820px -60px; }
-.emoji-2693 { background-position: -820px -80px; }
-.emoji-2694 { background-position: -820px -100px; }
-.emoji-2696 { background-position: -820px -120px; }
-.emoji-2697 { background-position: -820px -140px; }
-.emoji-2699 { background-position: -820px -160px; }
-.emoji-269B { background-position: -820px -180px; }
-.emoji-269C { background-position: -820px -200px; }
-.emoji-26A0 { background-position: -820px -220px; }
-.emoji-26A1 { background-position: -820px -240px; }
-.emoji-26AA { background-position: -820px -260px; }
-.emoji-26AB { background-position: -820px -280px; }
-.emoji-26B0 { background-position: -820px -300px; }
-.emoji-26B1 { background-position: -820px -320px; }
-.emoji-26BD { background-position: -820px -340px; }
-.emoji-26BE { background-position: -820px -360px; }
-.emoji-26C4 { background-position: -820px -380px; }
-.emoji-26C5 { background-position: -820px -400px; }
-.emoji-26C8 { background-position: -820px -420px; }
-.emoji-26CE { background-position: -820px -440px; }
-.emoji-26CF { background-position: -820px -460px; }
-.emoji-26D1 { background-position: -820px -480px; }
-.emoji-26D3 { background-position: -820px -500px; }
-.emoji-26D4 { background-position: -820px -520px; }
-.emoji-26E9 { background-position: -820px -540px; }
-.emoji-26EA { background-position: -820px -560px; }
-.emoji-26F0 { background-position: -820px -580px; }
-.emoji-26F1 { background-position: -820px -600px; }
-.emoji-26F2 { background-position: -820px -620px; }
-.emoji-26F3 { background-position: -820px -640px; }
-.emoji-26F4 { background-position: -820px -660px; }
-.emoji-26F5 { background-position: -820px -680px; }
-.emoji-26F7 { background-position: -820px -700px; }
-.emoji-26F8 { background-position: -820px -720px; }
-.emoji-26F9 { background-position: -820px -740px; }
-.emoji-26F9-1F3FB { background-position: -820px -760px; }
-.emoji-26F9-1F3FC { background-position: -820px -780px; }
-.emoji-26F9-1F3FD { background-position: -820px -800px; }
-.emoji-26F9-1F3FE { background-position: 0 -820px; }
-.emoji-26F9-1F3FF { background-position: -20px -820px; }
-.emoji-26FA { background-position: -40px -820px; }
-.emoji-26FD { background-position: -60px -820px; }
-.emoji-2702 { background-position: -80px -820px; }
-.emoji-2705 { background-position: -100px -820px; }
-.emoji-2708 { background-position: -120px -820px; }
-.emoji-2709 { background-position: -140px -820px; }
-.emoji-270A { background-position: -160px -820px; }
-.emoji-270A-1F3FB { background-position: -180px -820px; }
-.emoji-270A-1F3FC { background-position: -200px -820px; }
-.emoji-270A-1F3FD { background-position: -220px -820px; }
-.emoji-270A-1F3FE { background-position: -240px -820px; }
-.emoji-270A-1F3FF { background-position: -260px -820px; }
-.emoji-270B { background-position: -280px -820px; }
-.emoji-270B-1F3FB { background-position: -300px -820px; }
-.emoji-270B-1F3FC { background-position: -320px -820px; }
-.emoji-270B-1F3FD { background-position: -340px -820px; }
-.emoji-270B-1F3FE { background-position: -360px -820px; }
-.emoji-270B-1F3FF { background-position: -380px -820px; }
-.emoji-270C { background-position: -400px -820px; }
-.emoji-270C-1F3FB { background-position: -420px -820px; }
-.emoji-270C-1F3FC { background-position: -440px -820px; }
-.emoji-270C-1F3FD { background-position: -460px -820px; }
-.emoji-270C-1F3FE { background-position: -480px -820px; }
-.emoji-270C-1F3FF { background-position: -500px -820px; }
-.emoji-270D { background-position: -520px -820px; }
-.emoji-270D-1F3FB { background-position: -540px -820px; }
-.emoji-270D-1F3FC { background-position: -560px -820px; }
-.emoji-270D-1F3FD { background-position: -580px -820px; }
-.emoji-270D-1F3FE { background-position: -600px -820px; }
-.emoji-270D-1F3FF { background-position: -620px -820px; }
-.emoji-270F { background-position: -640px -820px; }
-.emoji-2712 { background-position: -660px -820px; }
-.emoji-2714 { background-position: -680px -820px; }
-.emoji-2716 { background-position: -700px -820px; }
-.emoji-271D { background-position: -720px -820px; }
-.emoji-2721 { background-position: -740px -820px; }
-.emoji-2728 { background-position: -760px -820px; }
-.emoji-2733 { background-position: -780px -820px; }
-.emoji-2734 { background-position: -800px -820px; }
-.emoji-2744 { background-position: -820px -820px; }
-.emoji-2747 { background-position: -840px 0; }
-.emoji-274C { background-position: -840px -20px; }
-.emoji-274E { background-position: -840px -40px; }
-.emoji-2753 { background-position: -840px -60px; }
-.emoji-2754 { background-position: -840px -80px; }
-.emoji-2755 { background-position: -840px -100px; }
-.emoji-2757 { background-position: -840px -120px; }
-.emoji-2763 { background-position: -840px -140px; }
-.emoji-2764 { background-position: -840px -160px; }
-.emoji-2795 { background-position: -840px -180px; }
-.emoji-2796 { background-position: -840px -200px; }
-.emoji-2797 { background-position: -840px -220px; }
-.emoji-27A1 { background-position: -840px -240px; }
-.emoji-27B0 { background-position: -840px -260px; }
-.emoji-27BF { background-position: -840px -280px; }
-.emoji-2934 { background-position: -840px -300px; }
-.emoji-2935 { background-position: -840px -320px; }
-.emoji-2B05 { background-position: -840px -340px; }
-.emoji-2B06 { background-position: -840px -360px; }
-.emoji-2B07 { background-position: -840px -380px; }
-.emoji-2B1B { background-position: -840px -400px; }
-.emoji-2B1C { background-position: -840px -420px; }
-.emoji-2B50 { background-position: -840px -440px; }
-.emoji-2B55 { background-position: -840px -460px; }
-.emoji-3030 { background-position: -840px -480px; }
-.emoji-303D { background-position: -840px -500px; }
-.emoji-3297 { background-position: -840px -520px; }
-.emoji-3299 { background-position: -840px -540px; }
-
-.emoji-icon {
- background-image: image-url('emoji.png');
- background-repeat: no-repeat;
- height: 20px;
- width: 20px;
-
- @media only screen and (-webkit-min-device-pixel-ratio: 2),
- only screen and (min--moz-device-pixel-ratio: 2),
- only screen and (-o-min-device-pixel-ratio: 2/1),
- only screen and (min-device-pixel-ratio: 2),
- only screen and (min-resolution: 192dpi),
- only screen and (min-resolution: 2dppx) {
- background-image: image-url('emoji@2x.png');
- background-size: 860px 840px;
- }
+gl-emoji {
+ display: inline-block;
+ display: inline-flex;
+ vertical-align: middle;
+ font-size: 1.5em;
}
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 0ba00cea8b5..8f2150066c7 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -4,6 +4,21 @@
&.reset-filters {
padding: 7px;
}
+
+ &.update-issues-btn {
+ float: right;
+ margin-right: 0;
+
+ @media (max-width: $screen-xs-max) {
+ float: none;
+ }
+ }
+}
+
+.filters-section {
+ @media (max-width: $screen-xs-max) {
+ display: inline-block;
+ }
}
@media (min-width: $screen-sm-min) {
@@ -34,6 +49,11 @@
display: block;
margin: 0 0 10px;
}
+
+ .dropdown-menu-toggle,
+ .update-issues-btn .btn {
+ width: 100%;
+ }
}
.filtered-search-container {
@@ -44,6 +64,89 @@
-webkit-flex-direction: column;
flex-direction: column;
}
+
+ .tokens-container {
+ display: -webkit-flex;
+ display: flex;
+ flex: 1;
+ -webkit-flex: 1;
+ padding-left: 30px;
+ position: relative;
+ margin-bottom: 0;
+ }
+
+ .input-token {
+ flex: 1;
+ -webkit-flex: 1;
+ }
+
+ .filtered-search-token + .input-token:not(:last-child) {
+ max-width: 200px;
+ }
+}
+
+.filtered-search-token,
+.filtered-search-term {
+ display: -webkit-flex;
+ display: flex;
+ margin-top: 5px;
+ margin-bottom: 5px;
+
+ .selectable {
+ display: -webkit-flex;
+ display: flex;
+ }
+
+ .name,
+ .value {
+ display: inline-block;
+ padding: 2px 7px;
+ }
+
+ .name {
+ background-color: $filter-name-resting-color;
+ color: $filter-name-text-color;
+ border-radius: 2px 0 0 2px;
+ margin-right: 1px;
+ text-transform: capitalize;
+ }
+
+ .value {
+ background-color: $white-normal;
+ color: $filter-value-text-color;
+ border-radius: 0 2px 2px 0;
+ margin-right: 5px;
+ }
+
+ .selected {
+ .name {
+ background-color: $filter-name-selected-color;
+ }
+
+ .value {
+ background-color: $filter-value-selected-color;
+ }
+ }
+}
+
+.filtered-search-term {
+ .name {
+ background-color: inherit;
+ color: $black;
+ text-transform: none;
+ }
+
+ .selectable {
+ cursor: text;
+ }
+}
+
+.scroll-container {
+ display: -webkit-flex;
+ display: flex;
+ overflow-x: scroll;
+ white-space: nowrap;
+ width: 100%;
}
.filtered-search-input-container {
@@ -51,6 +154,9 @@
display: flex;
position: relative;
width: 100%;
+ border: 1px solid $border-color;
+ background-color: $white-light;
+ max-width: 87%;
@media (max-width: $screen-xs-min) {
-webkit-flex: 1 1 100%;
@@ -67,12 +173,22 @@
}
.form-control {
- padding-left: 25px;
+ position: relative;
+ min-width: 200px;
+ padding-left: 0;
padding-right: 25px;
+ border-color: transparent;
&:focus ~ .fa-filter {
color: $common-gray-dark;
}
+
+ &:focus,
+ &:hover {
+ outline: none;
+ border-color: transparent;
+ box-shadow: none;
+ }
}
.fa-filter {
@@ -89,12 +205,13 @@
.clear-search {
width: 35px;
- background-color: transparent;
+ background-color: $white-light;
border: none;
position: absolute;
right: 0;
height: 100%;
outline: none;
+ z-index: 1;
&:hover .fa-times {
color: $common-gray-dark;
@@ -111,7 +228,15 @@
overflow: auto;
}
-@media (max-width: $screen-xs-min) {
+@media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
+ .issues-details-filters {
+ .dropdown-menu-toggle {
+ width: 100px;
+ }
+ }
+}
+
+@media (max-width: $screen-xs-max) {
.issues-details-filters {
padding: 0 0 10px;
background-color: $white-light;
@@ -205,4 +330,4 @@
.filter-dropdown-loading {
padding: 8px 16px;
-} \ No newline at end of file
+}
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 55ed4b7b06c..7adbb0a4188 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -229,44 +229,6 @@ ul.content-list {
}
}
-// Table list
-.table-list {
- display: table;
- width: 100%;
-
- .table-list-row {
- display: table-row;
- }
-
- .table-list-cell {
- display: table-cell;
- vertical-align: top;
- padding: 10px 16px;
- border-bottom: 1px solid $gray-darker;
-
- &.avatar-cell {
- width: 36px;
- padding-right: 0;
-
- img {
- margin-right: 0;
- }
- }
- }
-
- &.table-wide {
- .table-list-cell {
- &:last-of-type {
- padding-right: 0;
- }
-
- &:first-of-type {
- padding-left: 0;
- }
- }
- }
-}
-
.panel > .content-list > li {
padding: $gl-padding-top $gl-padding;
}
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index d4758d90352..a668a6c4c39 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -147,6 +147,9 @@
}
.atwho-view {
+ overflow-y: auto;
+ overflow-x: hidden;
+
small.description {
float: right;
padding: 3px 5px;
@@ -162,4 +165,8 @@
@include disableAllAnimation;
}
}
+
+ ul > li {
+ white-space: nowrap;
+ }
}
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index 8e2c56a8488..eb73f7cc794 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -100,8 +100,7 @@
@media (max-width: $screen-sm-max) {
.issues-filters {
- .milestone-filter,
- .labels-filter {
+ .milestone-filter {
display: none;
}
}
diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss
index efe93724013..9d8d08dff88 100644
--- a/app/assets/stylesheets/framework/panels.scss
+++ b/app/assets/stylesheets/framework/panels.scss
@@ -48,11 +48,3 @@
line-height: inherit;
}
}
-
-.panel-default {
- .table-list-row:last-child {
- .table-list-cell {
- border-bottom: 0;
- }
- }
-}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index ba0af072716..6841adb637e 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -248,7 +248,7 @@ $diff-view-modes-border: #c1c1c1;
* Fonts
*/
$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
-$regular_font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
+$regular_font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
/*
* Dropdowns
@@ -540,3 +540,12 @@ Pipeline Graph
$stage-hover-bg: #eaf3fc;
$stage-hover-border: #d1e7fc;
$action-icon-color: #d6d6d6;
+
+/*
+Filtered Search
+*/
+$filter-name-resting-color: #f8f8f8;
+$filter-name-text-color: rgba(0, 0, 0, 0.55);
+$filter-value-text-color: rgba(0, 0, 0, 0.85);
+$filter-name-selected-color: #ebebeb;
+$filter-value-selected-color: #d7d7d7;
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index c3d45d708c1..2029b6893ef 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -78,6 +78,7 @@
padding: 5px 10px;
background-color: $gray-light;
border-bottom: 1px solid $gray-darker;
+ border-top: 1px solid $gray-darker;
font-size: 14px;
&:first-child {
@@ -117,10 +118,37 @@
}
}
+.commit.flex-list {
+ display: flex;
+}
+
+.avatar-cell {
+ width: 46px;
+ padding-left: 10px;
+
+ img {
+ margin-right: 0;
+ }
+}
+
+.commit-detail {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ flex-grow: 1;
+ padding-left: 10px;
+
+ .merge-request-branches & {
+ flex-direction: column;
+ }
+}
+
+.commit-content {
+ padding-right: 10px;
+}
+
.commit-actions {
@media (min-width: $screen-sm-min) {
- width: 300px;
- text-align: right;
font-size: 0;
}
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 9ae2e962d14..73a5da715f2 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -151,3 +151,71 @@
}
}
}
+
+.prometheus-graph {
+ text {
+ fill: $stat-graph-axis-fill;
+ }
+}
+
+.x-axis path,
+.y-axis path,
+.label-x-axis-line,
+.label-y-axis-line {
+ fill: none;
+ stroke-width: 1;
+ shape-rendering: crispEdges;
+}
+
+.x-axis path,
+.y-axis path {
+ stroke: $stat-graph-axis-fill;
+}
+
+.label-x-axis-line,
+.label-y-axis-line {
+ stroke: $border-color;
+}
+
+.y-axis {
+ line {
+ stroke: $stat-graph-axis-fill;
+ stroke-width: 1;
+ }
+}
+
+.metric-area {
+ opacity: 0.8;
+}
+
+.prometheus-graph-overlay {
+ fill: none;
+ opacity: 0.0;
+ pointer-events: all;
+}
+
+.rect-text-metric {
+ fill: $white-light;
+ stroke-width: 1;
+ stroke: $black;
+}
+
+.rect-axis-text {
+ fill: $white-light;
+}
+
+.text-metric,
+.text-median-metric,
+.text-metric-usage,
+.text-metric-date {
+ fill: $black;
+}
+
+.text-metric-date {
+ font-weight: 200;
+}
+
+.selected-metric-line {
+ stroke: $black;
+ stroke-width: 1;
+}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index a629a5333d7..d3496e19dde 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -3,7 +3,6 @@
*
*/
.mr-state-widget {
- background: $gray-light;
color: $gl-text-color;
border: 1px solid $border-color;
border-radius: 2px;
@@ -109,12 +108,17 @@
@media (max-width: $screen-xs-max) {
flex-wrap: wrap;
}
+
+ .ci-status-icon > .icon-link > svg {
+ width: 22px;
+ height: 22px;
+ }
}
.mr-widget-body,
.ci_widget,
.mr-widget-footer {
- padding: $gl-padding;
+ padding: 16px;
}
.mr-widget-pipeline-graph {
@@ -174,10 +178,6 @@
}
}
- p:last-child {
- margin-bottom: 0;
- }
-
.btn-grouped {
margin-left: 0;
margin-right: 7px;
@@ -240,8 +240,7 @@
.commit {
margin: 0;
- padding-top: 2px;
- padding-bottom: 2px;
+ padding: 10px 0;
list-style: none;
&:hover {
@@ -340,8 +339,61 @@
}
}
+.remove-message-pipes {
+ ul {
+ margin: 10px 0 0 12px;
+ padding: 0;
+ list-style: none;
+ border-left: 2px solid $border-color;
+ display: inline-block;
+ }
+
+ li {
+ position: relative;
+ margin: 0;
+ padding: 0;
+ display: block;
+
+ span {
+ margin-left: 15px;
+ max-height: 20px;
+ }
+ }
+
+ li::before {
+ content: '';
+ position: absolute;
+ border-top: 2px solid $border-color;
+ height: 1px;
+ top: 8px;
+ width: 8px;
+ }
+
+ li:last-child {
+ &::before {
+ top: 18px;
+ }
+
+ span {
+ display: block;
+ position: relative;
+ top: 5px;
+ margin-top: 5px;
+ }
+ }
+}
+
.mr-source-target {
+ background-color: $gray-light;
line-height: 31px;
+ border-style: solid;
+ border-width: 1px;
+ border-color: $border-color;
+ border-top-right-radius: 3px;
+ border-top-left-radius: 3px;
+ border-bottom: none;
+ padding: 16px;
+ margin-bottom: -1px;
}
.panel-new-merge-request {
@@ -356,7 +408,7 @@
}
.panel-footer {
- padding: 5px 10px;
+ padding: 0;
.btn {
min-width: auto;
@@ -426,6 +478,11 @@
}
}
+.assign-to-me-link {
+ padding-left: 12px;
+ white-space: nowrap;
+}
+
.table-holder {
.ci-table {
@@ -437,6 +494,8 @@
}
.merged-buttons {
+ margin-top: 20px;
+
.btn {
float: left;
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 00f5f2645b3..dc79de19d48 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -331,6 +331,10 @@ ul.notes {
&:hover {
color: $gl-link-color;
+ }
+
+ &:focus,
+ &:hover {
text-decoration: none;
}
}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 69eea1b2217..20eabc83142 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -115,7 +115,7 @@
.table.ci-table {
- &.builds-page tr {
+ &.builds-page tbody tr {
height: 71px;
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 07b93430442..4914933430f 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -494,11 +494,11 @@ a.deploy-project-label {
.project-stats {
font-size: 0;
text-align: center;
- border-bottom: 1px solid $border-color;
.nav {
padding-top: 12px;
padding-bottom: 12px;
+ border-bottom: 1px solid $border-color;
}
.nav > li {
@@ -645,30 +645,15 @@ pre.light-well {
}
.project-last-commit {
+ background-color: $gray-light;
+ border: 1px solid $border-color;
+ border-radius: $border-radius-base;
+ padding: 12px;
+
@media (min-width: $screen-sm-min) {
margin-top: $gl-padding;
}
- &.container-fluid {
- padding-top: 12px;
- padding-bottom: 12px;
- background-color: $gray-light;
- border: 1px solid $border-color;
- border-right-width: 0;
- border-left-width: 0;
-
- @media (min-width: $screen-sm-min) {
- border-right-width: 1px;
- border-left-width: 1px;
- }
- }
-
- &.container-limited {
- @media (min-width: 1281px) {
- border-radius: $border-radius-base;
- }
- }
-
.ci-status {
margin-right: $gl-padding;
}
@@ -761,6 +746,8 @@ pre.light-well {
}
.protected-branches-list {
+ margin-bottom: 30px;
+
a {
color: $gl-text-color;
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index a28a87ed4f8..3889deee21a 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -24,3 +24,14 @@
.service-settings .control-label {
padding-top: 0;
}
+
+.token-token-container {
+ #impersonation-token-token {
+ width: 80%;
+ display: inline;
+ }
+
+ .btn-clipboard {
+ margin-left: 5px;
+ }
+}
diff --git a/app/assets/stylesheets/pages/settings_ci_cd.scss b/app/assets/stylesheets/pages/settings_ci_cd.scss
new file mode 100644
index 00000000000..b97a29cd1a0
--- /dev/null
+++ b/app/assets/stylesheets/pages/settings_ci_cd.scss
@@ -0,0 +1,12 @@
+.triggers-container {
+ .label-container {
+ display: inline-block;
+ margin-left: 10px;
+ }
+}
+
+.trigger-actions {
+ .btn {
+ margin-left: 10px;
+ }
+}
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 8d1063fc26f..fc4da4c495f 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -139,18 +139,10 @@
.blob-commit-info {
list-style: none;
background: $gray-light;
- padding: 6px 0;
+ padding: 16px 16px 16px 6px;
border: 1px solid $border-color;
border-bottom: none;
margin: 0;
-
- .table-list-cell {
- border-bottom: none;
- }
-
- .commit-actions {
- width: 260px;
- }
}
#modal-remove-blob > .modal-dialog { width: 850px; }
diff --git a/app/controllers/admin/applications_controller.rb b/app/controllers/admin/applications_controller.rb
index 62f62e99a97..9c9f420c1e0 100644
--- a/app/controllers/admin/applications_controller.rb
+++ b/app/controllers/admin/applications_controller.rb
@@ -2,7 +2,7 @@ class Admin::ApplicationsController < Admin::ApplicationController
include OauthApplications
before_action :set_application, only: [:show, :edit, :update, :destroy]
- before_action :load_scopes, only: [:new, :edit]
+ before_action :load_scopes, only: [:new, :create, :edit, :update]
def index
@applications = Doorkeeper::Application.where("owner_id IS NULL")
diff --git a/app/controllers/admin/impersonation_tokens_controller.rb b/app/controllers/admin/impersonation_tokens_controller.rb
new file mode 100644
index 00000000000..07c8bf714fc
--- /dev/null
+++ b/app/controllers/admin/impersonation_tokens_controller.rb
@@ -0,0 +1,53 @@
+class Admin::ImpersonationTokensController < Admin::ApplicationController
+ before_action :user
+
+ def index
+ set_index_vars
+ end
+
+ def create
+ @impersonation_token = finder.build(impersonation_token_params)
+
+ if @impersonation_token.save
+ flash[:impersonation_token] = @impersonation_token.token
+ redirect_to admin_user_impersonation_tokens_path, notice: "A new impersonation token has been created."
+ else
+ set_index_vars
+ render :index
+ end
+ end
+
+ def revoke
+ @impersonation_token = finder.find(params[:id])
+
+ if @impersonation_token.revoke!
+ flash[:notice] = "Revoked impersonation token #{@impersonation_token.name}!"
+ else
+ flash[:alert] = "Could not revoke impersonation token #{@impersonation_token.name}."
+ end
+
+ redirect_to admin_user_impersonation_tokens_path
+ end
+
+ private
+
+ def user
+ @user ||= User.find_by!(username: params[:user_id])
+ end
+
+ def finder(options = {})
+ PersonalAccessTokensFinder.new({ user: user, impersonation: true }.merge(options))
+ end
+
+ def impersonation_token_params
+ params.require(:personal_access_token).permit(:name, :expires_at, :impersonation, scopes: [])
+ end
+
+ def set_index_vars
+ @scopes = Gitlab::Auth::API_SCOPES
+
+ @impersonation_token ||= finder.build
+ @inactive_impersonation_tokens = finder(state: 'inactive').execute
+ @active_impersonation_tokens = finder(state: 'active').execute.order(:expires_at)
+ end
+end
diff --git a/app/controllers/concerns/repository_settings_redirect.rb b/app/controllers/concerns/repository_settings_redirect.rb
new file mode 100644
index 00000000000..0854c73a02f
--- /dev/null
+++ b/app/controllers/concerns/repository_settings_redirect.rb
@@ -0,0 +1,7 @@
+module RepositorySettingsRedirect
+ extend ActiveSupport::Concern
+
+ def redirect_to_repository_settings(project)
+ redirect_to namespace_project_settings_repository_path(project.namespace, project)
+ end
+end
diff --git a/app/controllers/emojis_controller.rb b/app/controllers/emojis_controller.rb
deleted file mode 100644
index 1bec5a7d27f..00000000000
--- a/app/controllers/emojis_controller.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-class EmojisController < ApplicationController
- layout false
-
- def index
- end
-end
diff --git a/app/controllers/oauth/authorizations_controller.rb b/app/controllers/oauth/authorizations_controller.rb
index c721dca58d9..05190103767 100644
--- a/app/controllers/oauth/authorizations_controller.rb
+++ b/app/controllers/oauth/authorizations_controller.rb
@@ -1,8 +1,8 @@
class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
- before_action :authenticate_resource_owner!
-
layout 'profile'
+ # Overriden from Doorkeeper::AuthorizationsController to
+ # include the call to session.delete
def new
if pre_auth.authorizable?
if skip_authorization? || matching_token?
@@ -16,44 +16,4 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
render "doorkeeper/authorizations/error"
end
end
-
- # TODO: Handle raise invalid authorization
- def create
- redirect_or_render authorization.authorize
- end
-
- def destroy
- redirect_or_render authorization.deny
- end
-
- private
-
- def matching_token?
- Doorkeeper::AccessToken.matching_token_for(pre_auth.client,
- current_resource_owner.id,
- pre_auth.scopes)
- end
-
- def redirect_or_render(auth)
- if auth.redirectable?
- redirect_to auth.redirect_uri
- else
- render json: auth.body, status: auth.status
- end
- end
-
- def pre_auth
- @pre_auth ||=
- Doorkeeper::OAuth::PreAuthorization.new(Doorkeeper.configuration,
- server.client_via_uid,
- params)
- end
-
- def authorization
- @authorization ||= strategy.request
- end
-
- def strategy
- @strategy ||= server.authorization_request(pre_auth.response_type)
- end
end
diff --git a/app/controllers/profiles/personal_access_tokens_controller.rb b/app/controllers/profiles/personal_access_tokens_controller.rb
index 6e007f17913..0abe7ea3c9b 100644
--- a/app/controllers/profiles/personal_access_tokens_controller.rb
+++ b/app/controllers/profiles/personal_access_tokens_controller.rb
@@ -4,7 +4,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
end
def create
- @personal_access_token = current_user.personal_access_tokens.generate(personal_access_token_params)
+ @personal_access_token = finder.build(personal_access_token_params)
if @personal_access_token.save
flash[:personal_access_token] = @personal_access_token.token
@@ -16,7 +16,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
end
def revoke
- @personal_access_token = current_user.personal_access_tokens.find(params[:id])
+ @personal_access_token = finder.find(params[:id])
if @personal_access_token.revoke!
flash[:notice] = "Revoked personal access token #{@personal_access_token.name}!"
@@ -29,14 +29,19 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
private
+ def finder(options = {})
+ PersonalAccessTokensFinder.new({ user: current_user, impersonation: false }.merge(options))
+ end
+
def personal_access_token_params
params.require(:personal_access_token).permit(:name, :expires_at, scopes: [])
end
def set_index_vars
- @personal_access_token ||= current_user.personal_access_tokens.build
- @scopes = Gitlab::Auth::SCOPES
- @active_personal_access_tokens = current_user.personal_access_tokens.active.order(:expires_at)
- @inactive_personal_access_tokens = current_user.personal_access_tokens.inactive
+ @scopes = Gitlab::Auth::API_SCOPES
+
+ @personal_access_token = finder.build
+ @inactive_personal_access_tokens = finder(state: 'inactive').execute
+ @active_personal_access_tokens = finder(state: 'active').execute.order(:expires_at)
end
end
diff --git a/app/controllers/projects/autocomplete_sources_controller.rb b/app/controllers/projects/autocomplete_sources_controller.rb
index d9dfa534669..ffb54390965 100644
--- a/app/controllers/projects/autocomplete_sources_controller.rb
+++ b/app/controllers/projects/autocomplete_sources_controller.rb
@@ -1,9 +1,5 @@
class Projects::AutocompleteSourcesController < Projects::ApplicationController
- before_action :load_autocomplete_service, except: [:emojis, :members]
-
- def emojis
- render json: Gitlab::AwardEmoji.urls
- end
+ before_action :load_autocomplete_service, except: [:members]
def members
render json: ::Projects::ParticipantsService.new(@project, current_user).execute(noteable)
diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb
index 61fef4dc133..28c9646910d 100644
--- a/app/controllers/projects/boards/issues_controller.rb
+++ b/app/controllers/projects/boards/issues_controller.rb
@@ -8,6 +8,7 @@ module Projects
def index
issues = ::Boards::Issues::ListService.new(project, current_user, filter_params).execute
issues = issues.page(params[:page]).per(params[:per] || 20)
+ make_sure_position_is_set(issues)
render json: {
issues: serialize_as_json(issues),
@@ -38,6 +39,12 @@ module Projects
private
+ def make_sure_position_is_set(issues)
+ issues.each do |issue|
+ issue.move_to_end && issue.save unless issue.relative_position
+ end
+ end
+
def issue
@issue ||=
IssuesFinder.new(current_user, project_id: project.id)
@@ -63,7 +70,7 @@ module Projects
end
def move_params
- params.permit(:board_id, :id, :from_list_id, :to_list_id)
+ params.permit(:board_id, :id, :from_list_id, :to_list_id, :move_before_iid, :move_after_iid)
end
def issue_params
@@ -73,7 +80,7 @@ module Projects
def serialize_as_json(resource)
resource.as_json(
labels: true,
- only: [:id, :iid, :title, :confidential, :due_date],
+ only: [:id, :iid, :title, :confidential, :due_date, :relative_position],
include: {
assignee: { only: [:id, :name, :username], methods: [:avatar_url] },
milestone: { only: [:id, :title] }
diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
index b094491e006..1502b734f37 100644
--- a/app/controllers/projects/deploy_keys_controller.rb
+++ b/app/controllers/projects/deploy_keys_controller.rb
@@ -1,4 +1,5 @@
class Projects::DeployKeysController < Projects::ApplicationController
+ include RepositorySettingsRedirect
respond_to :html
# Authorize
@@ -7,51 +8,36 @@ class Projects::DeployKeysController < Projects::ApplicationController
layout "project_settings"
def index
- @key = DeployKey.new
- set_index_vars
+ redirect_to_repository_settings(@project)
end
def new
- redirect_to namespace_project_deploy_keys_path(@project.namespace, @project)
+ redirect_to_repository_settings(@project)
end
def create
@key = DeployKey.new(deploy_key_params.merge(user: current_user))
- set_index_vars
- if @key.valid? && @project.deploy_keys << @key
- redirect_to namespace_project_deploy_keys_path(@project.namespace, @project)
- else
- render "index"
+ unless @key.valid? && @project.deploy_keys << @key
+ flash[:alert] = @key.errors.full_messages.join(', ').html_safe
end
+ redirect_to_repository_settings(@project)
end
def enable
Projects::EnableDeployKeyService.new(@project, current_user, params).execute
- redirect_to namespace_project_deploy_keys_path(@project.namespace, @project)
+ redirect_to_repository_settings(@project)
end
def disable
@project.deploy_keys_projects.find_by(deploy_key_id: params[:id]).destroy
- redirect_back_or_default(default: { action: 'index' })
+ redirect_to_repository_settings(@project)
end
protected
- def set_index_vars
- @enabled_keys ||= @project.deploy_keys
-
- @available_keys ||= current_user.accessible_deploy_keys - @enabled_keys
- @available_project_keys ||= current_user.project_deploy_keys - @enabled_keys
- @available_public_keys ||= DeployKey.are_public - @enabled_keys
-
- # Public keys that are already used by another accessible project are already
- # in @available_project_keys.
- @available_public_keys -= @available_project_keys
- end
-
def deploy_key_params
params.require(:deploy_key).permit(:key, :title, :can_push)
end
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index fed75396d6e..fa37963dfd4 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -5,7 +5,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
before_action :authorize_create_deployment!, only: [:stop]
before_action :authorize_update_environment!, only: [:edit, :update]
before_action :authorize_admin_environment!, only: [:terminal, :terminal_websocket_authorize]
- before_action :environment, only: [:show, :edit, :update, :stop, :terminal, :terminal_websocket_authorize]
+ before_action :environment, only: [:show, :edit, :update, :stop, :terminal, :terminal_websocket_authorize, :metrics]
before_action :verify_api_request!, only: :terminal_websocket_authorize
def index
@@ -109,6 +109,19 @@ class Projects::EnvironmentsController < Projects::ApplicationController
end
end
+ def metrics
+ # Currently, this acts as a hint to load the metrics details into the cache
+ # if they aren't there already
+ @metrics = environment.metrics || {}
+
+ respond_to do |format|
+ format.html
+ format.json do
+ render json: @metrics, status: @metrics.any? ? :ok : :no_content
+ end
+ end
+ end
+
private
def verify_api_request!
diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb
index 2f422d352ed..a8cb07eb67a 100644
--- a/app/controllers/projects/protected_branches_controller.rb
+++ b/app/controllers/projects/protected_branches_controller.rb
@@ -1,26 +1,22 @@
class Projects::ProtectedBranchesController < Projects::ApplicationController
+ include RepositorySettingsRedirect
# Authorize
before_action :require_non_empty_project
before_action :authorize_admin_project!
before_action :load_protected_branch, only: [:show, :update, :destroy]
- before_action :load_protected_branches, only: [:index]
layout "project_settings"
def index
- @protected_branch = @project.protected_branches.new
- load_gon_index
+ redirect_to_repository_settings(@project)
end
def create
@protected_branch = ::ProtectedBranches::CreateService.new(@project, current_user, protected_branch_params).execute
- if @protected_branch.persisted?
- redirect_to namespace_project_protected_branches_path(@project.namespace, @project)
- else
- load_protected_branches
- load_gon_index
- render :index
+ unless @protected_branch.persisted?
+ flash[:alert] = @protected_branches.errors.full_messages.join(', ').html_safe
end
+ redirect_to_repository_settings(@project)
end
def show
@@ -45,7 +41,7 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
@protected_branch.destroy
respond_to do |format|
- format.html { redirect_to namespace_project_protected_branches_path }
+ format.html { redirect_to_repository_settings(@project) }
format.js { head :ok }
end
end
@@ -61,24 +57,4 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
merge_access_levels_attributes: [:access_level, :id],
push_access_levels_attributes: [:access_level, :id])
end
-
- def load_protected_branches
- @protected_branches = @project.protected_branches.order(:name).page(params[:page])
- end
-
- def access_levels_options
- {
- push_access_levels: {
- "Roles" => ProtectedBranch::PushAccessLevel.human_access_levels.map { |id, text| { id: id, text: text, before_divider: true } },
- },
- merge_access_levels: {
- "Roles" => ProtectedBranch::MergeAccessLevel.human_access_levels.map { |id, text| { id: id, text: text, before_divider: true } }
- }
- }
- end
-
- def load_gon_index
- params = { open_branches: @project.open_branches.map { |br| { text: br.name, id: br.name, title: br.name } } }
- gon.push(params.merge(access_levels_options))
- end
end
diff --git a/app/controllers/projects/settings/repository_controller.rb b/app/controllers/projects/settings/repository_controller.rb
new file mode 100644
index 00000000000..b6ce4abca45
--- /dev/null
+++ b/app/controllers/projects/settings/repository_controller.rb
@@ -0,0 +1,50 @@
+module Projects
+ module Settings
+ class RepositoryController < Projects::ApplicationController
+ before_action :authorize_admin_project!
+
+ def show
+ @deploy_keys = DeployKeysPresenter
+ .new(@project, current_user: current_user)
+
+ define_protected_branches
+ end
+
+ private
+
+ def define_protected_branches
+ load_protected_branches
+ @protected_branch = @project.protected_branches.new
+ load_gon_index
+ end
+
+ def load_protected_branches
+ @protected_branches = @project.protected_branches.order(:name).page(params[:page])
+ end
+
+ def access_levels_options
+ {
+ push_access_levels: {
+ roles: ProtectedBranch::PushAccessLevel.human_access_levels.map do |id, text|
+ { id: id, text: text, before_divider: true }
+ end
+ },
+ merge_access_levels: {
+ roles: ProtectedBranch::MergeAccessLevel.human_access_levels.map do |id, text|
+ { id: id, text: text, before_divider: true }
+ end
+ }
+ }
+ end
+
+ def open_branches
+ branches = @project.open_branches.map { |br| { text: br.name, id: br.name, title: br.name } }
+ { open_branches: branches }
+ end
+
+ def load_gon_index
+ gon.push(open_branches.merge(access_levels_options))
+ end
+ end
+ end
+end
diff --git a/app/controllers/projects/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb
index b2c11ea4156..c47198c5eb6 100644
--- a/app/controllers/projects/triggers_controller.rb
+++ b/app/controllers/projects/triggers_controller.rb
@@ -1,5 +1,8 @@
class Projects::TriggersController < Projects::ApplicationController
before_action :authorize_admin_build!
+ before_action :authorize_manage_trigger!, except: [:index, :create]
+ before_action :authorize_admin_trigger!, only: [:edit, :update]
+ before_action :trigger, only: [:take_ownership, :edit, :update, :destroy]
layout 'project_settings'
@@ -8,27 +11,67 @@ class Projects::TriggersController < Projects::ApplicationController
end
def create
- @trigger = project.triggers.new
- @trigger.save
+ @trigger = project.triggers.create(create_params.merge(owner: current_user))
if @trigger.valid?
- redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Trigger was created successfully.'
+ flash[:notice] = 'Trigger was created successfully.'
else
- @triggers = project.triggers.select(&:persisted?)
- render action: "show"
+ flash[:alert] = 'You could not create a new trigger.'
+ end
+
+ redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ end
+
+ def take_ownership
+ if trigger.update(owner: current_user)
+ flash[:notice] = 'Trigger was re-assigned.'
+ else
+ flash[:alert] = 'You could not take ownership of trigger.'
+ end
+
+ redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ end
+
+ def edit
+ end
+
+ def update
+ if trigger.update(update_params)
+ redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project), notice: 'Trigger was successfully updated.'
+ else
+ render action: "edit"
end
end
def destroy
- trigger.destroy
- flash[:alert] = "Trigger removed"
+ if trigger.destroy
+ flash[:notice] = "Trigger removed."
+ else
+ flash[:alert] = "Could not remove the trigger."
+ end
redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
end
private
+ def authorize_manage_trigger!
+ access_denied! unless can?(current_user, :manage_trigger, trigger)
+ end
+
+ def authorize_admin_trigger!
+ access_denied! unless can?(current_user, :admin_trigger, trigger)
+ end
+
def trigger
- @trigger ||= project.triggers.find(params[:id])
+ @trigger ||= project.triggers.find(params[:id]) || render_404
+ end
+
+ def create_params
+ params.require(:trigger).permit(:description)
+ end
+
+ def update_params
+ params.require(:trigger).permit(:description)
end
end
diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb
index 509f4f412ca..f1bfd574f04 100644
--- a/app/controllers/uploads_controller.rb
+++ b/app/controllers/uploads_controller.rb
@@ -14,6 +14,8 @@ class UploadsController < ApplicationController
end
disposition = uploader.image? ? 'inline' : 'attachment'
+
+ expires_in 0.seconds, must_revalidate: true, private: true
send_file uploader.file.path, disposition: disposition
end
diff --git a/app/finders/personal_access_tokens_finder.rb b/app/finders/personal_access_tokens_finder.rb
new file mode 100644
index 00000000000..760166b453f
--- /dev/null
+++ b/app/finders/personal_access_tokens_finder.rb
@@ -0,0 +1,45 @@
+class PersonalAccessTokensFinder
+ attr_accessor :params
+
+ delegate :build, :find, :find_by, to: :execute
+
+ def initialize(params = {})
+ @params = params
+ end
+
+ def execute
+ tokens = PersonalAccessToken.all
+ tokens = by_user(tokens)
+ tokens = by_impersonation(tokens)
+ by_state(tokens)
+ end
+
+ private
+
+ def by_user(tokens)
+ return tokens unless @params[:user]
+ tokens.where(user: @params[:user])
+ end
+
+ def by_impersonation(tokens)
+ case @params[:impersonation]
+ when true
+ tokens.with_impersonation
+ when false
+ tokens.without_impersonation
+ else
+ tokens
+ end
+ end
+
+ def by_state(tokens)
+ case @params[:state]
+ when 'active'
+ tokens.active
+ when 'inactive'
+ tokens.inactive
+ else
+ tokens
+ end
+ end
+end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 4b025669f69..ca326dd0627 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -81,8 +81,8 @@ module ApplicationSettingsHelper
end
def repository_storages_options_for_select
- options = Gitlab.config.repositories.storages.map do |name, path|
- ["#{name} - #{path}", name]
+ options = Gitlab.config.repositories.storages.map do |name, storage|
+ ["#{name} - #{storage['path']}", name]
end
options_for_select(options, @application_setting.repository_storages)
diff --git a/app/helpers/builds_helper.rb b/app/helpers/builds_helper.rb
index 5ac3e66bb1f..2fcb7a59fc3 100644
--- a/app/helpers/builds_helper.rb
+++ b/app/helpers/builds_helper.rb
@@ -12,7 +12,7 @@ module BuildsHelper
build_url: namespace_project_build_url(@project.namespace, @project, @build, :json),
build_status: @build.status,
build_stage: @build.stage,
- log_state: @build.trace_with_state[:state].to_s
+ log_state: ''
}
end
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 94f3b480178..2c2c408b035 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -48,6 +48,8 @@ module CiStatusHelper
'icon_status_created'
when 'skipped'
'icon_status_skipped'
+ when 'manual'
+ 'icon_status_manual'
else
'icon_status_canceled'
end
diff --git a/app/helpers/emoji_helper.rb b/app/helpers/emoji_helper.rb
new file mode 100644
index 00000000000..482f68f412b
--- /dev/null
+++ b/app/helpers/emoji_helper.rb
@@ -0,0 +1,5 @@
+module EmojiHelper
+ def emoji_icon(*args)
+ raw Gitlab::Emoji.gl_emoji_tag(*args)
+ end
+end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 362046c0270..5605393c0c3 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -162,7 +162,12 @@ module EventsHelper
def event_note(text, options = {})
text = first_line_in_markdown(text, 150, options)
- sanitize(text, tags: %w(a img b pre code p span))
+
+ sanitize(
+ text,
+ tags: %w(a img b pre code p span),
+ attributes: Rails::Html::WhiteListSanitizer.allowed_attributes + ['style']
+ )
end
def event_commit_title(message)
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index f16a63e2178..e9b7cbbad6a 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -74,6 +74,10 @@ module GitlabRoutingHelper
namespace_project_environment_path(environment.project.namespace, environment.project, environment, *args)
end
+ def environment_metrics_path(environment, *args)
+ metrics_namespace_project_environment_path(environment.project.namespace, environment.project, environment, *args)
+ end
+
def issue_path(entity, *args)
namespace_project_issue_path(entity.project.namespace, entity.project, entity, *args)
end
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index a2d21b67a77..4bdf07fe1ad 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -87,34 +87,6 @@ module IssuesHelper
icon('eye-slash') if issue.confidential?
end
- def emoji_icon(name, unicode = nil, aliases = [], sprite: true)
- unicode ||= Gitlab::Emoji.emoji_filename(name) rescue ""
-
- data = {
- aliases: aliases.join(" "),
- emoji: name,
- unicode_name: unicode
- }
-
- if sprite
- # Emoji icons for the emoji menu, these use a spritesheet.
- content_tag :div, "",
- class: "icon emoji-icon emoji-#{unicode}",
- title: name,
- data: data
- else
- # Emoji icons displayed separately, used for the awards already given
- # to an issue or merge request.
- content_tag :img, "",
- class: "icon emoji",
- title: name,
- height: "20px",
- width: "20px",
- src: url_to_image("#{unicode}.png"),
- data: data
- end
- end
-
def award_user_list(awards, current_user, limit: 10)
names = awards.map do |award|
award.user == current_user ? 'You' : award.user.name
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index 74cccb23956..710218082f2 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -35,9 +35,8 @@ module PreferencesHelper
def project_view_choices
[
- ['Readme', :readme],
- ['Activity view', :activity],
- ['Files and Readme (default)', :files]
+ ['Files and Readme (default)', :files],
+ ['Activity view', :activity]
]
end
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index 8ad3851fb9a..18734f1411f 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -50,7 +50,7 @@ module SortingHelper
end
def sort_title_priority
- 'Priority'
+ 'Label priority'
end
def sort_title_oldest_updated
diff --git a/app/models/chat_team.rb b/app/models/chat_team.rb
index 7952141a0d6..c52b6f15913 100644
--- a/app/models/chat_team.rb
+++ b/app/models/chat_team.rb
@@ -1,5 +1,6 @@
class ChatTeam < ActiveRecord::Base
validates :team_id, presence: true
+ validates :namespace, uniqueness: true
belongs_to :namespace
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index d69643967a1..3722047251d 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -517,6 +517,27 @@ module Ci
]
end
+ def steps
+ [Gitlab::Ci::Build::Step.from_commands(self),
+ Gitlab::Ci::Build::Step.from_after_script(self)].compact
+ end
+
+ def image
+ Gitlab::Ci::Build::Image.from_image(self)
+ end
+
+ def services
+ Gitlab::Ci::Build::Image.from_services(self)
+ end
+
+ def artifacts
+ [options[:artifacts]]
+ end
+
+ def cache
+ [options[:cache]]
+ end
+
def credentials
Gitlab::Ci::Build::Credentials::Factory.new(self).create!
end
@@ -543,10 +564,35 @@ module Ci
@unscoped_project ||= Project.unscoped.find_by(id: gl_project_id)
end
+ CI_REGISTRY_USER = 'gitlab-ci-token'.freeze
+
def predefined_variables
variables = [
{ key: 'CI', value: 'true', public: true },
{ key: 'GITLAB_CI', value: 'true', public: true },
+ { key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
+ { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
+ { key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true },
+ { key: 'CI_JOB_ID', value: id.to_s, public: true },
+ { key: 'CI_JOB_NAME', value: name, public: true },
+ { key: 'CI_JOB_STAGE', value: stage, public: true },
+ { key: 'CI_JOB_TOKEN', value: token, public: false },
+ { key: 'CI_COMMIT_SHA', value: sha, public: true },
+ { key: 'CI_COMMIT_REF_NAME', value: ref, public: true },
+ { key: 'CI_COMMIT_REF_SLUG', value: ref_slug, public: true },
+ { key: 'CI_REGISTRY_USER', value: CI_REGISTRY_USER, public: true },
+ { key: 'CI_REGISTRY_PASSWORD', value: token, public: false },
+ { key: 'CI_REPOSITORY_URL', value: repo_url, public: false }
+ ]
+
+ variables << { key: "CI_COMMIT_TAG", value: ref, public: true } if tag?
+ variables << { key: "CI_PIPELINE_TRIGGERED", value: 'true', public: true } if trigger_request
+ variables << { key: "CI_JOB_MANUAL", value: 'true', public: true } if action?
+ variables.concat(legacy_variables)
+ end
+
+ def legacy_variables
+ variables = [
{ key: 'CI_BUILD_ID', value: id.to_s, public: true },
{ key: 'CI_BUILD_TOKEN', value: token, public: false },
{ key: 'CI_BUILD_REF', value: sha, public: true },
@@ -554,14 +600,12 @@ module Ci
{ key: 'CI_BUILD_REF_NAME', value: ref, public: true },
{ key: 'CI_BUILD_REF_SLUG', value: ref_slug, public: true },
{ key: 'CI_BUILD_NAME', value: name, public: true },
- { key: 'CI_BUILD_STAGE', value: stage, public: true },
- { key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
- { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
- { key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true }
+ { key: 'CI_BUILD_STAGE', value: stage, public: true }
]
- variables << { key: 'CI_BUILD_TAG', value: ref, public: true } if tag?
- variables << { key: 'CI_BUILD_TRIGGERED', value: 'true', public: true } if trigger_request
- variables << { key: 'CI_BUILD_MANUAL', value: 'true', public: true } if action?
+
+ variables << { key: "CI_BUILD_TAG", value: ref, public: true } if tag?
+ variables << { key: "CI_BUILD_TRIGGERED", value: 'true', public: true } if trigger_request
+ variables << { key: "CI_BUILD_MANUAL", value: 'true', public: true } if action?
variables
end
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 4863c34a6a6..edd21f984c8 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -127,18 +127,15 @@ module Ci
def tick_runner_queue
SecureRandom.hex.tap do |new_update|
- Gitlab::Redis.with do |redis|
- redis.set(runner_queue_key, new_update, ex: RUNNER_QUEUE_EXPIRY_TIME)
- end
+ ::Gitlab::Workhorse.set_key_and_notify(runner_queue_key, new_update,
+ expire: RUNNER_QUEUE_EXPIRY_TIME, overwrite: true)
end
end
def ensure_runner_queue_value
- Gitlab::Redis.with do |redis|
- value = SecureRandom.hex
- redis.set(runner_queue_key, value, ex: RUNNER_QUEUE_EXPIRY_TIME, nx: true)
- redis.get(runner_queue_key)
- end
+ new_value = SecureRandom.hex
+ ::Gitlab::Workhorse.set_key_and_notify(runner_queue_key, new_value,
+ expire: RUNNER_QUEUE_EXPIRY_TIME, overwrite: false)
end
def is_runner_queue_value_latest?(value)
diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb
index 8aa45b2f02e..90473d41c04 100644
--- a/app/models/ci/trigger.rb
+++ b/app/models/ci/trigger.rb
@@ -29,8 +29,12 @@ module Ci
token[0...4]
end
- def can_show_token?(user)
- owner.blank? || owner == user
+ def legacy?
+ self.owner_id.blank?
+ end
+
+ def can_access_project?
+ self.owner_id.blank? || Ability.allowed?(self.owner, :create_build, project)
end
end
end
diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb
index 073ac4c1b65..a7fd0a15f0f 100644
--- a/app/models/concerns/awardable.rb
+++ b/app/models/concerns/awardable.rb
@@ -101,6 +101,6 @@ module Awardable
private
def normalize_name(name)
- Gitlab::AwardEmoji.normalize_emoji_name(name)
+ Gitlab::Emoji.normalize_emoji_name(name)
end
end
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index b819947c9e6..5101cc7e687 100644
--- a/app/models/concerns/has_status.rb
+++ b/app/models/concerns/has_status.rb
@@ -7,7 +7,7 @@ module HasStatus
STARTED_STATUSES = %w[running success failed skipped manual].freeze
ACTIVE_STATUSES = %w[pending running].freeze
COMPLETED_STATUSES = %w[success failed canceled skipped].freeze
- ORDERED_STATUSES = %w[manual failed pending running canceled success skipped].freeze
+ ORDERED_STATUSES = %w[failed pending running manual canceled success skipped created].freeze
class_methods do
def status_sql
diff --git a/app/models/concerns/relative_positioning.rb b/app/models/concerns/relative_positioning.rb
new file mode 100644
index 00000000000..603f2dd7e5d
--- /dev/null
+++ b/app/models/concerns/relative_positioning.rb
@@ -0,0 +1,101 @@
+module RelativePositioning
+ extend ActiveSupport::Concern
+
+ MIN_POSITION = 0
+ MAX_POSITION = Gitlab::Database::MAX_INT_VALUE
+
+ included do
+ after_save :save_positionable_neighbours
+ end
+
+ def min_relative_position
+ self.class.in_projects(project.id).minimum(:relative_position)
+ end
+
+ def max_relative_position
+ self.class.in_projects(project.id).maximum(:relative_position)
+ end
+
+ def prev_relative_position
+ prev_pos = nil
+
+ if self.relative_position
+ prev_pos = self.class.
+ in_projects(project.id).
+ where('relative_position < ?', self.relative_position).
+ maximum(:relative_position)
+ end
+
+ prev_pos || MIN_POSITION
+ end
+
+ def next_relative_position
+ next_pos = nil
+
+ if self.relative_position
+ next_pos = self.class.
+ in_projects(project.id).
+ where('relative_position > ?', self.relative_position).
+ minimum(:relative_position)
+ end
+
+ next_pos || MAX_POSITION
+ end
+
+ def move_between(before, after)
+ return move_after(before) unless after
+ return move_before(after) unless before
+
+ pos_before = before.relative_position
+ pos_after = after.relative_position
+
+ if pos_after && (pos_before == pos_after)
+ self.relative_position = pos_before
+ before.move_before(self)
+ after.move_after(self)
+
+ @positionable_neighbours = [before, after]
+ else
+ self.relative_position = position_between(pos_before, pos_after)
+ end
+ end
+
+ def move_before(after)
+ self.relative_position = position_between(after.prev_relative_position, after.relative_position)
+ end
+
+ def move_after(before)
+ self.relative_position = position_between(before.relative_position, before.next_relative_position)
+ end
+
+ def move_to_end
+ self.relative_position = position_between(max_relative_position, MAX_POSITION)
+ end
+
+ private
+
+ # This method takes two integer values (positions) and
+ # calculates some random position between them. The range is huge as
+ # the maximum integer value is 2147483647. Ideally, the calculated value would be
+ # exactly between those terminating values, but this will introduce possibility of a race condition
+ # so two or more issues can get the same value, we want to avoid that and we also want to avoid
+ # using a lock here. If we have two issues with distance more than one thousand, we are OK.
+ # Given the huge range of possible values that integer can fit we shoud never face a problem.
+ def position_between(pos_before, pos_after)
+ pos_before ||= MIN_POSITION
+ pos_after ||= MAX_POSITION
+
+ pos_before, pos_after = [pos_before, pos_after].sort
+
+ rand(pos_before.next..pos_after.pred)
+ end
+
+ def save_positionable_neighbours
+ return unless @positionable_neighbours
+
+ status = @positionable_neighbours.all?(&:save)
+ @positionable_neighbours = nil
+
+ status
+ end
+end
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 1a21b5e52b5..bf33010fd21 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -145,6 +145,14 @@ class Environment < ActiveRecord::Base
project.deployment_service.terminals(self) if has_terminals?
end
+ def has_metrics?
+ project.monitoring_service.present? && available? && last_deployment.present?
+ end
+
+ def metrics
+ project.monitoring_service.metrics(self) if has_metrics?
+ end
+
# An environment name is not necessarily suitable for use in URLs, DNS
# or other third-party contexts, so provide a slugified version. A slug has
# the following properties:
diff --git a/app/models/issue.rb b/app/models/issue.rb
index de90f19f854..0f7a26ee3e1 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -7,6 +7,7 @@ class Issue < ActiveRecord::Base
include Sortable
include Spammable
include FasterCacheKeys
+ include RelativePositioning
DueDateStruct = Struct.new(:title, :name).freeze
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
diff --git a/app/models/oauth_access_grant.rb b/app/models/oauth_access_grant.rb
new file mode 100644
index 00000000000..3a997406565
--- /dev/null
+++ b/app/models/oauth_access_grant.rb
@@ -0,0 +1,4 @@
+class OauthAccessGrant < Doorkeeper::AccessGrant
+ belongs_to :resource_owner, class_name: 'User'
+ belongs_to :application, class_name: 'Doorkeeper::Application'
+end
diff --git a/app/models/oauth_access_token.rb b/app/models/oauth_access_token.rb
index 116fb71ac08..b85f5dbaf2e 100644
--- a/app/models/oauth_access_token.rb
+++ b/app/models/oauth_access_token.rb
@@ -1,4 +1,4 @@
-class OauthAccessToken < ActiveRecord::Base
+class OauthAccessToken < Doorkeeper::AccessToken
belongs_to :resource_owner, class_name: 'User'
belongs_to :application, class_name: 'Doorkeeper::Application'
end
diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb
index 10a34c42fd8..e8b000ddad6 100644
--- a/app/models/personal_access_token.rb
+++ b/app/models/personal_access_token.rb
@@ -1,4 +1,5 @@
class PersonalAccessToken < ActiveRecord::Base
+ include Expirable
include TokenAuthenticatable
add_authentication_token_field :token
@@ -6,17 +7,30 @@ class PersonalAccessToken < ActiveRecord::Base
belongs_to :user
- scope :active, -> { where(revoked: false).where("expires_at >= NOW() OR expires_at IS NULL") }
+ before_save :ensure_token
+
+ scope :active, -> { where("revoked = false AND (expires_at >= NOW() OR expires_at IS NULL)") }
scope :inactive, -> { where("revoked = true OR expires_at < NOW()") }
+ scope :with_impersonation, -> { where(impersonation: true) }
+ scope :without_impersonation, -> { where(impersonation: false) }
- def self.generate(params)
- personal_access_token = self.new(params)
- personal_access_token.ensure_token
- personal_access_token
- end
+ validates :scopes, presence: true
+ validate :validate_api_scopes
def revoke!
self.revoked = true
self.save
end
+
+ def active?
+ !revoked? && !expired?
+ end
+
+ protected
+
+ def validate_api_scopes
+ unless scopes.all? { |scope| Gitlab::Auth::API_SCOPES.include?(scope.to_sym) }
+ errors.add :scopes, "can only contain API scopes"
+ end
+ end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 7d211784c3c..8c2dadf4659 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -113,6 +113,7 @@ class Project < ActiveRecord::Base
has_one :gitlab_issue_tracker_service, dependent: :destroy, inverse_of: :project
has_one :external_wiki_service, dependent: :destroy
has_one :kubernetes_service, dependent: :destroy, inverse_of: :project
+ has_one :prometheus_service, dependent: :destroy, inverse_of: :project
has_one :mock_ci_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
@@ -392,7 +393,7 @@ class Project < ActiveRecord::Base
end
def repository_storage_path
- Gitlab.config.repositories.storages[repository_storage]
+ Gitlab.config.repositories.storages[repository_storage]['path']
end
def team
@@ -771,6 +772,14 @@ class Project < ActiveRecord::Base
@deployment_service ||= deployment_services.reorder(nil).find_by(active: true)
end
+ def monitoring_services
+ services.where(category: :monitoring)
+ end
+
+ def monitoring_service
+ @monitoring_service ||= monitoring_services.reorder(nil).find_by(active: true)
+ end
+
def jira_tracker?
issues_tracker.to_param == 'jira'
end
diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb
index f2e1c906dac..02fbd5497fa 100644
--- a/app/models/project_services/kubernetes_service.rb
+++ b/app/models/project_services/kubernetes_service.rb
@@ -36,7 +36,7 @@ class KubernetesService < DeploymentService
def initialize_properties
if properties.nil?
self.properties = {}
- self.namespace = project.path if project.present?
+ self.namespace = "#{project.path}-#{project.id}" if project.present?
end
end
diff --git a/app/models/project_services/monitoring_service.rb b/app/models/project_services/monitoring_service.rb
new file mode 100644
index 00000000000..ea585721e8f
--- /dev/null
+++ b/app/models/project_services/monitoring_service.rb
@@ -0,0 +1,16 @@
+# Base class for monitoring services
+#
+# These services integrate with a deployment solution like Prometheus
+# to provide additional features for environments.
+class MonitoringService < Service
+ default_value_for :category, 'monitoring'
+
+ def self.supported_events
+ %w()
+ end
+
+ # Environments have a number of metrics
+ def metrics(environment)
+ raise NotImplementedError
+ end
+end
diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb
new file mode 100644
index 00000000000..375966b9efc
--- /dev/null
+++ b/app/models/project_services/prometheus_service.rb
@@ -0,0 +1,93 @@
+class PrometheusService < MonitoringService
+ include ReactiveCaching
+
+ self.reactive_cache_key = ->(service) { [service.class.model_name.singular, service.project_id] }
+ self.reactive_cache_lease_timeout = 30.seconds
+ self.reactive_cache_refresh_interval = 30.seconds
+ self.reactive_cache_lifetime = 1.minute
+
+ # Access to prometheus is directly through the API
+ prop_accessor :api_url
+
+ with_options presence: true, if: :activated? do
+ validates :api_url, url: true
+ end
+
+ after_save :clear_reactive_cache!
+
+ def initialize_properties
+ if properties.nil?
+ self.properties = {}
+ end
+ end
+
+ def title
+ 'Prometheus'
+ end
+
+ def description
+ 'Prometheus monitoring'
+ end
+
+ def help
+ 'Retrieves `container_cpu_usage_seconds_total` and `container_memory_usage_bytes` from the configured Prometheus server. An `environment` label is required on each metric to identify the Environment.'
+ end
+
+ def self.to_param
+ 'prometheus'
+ end
+
+ def fields
+ [
+ {
+ type: 'text',
+ name: 'api_url',
+ title: 'API URL',
+ placeholder: 'Prometheus API Base URL, like http://prometheus.example.com/'
+ }
+ ]
+ end
+
+ # Check we can connect to the Prometheus API
+ def test(*args)
+ client.ping
+
+ { success: true, result: 'Checked API endpoint' }
+ rescue Gitlab::PrometheusError => err
+ { success: false, result: err }
+ end
+
+ def metrics(environment)
+ with_reactive_cache(environment.slug) do |data|
+ data
+ end
+ end
+
+ # Cache metrics for specific environment
+ def calculate_reactive_cache(environment_slug)
+ return unless active? && project && !project.pending_delete?
+
+ memory_query = %{sum(container_memory_usage_bytes{container_name="app",environment="#{environment_slug}"})/1024/1024}
+ cpu_query = %{sum(rate(container_cpu_usage_seconds_total{container_name="app",environment="#{environment_slug}"}[2m]))}
+
+ {
+ success: true,
+ metrics: {
+ # Memory used in MB
+ memory_values: client.query_range(memory_query, start: 8.hours.ago),
+ memory_current: client.query(memory_query),
+ # CPU Usage rate in cores.
+ cpu_values: client.query_range(cpu_query, start: 8.hours.ago),
+ cpu_current: client.query(cpu_query)
+ },
+ last_update: Time.now.utc
+ }
+
+ rescue Gitlab::PrometheusError => err
+ { success: false, result: err.message }
+ end
+
+ def client
+ @prometheus ||= Gitlab::Prometheus.new(api_url: api_url)
+ end
+end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index e7cc8d6e083..6ab04440ca8 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -50,10 +50,6 @@ class Repository
end
end
- def self.storages
- Gitlab.config.repositories.storages
- end
-
def initialize(path_with_namespace, project)
@path_with_namespace = path_with_namespace
@project = project
@@ -316,11 +312,13 @@ class Repository
if !branch_name || branch_name == root_ref
branches.each do |branch|
cache.expire(:"diverging_commit_counts_#{branch.name}")
+ cache.expire(:"commit_count_#{branch.name}")
end
# In case a commit is pushed to a non-root branch we only have to flush the
# cache for said branch.
else
cache.expire(:"diverging_commit_counts_#{branch_name}")
+ cache.expire(:"commit_count_#{branch_name}")
end
end
@@ -500,6 +498,16 @@ class Repository
end
cache_method :commit_count, fallback: 0
+ def commit_count_for_ref(ref)
+ return 0 unless exists?
+
+ begin
+ cache.fetch(:"commit_count_#{ref}") { raw_repository.commit_count(ref) }
+ rescue Rugged::ReferenceError
+ 0
+ end
+ end
+
def branch_names
branches.map(&:name)
end
diff --git a/app/models/service.rb b/app/models/service.rb
index 3ef4cbead10..2f75a2e4e7f 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -232,6 +232,7 @@ class Service < ActiveRecord::Base
mattermost
pipelines_email
pivotaltracker
+ prometheus
pushover
redmine
slack_slash_commands
diff --git a/app/models/user.rb b/app/models/user.rb
index bd57904a2cd..76fb4cd470e 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -325,8 +325,7 @@ class User < ActiveRecord::Base
end
def find_by_personal_access_token(token_string)
- personal_access_token = PersonalAccessToken.active.find_by_token(token_string) if token_string
- personal_access_token&.user
+ PersonalAccessTokensFinder.new(state: 'active').find_by(token: token_string)&.user
end
# Returns a user for the given SSH key.
diff --git a/app/policies/ci/trigger_policy.rb b/app/policies/ci/trigger_policy.rb
new file mode 100644
index 00000000000..c90c9ac0583
--- /dev/null
+++ b/app/policies/ci/trigger_policy.rb
@@ -0,0 +1,13 @@
+module Ci
+ class TriggerPolicy < BasePolicy
+ def rules
+ delegate! @subject.project
+
+ if can?(:admin_build)
+ can! :admin_trigger if @subject.owner.blank? ||
+ @subject.owner == @user
+ can! :manage_trigger
+ end
+ end
+ end
+end
diff --git a/app/presenters/projects/settings/deploy_keys_presenter.rb b/app/presenters/projects/settings/deploy_keys_presenter.rb
new file mode 100644
index 00000000000..86ac513b3c0
--- /dev/null
+++ b/app/presenters/projects/settings/deploy_keys_presenter.rb
@@ -0,0 +1,60 @@
+module Projects
+ module Settings
+ class DeployKeysPresenter < Gitlab::View::Presenter::Simple
+ presents :project
+ delegate :size, to: :enabled_keys, prefix: true
+ delegate :size, to: :available_project_keys, prefix: true
+ delegate :size, to: :available_public_keys, prefix: true
+
+ def new_key
+ @key ||= DeployKey.new
+ end
+
+ def enabled_keys
+ @enabled_keys ||= project.deploy_keys
+ end
+
+ def any_keys_enabled?
+ enabled_keys.any?
+ end
+
+ def available_keys
+ @available_keys ||= current_user.accessible_deploy_keys - enabled_keys
+ end
+
+ def available_project_keys
+ @available_project_keys ||= current_user.project_deploy_keys - enabled_keys
+ end
+
+ def any_available_project_keys_enabled?
+ available_project_keys.any?
+ end
+
+ def key_available?(deploy_key)
+ available_keys.include?(deploy_key)
+ end
+
+ def available_public_keys
+ return @available_public_keys if defined?(@available_public_keys)
+
+ @available_public_keys ||= DeployKey.are_public - enabled_keys
+
+ # Public keys that are already used by another accessible project are already
+ # in @available_project_keys.
+ @available_public_keys -= available_project_keys
+ end
+
+ def any_available_public_keys_enabled?
+ available_public_keys.any?
+ end
+
+ def to_partial_path
+ 'projects/deploy_keys/index'
+ end
+
+ def form_partial_path
+ 'projects/deploy_keys/form'
+ end
+ end
+ end
+end
diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb
index 8a94c54b6ab..185838764c1 100644
--- a/app/services/boards/issues/list_service.rb
+++ b/app/services/boards/issues/list_service.rb
@@ -5,7 +5,7 @@ module Boards
issues = IssuesFinder.new(current_user, filter_params).execute
issues = without_board_labels(issues) unless movable_list?
issues = with_list_label(issues) if movable_list?
- issues
+ issues.reorder(Gitlab::Database.nulls_last_order('relative_position', 'ASC'))
end
private
@@ -26,7 +26,6 @@ module Boards
def filter_params
set_default_scope
- set_default_sort
set_project
set_state
@@ -37,10 +36,6 @@ module Boards
params[:scope] = 'all'
end
- def set_default_sort
- params[:sort] = 'priority'
- end
-
def set_project
params[:project_id] = project.id
end
diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb
index 96554a92a02..2a9981ab884 100644
--- a/app/services/boards/issues/move_service.rb
+++ b/app/services/boards/issues/move_service.rb
@@ -3,7 +3,7 @@ module Boards
class MoveService < BaseService
def execute(issue)
return false unless can?(current_user, :update_issue, issue)
- return false unless valid_move?
+ return false if issue_params.empty?
update_service.execute(issue)
end
@@ -14,7 +14,7 @@ module Boards
@board ||= project.boards.find(params[:board_id])
end
- def valid_move?
+ def move_between_lists?
moving_from_list.present? && moving_to_list.present? &&
moving_from_list != moving_to_list
end
@@ -32,11 +32,19 @@ module Boards
end
def issue_params
- {
- add_label_ids: add_label_ids,
- remove_label_ids: remove_label_ids,
- state_event: issue_state
- }
+ attrs = {}
+
+ if move_between_lists?
+ attrs.merge!(
+ add_label_ids: add_label_ids,
+ remove_label_ids: remove_label_ids,
+ state_event: issue_state,
+ )
+ end
+
+ attrs[:move_between_iids] = move_between_iids if move_between_iids
+
+ attrs
end
def issue_state
@@ -58,6 +66,12 @@ module Boards
Array(label_ids).compact
end
+
+ def move_between_iids
+ return unless params[:move_after_iid] || params[:move_before_iid]
+
+ [params[:move_after_iid], params[:move_before_iid]]
+ end
end
end
end
diff --git a/app/services/ci/register_build_service.rb b/app/services/ci/register_job_service.rb
index 5b52a0425de..0ab9042bf24 100644
--- a/app/services/ci/register_build_service.rb
+++ b/app/services/ci/register_job_service.rb
@@ -1,7 +1,7 @@
module Ci
# This class responsible for assigning
# proper pending build to runner on runner API request
- class RegisterBuildService
+ class RegisterJobService
include Gitlab::CurrentSettings
attr_reader :runner
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index dbe2fda27b5..bc7431c89a8 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -99,6 +99,8 @@ class GitPushService < BaseService
UpdateMergeRequestsWorker
.perform_async(@project.id, current_user.id, params[:oldrev], params[:newrev], params[:ref])
+ SystemHookPushWorker.perform_async(build_push_data.dup, :push_hooks)
+
EventCreateService.new.push(@project, current_user, build_push_data)
@project.execute_hooks(build_push_data.dup, :push_hooks)
@project.execute_services(build_push_data.dup, :push_hooks)
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index b618c3e038e..b071a398481 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -211,7 +211,7 @@ class IssuableBaseService < BaseService
label_ids = process_label_ids(params, existing_label_ids: issuable.label_ids)
params[:label_ids] = label_ids if labels_changing?(issuable.label_ids, label_ids)
- if params.present?
+ if issuable.changed? || params.present?
issuable.assign_attributes(params.merge(updated_by: current_user))
before_update(issuable)
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index 366b3572738..85b6eb3fe3d 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -13,6 +13,7 @@ module Issues
def before_create(issue)
spam_check(issue, current_user)
+ issue.move_to_end
end
def after_create(issuable)
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index 22e32b13259..a444c78b609 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -3,8 +3,8 @@ module Issues
include SpamCheckService
def execute(issue)
+ handle_move_between_iids(issue)
filter_spam_check_params
-
update(issue)
end
@@ -37,11 +37,13 @@ module Issues
end
added_labels = issue.labels - old_labels
+
if added_labels.present?
notification_service.relabeled_issue(issue, added_labels, current_user)
end
added_mentions = issue.mentioned_users - old_mentioned_users
+
if added_mentions.present?
notification_service.new_mentions_in_issue(issue, added_mentions, current_user)
end
@@ -55,8 +57,24 @@ module Issues
Issues::CloseService
end
+ def handle_move_between_iids(issue)
+ return unless params[:move_between_iids]
+
+ after_iid, before_iid = params.delete(:move_between_iids)
+
+ issue_before = get_issue_if_allowed(issue.project, before_iid) if before_iid
+ issue_after = get_issue_if_allowed(issue.project, after_iid) if after_iid
+
+ issue.move_between(issue_before, issue_after)
+ end
+
private
+ def get_issue_if_allowed(project, iid)
+ issue = project.issues.find_by(iid: iid)
+ issue if can?(current_user, :update_issue, issue)
+ end
+
def create_confidentiality_note(issue)
SystemNoteService.change_issue_confidentiality(issue, issue.project, current_user)
end
diff --git a/app/validators/namespace_validator.rb b/app/validators/namespace_validator.rb
index eb3ed31b65b..03921db6947 100644
--- a/app/validators/namespace_validator.rb
+++ b/app/validators/namespace_validator.rb
@@ -35,12 +35,21 @@ class NamespaceValidator < ActiveModel::EachValidator
users
].freeze
+ WILDCARD_ROUTES = %w[tree commits wikis new edit create update logs_tree
+ preview blob blame raw files create_dir find_file].freeze
+
+ STRICT_RESERVED = (RESERVED + WILDCARD_ROUTES).freeze
+
def self.valid?(value)
!reserved?(value) && follow_format?(value)
end
- def self.reserved?(value)
- RESERVED.include?(value)
+ def self.reserved?(value, strict: false)
+ if strict
+ STRICT_RESERVED.include?(value)
+ else
+ RESERVED.include?(value)
+ end
end
def self.follow_format?(value)
@@ -54,7 +63,9 @@ class NamespaceValidator < ActiveModel::EachValidator
record.errors.add(attribute, Gitlab::Regex.namespace_regex_message)
end
- if reserved?(value)
+ strict = record.is_a?(Group) && record.parent_id
+
+ if reserved?(value, strict: strict)
record.errors.add(attribute, "#{value} is a reserved name")
end
end
diff --git a/app/validators/project_path_validator.rb b/app/validators/project_path_validator.rb
index 36279daa743..ee2ae65be7b 100644
--- a/app/validators/project_path_validator.rb
+++ b/app/validators/project_path_validator.rb
@@ -14,10 +14,8 @@ class ProjectPathValidator < ActiveModel::EachValidator
# without tree as reserved name routing can match 'group/project' as group name,
# 'tree' as project name and 'deploy_keys' as route.
#
- RESERVED = (NamespaceValidator::RESERVED -
- %w[dashboard help ci admin search notes services assets profile public] +
- %w[tree commits wikis new edit create update logs_tree
- preview blob blame raw files create_dir find_file]).freeze
+ RESERVED = (NamespaceValidator::STRICT_RESERVED -
+ %w[dashboard help ci admin search notes services assets profile public]).freeze
def self.valid?(value)
!reserved?(value)
diff --git a/app/views/admin/abuse_reports/index.html.haml b/app/views/admin/abuse_reports/index.html.haml
index 6c48328da4f..6a5e170ddd8 100644
--- a/app/views/admin/abuse_reports/index.html.haml
+++ b/app/views/admin/abuse_reports/index.html.haml
@@ -16,4 +16,4 @@
- else
.empty-state
.text-center
- %h4 There are no abuse reports! #{emoji_icon 'tada'}
+ %h4 There are no abuse reports! #{emoji_icon('tada')}
diff --git a/app/views/admin/impersonation_tokens/index.html.haml b/app/views/admin/impersonation_tokens/index.html.haml
new file mode 100644
index 00000000000..1378dde52ab
--- /dev/null
+++ b/app/views/admin/impersonation_tokens/index.html.haml
@@ -0,0 +1,8 @@
+- page_title "Impersonation Tokens", @user.name, "Users"
+= render 'admin/users/head'
+
+.row.prepend-top-default
+ .col-lg-12
+ = render "shared/personal_access_tokens_form", path: admin_user_impersonation_tokens_path, impersonation: true, token: @impersonation_token, scopes: @scopes
+
+ = render "shared/personal_access_tokens_table", impersonation: true, active_tokens: @active_impersonation_tokens, inactive_tokens: @inactive_impersonation_tokens
diff --git a/app/views/admin/users/_head.html.haml b/app/views/admin/users/_head.html.haml
index 9984e733956..d20be373564 100644
--- a/app/views/admin/users/_head.html.haml
+++ b/app/views/admin/users/_head.html.haml
@@ -21,4 +21,6 @@
= link_to "SSH keys", keys_admin_user_path(@user)
= nav_link(controller: :identities) do
= link_to "Identities", admin_user_identities_path(@user)
+ = nav_link(controller: :impersonation_tokens) do
+ = link_to "Impersonation Tokens", admin_user_impersonation_tokens_path(@user)
.append-bottom-default
diff --git a/app/views/award_emoji/_awards_block.html.haml b/app/views/award_emoji/_awards_block.html.haml
index e3305e21e96..a1ef34dc588 100644
--- a/app/views/award_emoji/_awards_block.html.haml
+++ b/app/views/award_emoji/_awards_block.html.haml
@@ -4,7 +4,7 @@
%button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button",
class: (award_state_class(awards, current_user)),
data: { placement: "bottom", title: award_user_list(awards, current_user) } }
- = emoji_icon(emoji, sprite: false)
+ = emoji_icon(emoji)
%span.award-control-text.js-counter
= awards.count
diff --git a/app/views/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml
index a196561f381..82aa51f9778 100644
--- a/app/views/doorkeeper/authorizations/new.html.haml
+++ b/app/views/doorkeeper/authorizations/new.html.haml
@@ -27,6 +27,7 @@
= hidden_field_tag :state, @pre_auth.state
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
+ = hidden_field_tag :nonce, @pre_auth.nonce
= submit_tag "Authorize", class: "btn btn-success wide pull-left"
= form_tag oauth_authorization_path, method: :delete do
= hidden_field_tag :client_id, @pre_auth.client.uid
@@ -34,4 +35,5 @@
= hidden_field_tag :state, @pre_auth.state
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
+ = hidden_field_tag :nonce, @pre_auth.nonce
= submit_tag "Deny", class: "btn btn-danger prepend-left-10"
diff --git a/app/views/emojis/index.html.haml b/app/views/emojis/index.html.haml
deleted file mode 100644
index 49bd9acd2db..00000000000
--- a/app/views/emojis/index.html.haml
+++ /dev/null
@@ -1,11 +0,0 @@
-.emoji-menu
- = text_field_tag :emoji_search, "", class: "emoji-search search-input form-control", placeholder: "Search emoji"
- .emoji-menu-content
- - Gitlab::AwardEmoji.emoji_by_category.each do |category, emojis|
- %h5.emoji-menu-title
- = Gitlab::AwardEmoji::CATEGORIES[category]
- %ul.clearfix.emoji-menu-list
- - emojis.each do |emoji|
- %li.pull-left.text-center.emoji-menu-list-item
- %button.emoji-menu-btn.text-center.js-emoji-btn{ type: "button" }
- = emoji_icon(emoji["name"], emoji["unicode"], emoji["aliases"])
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index 5d1369c2010..2684f16c373 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -131,6 +131,12 @@
%tr
%td.shortcut
.key g
+ .key e
+ %td
+ Go to the project's activity feed
+ %tr
+ %td.shortcut
+ .key g
.key f
%td
Go to files
@@ -155,6 +161,12 @@
%tr
%td.shortcut
.key g
+ .key g
+ %td
+ Go to repository charts
+ %tr
+ %td.shortcut
+ .key g
.key i
%td
Go to issues
diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml
index 3daa1e90a8c..769f6fb0151 100644
--- a/app/views/layouts/_init_auto_complete.html.haml
+++ b/app/views/layouts/_init_auto_complete.html.haml
@@ -4,7 +4,6 @@
- if project
:javascript
gl.GfmAutoComplete.dataSources = {
- emojis: "#{emojis_namespace_project_autocomplete_sources_path(project.namespace, project)}",
members: "#{members_namespace_project_autocomplete_sources_path(project.namespace, project, type: noteable_type, type_id: params[:id])}",
issues: "#{issues_namespace_project_autocomplete_sources_path(project.namespace, project)}",
mergeRequests: "#{merge_requests_namespace_project_autocomplete_sources_path(project.namespace, project)}",
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 2335d467389..299dace3406 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -1,20 +1,4 @@
-- if current_user
- .controls
- .dropdown.project-settings-dropdown
- %a.dropdown-new.btn.btn-default#project-settings-button{ href: '#', 'data-toggle' => 'dropdown' }
- = icon('cog')
- = icon('caret-down')
- %ul.dropdown-menu.dropdown-menu-align-right
- - can_edit = can?(current_user, :admin_project, @project)
-
- = render 'layouts/nav/project_settings', can_edit: can_edit
-
- - if can_edit
- %li.divider
- %li
- = link_to edit_project_path(@project) do
- Edit Project
-
+- can_edit = can?(current_user, :admin_project, @project)
.scrolling-tabs-container{ class: nav_control_class }
.fade-left
= icon('angle-left')
@@ -71,18 +55,41 @@
%span
Snippets
- -# Global shortcut to network page for compatibility
+ - if project_nav_tab? :settings
+ = nav_link(path: %w[projects#edit members#show integrations#show repository#show ci_cd#show pages#show]) do
+ = link_to edit_project_path(@project), title: 'Settings', class: 'shortcuts-tree' do
+ %span
+ Settings
+ - else
+ = nav_link(path: %w[members#show]) do
+ = link_to namespace_project_settings_members_path(@project.namespace, @project), title: 'Settings', class: 'shortcuts-tree' do
+ %span
+ Settings
+
+ -# Shortcut to Project > Activity
+ %li.hidden
+ = link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do
+ %span
+ Activity
+
+ -# Shortcut to Repository > Graph (formerly, Network)
- if project_nav_tab? :network
%li.hidden
= link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do
- Network
+ Graph
+
+ -# Shortcut to Repository > Charts (formerly, top-nav item "Graphs")
+ - unless @project.empty_repo?
+ %li.hidden
+ = link_to charts_namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Charts', class: 'shortcuts-repository-charts' do
+ Charts
- -# Shortcut to create a new issue
+ -# Shortcut to Issues > New Issue
%li.hidden
= link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'shortcuts-new-issue' do
Create a new issue
- -# Shortcut to builds page
+ -# Shortcut to Pipelines > Jobs
- if project_nav_tab? :builds
%li.hidden
= link_to project_builds_path(@project), title: 'Jobs', class: 'shortcuts-builds' do
diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml
deleted file mode 100644
index 665725f6862..00000000000
--- a/app/views/layouts/nav/_project_settings.html.haml
+++ /dev/null
@@ -1,28 +0,0 @@
-- if project_nav_tab? :team
- = nav_link(controller: [:members, :teams]) do
- = link_to namespace_project_settings_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do
- %span
- Members
-- if can_edit
- = nav_link(controller: :deploy_keys) do
- = link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys' do
- %span
- Deploy Keys
- = nav_link(controller: :integrations) do
- = link_to namespace_project_settings_integrations_path(@project.namespace, @project), title: 'Integrations' do
- %span
- Integrations
- = nav_link(controller: :protected_branches) do
- = link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches' do
- %span
- Protected Branches
-
- - if @project.feature_available?(:builds, current_user)
- = nav_link(controller: :ci_cd) do
- = link_to namespace_project_settings_ci_cd_path(@project.namespace, @project), title: 'CI/CD Pipelines' do
- %span
- CI/CD Pipelines
- = nav_link(controller: :pages) do
- = link_to namespace_project_pages_path(@project.namespace, @project), title: 'Pages', data: {placement: 'right'} do
- %span
- Pages
diff --git a/app/views/profiles/personal_access_tokens/_form.html.haml b/app/views/profiles/personal_access_tokens/_form.html.haml
deleted file mode 100644
index 3f6efa33953..00000000000
--- a/app/views/profiles/personal_access_tokens/_form.html.haml
+++ /dev/null
@@ -1,21 +0,0 @@
-- personal_access_token = local_assigns.fetch(:personal_access_token)
-- scopes = local_assigns.fetch(:scopes)
-
-= form_for [:profile, personal_access_token], method: :post, html: { class: 'js-requires-input' } do |f|
-
- = form_errors(personal_access_token)
-
- .form-group
- = f.label :name, class: 'label-light'
- = f.text_field :name, class: "form-control", required: true
-
- .form-group
- = f.label :expires_at, class: 'label-light'
- = f.text_field :expires_at, class: "datepicker form-control"
-
- .form-group
- = f.label :scopes, class: 'label-light'
- = render 'shared/tokens/scopes_form', prefix: 'personal_access_token', token: personal_access_token, scopes: scopes
-
- .prepend-top-default
- = f.submit 'Create Personal Access Token', class: "btn btn-create"
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index 903b957c26b..0645ecad496 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -24,80 +24,11 @@
%hr
- %h5.prepend-top-0
- Add a Personal Access Token
- %p.profile-settings-content
- Pick a name for the application, and we'll give you a unique token.
-
- = render "form", personal_access_token: @personal_access_token, scopes: @scopes
-
- %hr
-
- %h5 Active Personal Access Tokens (#{@active_personal_access_tokens.length})
-
- - if @active_personal_access_tokens.present?
- .table-responsive
- %table.table.active-personal-access-tokens
- %thead
- %tr
- %th Name
- %th Created
- %th Expires
- %th Scopes
- %th
- %tbody
- - @active_personal_access_tokens.each do |token|
- %tr
- %td= token.name
- %td= token.created_at.to_date.to_s(:medium)
- %td
- - if token.expires_at.present?
- = token.expires_at.to_date.to_s(:medium)
- - else
- %span.personal-access-tokens-never-expires-label Never
- %td= token.scopes.present? ? token.scopes.join(", ") : "<no scopes selected>"
- %td= link_to "Revoke", revoke_profile_personal_access_token_path(token), method: :put, class: "btn btn-danger pull-right", data: { confirm: "Are you sure you want to revoke this token? This action cannot be undone." }
-
- - else
- .settings-message.text-center
- You don't have any active tokens yet.
-
- %hr
-
- %h5 Inactive Personal Access Tokens (#{@inactive_personal_access_tokens.length})
-
- - if @inactive_personal_access_tokens.present?
- .table-responsive
- %table.table.inactive-personal-access-tokens
- %thead
- %tr
- %th Name
- %th Created
- %tbody
- - @inactive_personal_access_tokens.each do |token|
- %tr
- %td= token.name
- %td= token.created_at.to_date.to_s(:medium)
-
- - else
- .settings-message.text-center
- There are no inactive tokens.
+ = render "shared/personal_access_tokens_form", path: profile_personal_access_tokens_path, impersonation: false, token: @personal_access_token, scopes: @scopes
+ = render "shared/personal_access_tokens_table", impersonation: false, active_tokens: @active_personal_access_tokens, inactive_tokens: @inactive_personal_access_tokens
:javascript
- var $dateField = $('#personal_access_token_expires_at');
- var date = $dateField.val();
-
- new Pikaday({
- field: $dateField.get(0),
- theme: 'gitlab-theme',
- format: 'yyyy-mm-dd',
- minDate: new Date(),
- onSelect: function(dateText) {
- $dateField.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
- }
- });
-
$("#created-personal-access-token").click(function() {
this.select();
});
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index 41a7191302d..24ff74ecb3b 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -18,7 +18,7 @@
- else
= link_to title, '#'
-%ul.blob-commit-info.table-list.hidden-xs
+%ul.blob-commit-info.hidden-xs
- blob_commit = @repository.last_commit_for_path(@commit.id, blob.path)
= render blob_commit, project: @project, ref: @ref
diff --git a/app/views/projects/boards/components/_board_list.html.haml b/app/views/projects/boards/components/_board_list.html.haml
index 0993e880da9..4a4dd84d5d2 100644
--- a/app/views/projects/boards/components/_board_list.html.haml
+++ b/app/views/projects/boards/components/_board_list.html.haml
@@ -8,7 +8,7 @@
"v-show" => "!loading",
":data-board" => "list.id",
":class" => '{ "is-smaller": showIssueForm }' }
- %board-card{ "v-for" => "(issue, index) in orderedIssues",
+ %board-card{ "v-for" => "(issue, index) in issues",
"ref" => "issue",
":index" => "index",
":list" => "list",
@@ -17,7 +17,8 @@
":root-path" => "rootPath",
":disabled" => "disabled",
":key" => "issue.id" }
- %li.board-list-count.text-center{ "v-if" => "showCount" }
+ %li.board-list-count.text-center{ "v-if" => "showCount",
+ "data-issue-id" => "-1" }
= icon("spinner spin", "v-show" => "list.loadingMore" )
%span{ "v-if" => "list.issues.length === list.issuesSize" }
Showing all issues
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index 228dad528ab..307010edb58 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -1,6 +1,5 @@
- @no_container = true
- page_title "#{@build.name} (##{@build.id})", "Jobs"
-- trace_with_state = @build.trace_with_state
= render "projects/pipelines/head", build_subnav: true
%div{ class: container_class }
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 4d0b7a5ca85..d001e01609a 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -34,8 +34,9 @@
= revert_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false)
%li.clearfix
= cherry_pick_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false)
- %li.clearfix
- = link_to "Tag", new_namespace_project_tag_path(@project.namespace, @project, ref: @commit)
+ - if can_collaborate_with_project?
+ %li.clearfix
+ = link_to "Tag", new_namespace_project_tag_path(@project.namespace, @project, ref: @commit)
%li.divider
%li.dropdown-header
Download
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 002e3d345dc..6ab9a80e083 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -9,33 +9,34 @@
- cache_key.push(commit.status(ref)) if commit.status(ref)
= cache(cache_key, expires_in: 1.day) do
- %li.commit.table-list-row.js-toggle-container{ id: "commit-#{commit.short_id}" }
+ %li.commit.flex-list.js-toggle-container{ id: "commit-#{commit.short_id}" }
- .table-list-cell.avatar-cell.hidden-xs
+ .avatar-cell.hidden-xs
= author_avatar(commit, size: 36)
- .table-list-cell.commit-content
- = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message item-title"
- %span.commit-row-message.visible-xs-inline
- &middot;
- = commit.short_id
- - if commit.status(ref)
- .visible-xs-inline
- = render_commit_status(commit, ref: ref)
- - if commit.description?
- %a.text-expander.hidden-xs.js-toggle-button ...
+ .commit-detail
+ .commit-content
+ = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message item-title"
+ %span.commit-row-message.visible-xs-inline
+ &middot;
+ = commit.short_id
+ - if commit.status(ref)
+ .visible-xs-inline
+ = render_commit_status(commit, ref: ref)
+ - if commit.description?
+ %a.text-expander.hidden-xs.js-toggle-button ...
- - if commit.description?
- %pre.commit-row-description.js-toggle-content
- = preserve(markdown(commit.description, pipeline: :single_line, author: commit.author))
- .commiter
- = commit_author_link(commit, avatar: false, size: 24)
- committed
- #{time_ago_with_tooltip(commit.committed_date)}
+ - if commit.description?
+ %pre.commit-row-description.js-toggle-content
+ = preserve(markdown(commit.description, pipeline: :single_line, author: commit.author))
+ .commiter
+ = commit_author_link(commit, avatar: false, size: 24)
+ committed
+ #{time_ago_with_tooltip(commit.committed_date)}
- .table-list-cell.commit-actions.hidden-xs
- - if commit.status(ref)
- = render_commit_status(commit, ref: ref)
- = clipboard_button(clipboard_text: commit.id, title: "Copy commit SHA to clipboard")
- = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent"
- = link_to_browse_code(project, commit)
+ .commit-actions.flex-row.hidden-xs
+ - if commit.status(ref)
+ = render_commit_status(commit, ref: ref)
+ = clipboard_button(clipboard_text: commit.id, title: "Copy commit SHA to clipboard")
+ = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent"
+ = link_to_browse_code(project, commit)
diff --git a/app/views/projects/commits/_commit_list.html.haml b/app/views/projects/commits/_commit_list.html.haml
index 64d93e4141c..6f5835cb9be 100644
--- a/app/views/projects/commits/_commit_list.html.haml
+++ b/app/views/projects/commits/_commit_list.html.haml
@@ -11,4 +11,4 @@
%li.warning-row.unstyled
#{number_with_delimiter(hidden)} additional commits have been omitted to prevent performance issues.
- else
- %ul.content-list.table-list= render commits, project: @project, ref: @ref
+ %ul.content-list= render commits, project: @project, ref: @ref
diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml
index 904cdb5767f..88c7d7bc44b 100644
--- a/app/views/projects/commits/_commits.html.haml
+++ b/app/views/projects/commits/_commits.html.haml
@@ -4,7 +4,7 @@
- commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, commits|
%li.commit-header #{day.strftime('%d %b, %Y')} #{pluralize(commits.count, 'commit')}
%li.commits-row
- %ul.content-list.commit-list.table-list.table-wide
+ %ul.content-list.commit-list
= render commits, project: project, ref: ref
- if hidden > 0
diff --git a/app/views/projects/deploy_keys/_deploy_key.html.haml b/app/views/projects/deploy_keys/_deploy_key.html.haml
index d1e3cb14022..ec8fc4c9ee8 100644
--- a/app/views/projects/deploy_keys/_deploy_key.html.haml
+++ b/app/views/projects/deploy_keys/_deploy_key.html.haml
@@ -18,7 +18,7 @@
%span.key-created-at
created #{time_ago_with_tooltip(deploy_key.created_at)}
.visible-xs-block.visible-sm-block
- - if @available_keys.include?(deploy_key)
+ - if @deploy_keys.key_available?(deploy_key)
= link_to enable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: "btn btn-sm prepend-left-10", method: :put do
Enable
- else
diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml
index c91bb9c255a..1421da72418 100644
--- a/app/views/projects/deploy_keys/_form.html.haml
+++ b/app/views/projects/deploy_keys/_form.html.haml
@@ -1,5 +1,5 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: "js-requires-input" } do |f|
- = form_errors(@key)
+= form_for [@project.namespace.becomes(Namespace), @project, @deploy_keys.new_key], url: namespace_project_deploy_keys_path, html: { class: "js-requires-input" } do |f|
+ = form_errors(@deploy_keys.new_key)
.form-group
= f.label :title, class: "label-light"
= f.text_field :title, class: 'form-control', autofocus: true, required: true
diff --git a/app/views/projects/deploy_keys/_index.html.haml b/app/views/projects/deploy_keys/_index.html.haml
new file mode 100644
index 00000000000..0cbe9b3275a
--- /dev/null
+++ b/app/views/projects/deploy_keys/_index.html.haml
@@ -0,0 +1,34 @@
+.row.prepend-top-default
+ .col-lg-3.profile-settings-sidebar
+ %h4.prepend-top-0
+ Deploy Keys
+ %p
+ Deploy keys allow read-only access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one.
+ .col-lg-9
+ %h5.prepend-top-0
+ Create a new deploy key for this project
+ = render @deploy_keys.form_partial_path
+ .col-lg-9.col-lg-offset-3
+ %hr
+ .col-lg-9.col-lg-offset-3.append-bottom-default.deploy-keys
+ %h5.prepend-top-0
+ Enabled deploy keys for this project (#{@deploy_keys.enabled_keys_size})
+ - if @deploy_keys.any_keys_enabled?
+ %ul.well-list
+ = render partial: 'projects/deploy_keys/deploy_key', collection: @deploy_keys.enabled_keys, as: :deploy_key
+ - else
+ .settings-message.text-center
+ No deploy keys found. Create one with the form above.
+ %h5.prepend-top-default
+ Deploy keys from projects you have access to (#{@deploy_keys.available_project_keys_size})
+ - if @deploy_keys.any_available_project_keys_enabled?
+ %ul.well-list
+ = render partial: 'projects/deploy_keys/deploy_key', collection: @deploy_keys.available_project_keys, as: :deploy_key
+ - else
+ .settings-message.text-center
+ No deploy keys from your projects could be found. Create one with the form above or add existing one below.
+ - if @deploy_keys.any_available_public_keys_enabled?
+ %h5.prepend-top-default
+ Public deploy keys available to any project (#{@deploy_keys.available_public_keys_size})
+ %ul.well-list
+ = render partial: 'projects/deploy_keys/deploy_key', collection: @deploy_keys.available_public_keys, as: :deploy_key
diff --git a/app/views/projects/deploy_keys/index.html.haml b/app/views/projects/deploy_keys/index.html.haml
deleted file mode 100644
index 04fbb37d93f..00000000000
--- a/app/views/projects/deploy_keys/index.html.haml
+++ /dev/null
@@ -1,36 +0,0 @@
-- page_title "Deploy Keys"
-
-.row.prepend-top-default
- .col-lg-3.profile-settings-sidebar
- %h4.prepend-top-0
- = page_title
- %p
- Deploy keys allow read-only access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one.
- .col-lg-9
- %h5.prepend-top-0
- Create a new deploy key for this project
- = render "form"
- .col-lg-9.col-lg-offset-3
- %hr
- .col-lg-9.col-lg-offset-3.append-bottom-default.deploy-keys
- %h5.prepend-top-0
- Enabled deploy keys for this project (#{@enabled_keys.size})
- - if @enabled_keys.any?
- %ul.well-list
- = render @enabled_keys
- - else
- .settings-message.text-center
- No deploy keys found. Create one with the form above or add existing one below.
- %h5.prepend-top-default
- Deploy keys from projects you have access to (#{@available_project_keys.size})
- - if @available_project_keys.any?
- %ul.well-list
- = render @available_project_keys
- - else
- .settings-message.text-center
- No deploy keys from your projects could be found. Create one with the form above or add existing one below.
- - if @available_public_keys.any?
- %h5.prepend-top-default
- Public deploy keys available to any project (#{@available_public_keys.size})
- %ul.well-list
- = render @available_public_keys
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 83ae9fd10ec..2802a4eca7b 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -1,3 +1,4 @@
+= render "projects/settings/head"
.project-edit-container
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/projects/environments/_metrics_button.html.haml b/app/views/projects/environments/_metrics_button.html.haml
new file mode 100644
index 00000000000..acbac1869fd
--- /dev/null
+++ b/app/views/projects/environments/_metrics_button.html.haml
@@ -0,0 +1,6 @@
+- environment = local_assigns.fetch(:environment)
+
+- return unless environment.has_metrics? && can?(current_user, :read_environment, environment)
+
+= link_to environment_metrics_path(environment), title: 'See metrics', class: 'btn metrics-button' do
+ = icon('area-chart')
diff --git a/app/views/projects/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml
new file mode 100644
index 00000000000..f8e94ca98ae
--- /dev/null
+++ b/app/views/projects/environments/metrics.html.haml
@@ -0,0 +1,21 @@
+- @no_container = true
+- page_title "Metrics for environment", @environment.name
+= render "projects/pipelines/head"
+
+%div{ class: container_class }
+ .top-area
+ .row
+ .col-sm-6
+ %h3.page-title
+ Environment:
+ = @environment.name
+
+ .col-sm-6
+ .nav-controls
+ = render 'projects/deployments/actions', deployment: @environment.last_deployment
+ .row
+ .col-sm-12
+ %svg.prometheus-graph{ 'graph-type' => 'cpu_values' }
+ .row
+ .col-sm-12
+ %svg.prometheus-graph{ 'graph-type' => 'memory_values' }
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index 7036325fff8..29a98f23b88 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -8,6 +8,7 @@
%h3.page-title= @environment.name
.col-md-3
.nav-controls
+ = render 'projects/environments/metrics_button', environment: @environment
= render 'projects/environments/terminal_button', environment: @environment
= render 'projects/environments/external_url', environment: @environment
- if can?(current_user, :update_environment, @environment)
diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml
index 7076f5db015..8b011af78eb 100644
--- a/app/views/projects/issues/_form.html.haml
+++ b/app/views/projects/issues/_form.html.haml
@@ -1,8 +1,2 @@
= form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form common-note-form js-quick-submit js-requires-input' } do |f|
= render 'shared/issuable/form', f: f, issuable: @issue
-
-:javascript
- $('.assign-to-me-link').on('click', function(e){
- $('#issue_assignee_id').val("#{current_user.id}").trigger("change");
- e.preventDefault();
- });
diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml
index 88525f4036a..9607a7b5d06 100644
--- a/app/views/projects/merge_requests/_form.html.haml
+++ b/app/views/projects/merge_requests/_form.html.haml
@@ -1,8 +1,2 @@
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal common-note-form js-requires-input js-quick-submit' } do |f|
= render 'shared/issuable/form', f: f, issuable: @merge_request
-
-:javascript
- $('.assign-to-me-link').on('click', function(e){
- $('#merge_request_assignee_id').val("#{current_user.id}").trigger("change");
- e.preventDefault();
- });
diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml
index 466ec1475d8..ad14b4e583e 100644
--- a/app/views/projects/merge_requests/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/_new_compare.html.haml
@@ -21,7 +21,7 @@
selected: f.object.source_project_id
.merge-request-select.dropdown
= f.hidden_field :source_branch
- = dropdown_toggle f.object.source_branch || "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" }
+ = dropdown_toggle local_assigns.fetch(f.object.source_branch, "Select source branch"), { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" }
.dropdown-menu.dropdown-menu-selectable.dropdown-source-branch
= dropdown_title("Select source branch")
= dropdown_filter("Search branches")
@@ -30,7 +30,7 @@
branches: @merge_request.source_branches,
selected: f.object.source_branch
.panel-footer
- = icon('spinner spin', class: 'js-source-loading')
+ .text-center= icon('spinner spin', class: 'js-source-loading')
%ul.list-unstyled.mr_source_commit
.col-md-6
@@ -60,7 +60,7 @@
branches: @merge_request.target_branches,
selected: f.object.target_branch
.panel-footer
- = icon('spinner spin', class: "js-target-loading")
+ .text-center= icon('spinner spin', class: "js-target-loading")
%ul.list-unstyled.mr_target_commit
- if @merge_request.errors.any?
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index bd72310c16b..e7fcac4c477 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -52,11 +52,6 @@
= spinner
:javascript
- $('.assign-to-me-link').on('click', function(e){
- $('#merge_request_assignee_id').val("#{current_user.id}").trigger("change");
- e.preventDefault();
- });
-:javascript
var merge_request = new MergeRequest({
action: "#{(@show_changes_tab ? 'new/diffs' : 'new')}"
});
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 03d618327d4..17be0490a86 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -29,9 +29,9 @@
%li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch)
%li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
.normal
- %span Request to merge
+ %span <b>Request to merge</b>
%span.label-branch= source_branch_with_namespace(@merge_request)
- %span into
+ %span <b>into</b>
%span.label-branch
= link_to_if @merge_request.target_branch_exists?, @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch)
- if @merge_request.open? && @merge_request.diverged_from_target_branch?
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index c676953f729..1298376ac25 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -1,8 +1,8 @@
- if @pipeline
.mr-widget-heading
- - %w[success success_with_warnings skipped canceled failed running pending].each do |status|
+ - %w[success success_with_warnings skipped manual canceled failed running pending].each do |status|
.ci_widget{ class: "ci-#{status}", style: ("display:none" unless @pipeline.status == status) }
- %div{ class: "ci-status-icon-#{status}" }
+ %div{ class: "ci-status-icon ci-status-icon-#{status}" }
= link_to namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'icon-link' do
= ci_icon_for_status(status)
%span
diff --git a/app/views/projects/merge_requests/widget/_merged.html.haml b/app/views/projects/merge_requests/widget/_merged.html.haml
index 7794d6d7df2..adc3bbc37f3 100644
--- a/app/views/projects/merge_requests/widget/_merged.html.haml
+++ b/app/views/projects/merge_requests/widget/_merged.html.haml
@@ -7,28 +7,46 @@
by #{link_to_member(@project, @merge_request.merge_event.author, avatar: true)}
#{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
- if !@merge_request.source_branch_exists? || params[:deleted_source_branch]
- %p
- The changes were merged into
- #{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}.
- The source branch has been removed.
+ .remove-message-pipes
+ %ul
+ %li
+ %span
+ The changes were merged into
+ #{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}.
+ %li
+ %span
+ The source branch has been removed.
= render 'projects/merge_requests/widget/merged_buttons'
- elsif @merge_request.can_remove_source_branch?(current_user)
- .remove_source_branch_widget
- %p
- The changes were merged into
- #{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}.
- You can remove the source branch now.
+ .remove_source_branch_widget.remove-message-pipes
+ %ul
+ %li
+ %span
+ The changes were merged into
+ #{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}.
+ %li
+ %span
+ You can remove the source branch now.
= render 'projects/merge_requests/widget/merged_buttons', source_branch_exists: true
- .remove_source_branch_widget.failed.hide
- %p
- Failed to remove source branch '#{@merge_request.source_branch}'.
-
- .remove_source_branch_in_progress.hide
- %p
- = icon('spinner spin')
- Removing source branch '#{@merge_request.source_branch}'. Please wait, this page will be automatically reloaded.
+ .remove_source_branch_widget.failed.remove-message-pipes.hide
+ %ul
+ %li
+ %span
+ Failed to remove source branch '#{@merge_request.source_branch}'.
+ .remove_source_branch_in_progress.remove-message-pipes.hide
+ %ul
+ %li
+ %span
+ = icon('spinner spin')
+ Removing source branch '#{@merge_request.source_branch}'.
+ %li
+ %span
+ Please wait, this page will be automatically reloaded.
- else
- %p
- The changes were merged into
- #{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}.
- = render 'projects/merge_requests/widget/merged_buttons'
+ .remove-message-pipes
+ %ul
+ %li
+ %span
+ The changes were merged into
+ #{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}.
+ = render 'projects/merge_requests/widget/merged_buttons'
diff --git a/app/views/projects/merge_requests/widget/_merged_buttons.haml b/app/views/projects/merge_requests/widget/_merged_buttons.haml
index 9eef011b591..caf3bf54eef 100644
--- a/app/views/projects/merge_requests/widget/_merged_buttons.haml
+++ b/app/views/projects/merge_requests/widget/_merged_buttons.haml
@@ -9,6 +9,6 @@
= icon('trash-o')
Remove Source Branch
- if mr_can_be_reverted
- = revert_commit_link(@merge_request.merge_commit, namespace_project_merge_request_path(@project.namespace, @project, @merge_request), btn_class: "warning")
+ = revert_commit_link(@merge_request.merge_commit, namespace_project_merge_request_path(@project.namespace, @project, @merge_request), btn_class: "close")
- if mr_can_be_cherry_picked
= cherry_pick_commit_link(@merge_request.merge_commit, namespace_project_merge_request_path(@project.namespace, @project, @merge_request), btn_class: "default")
diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml
index f0ccc4e00fd..bc426f1dc0c 100644
--- a/app/views/projects/merge_requests/widget/_open.html.haml
+++ b/app/views/projects/merge_requests/widget/_open.html.haml
@@ -27,6 +27,8 @@
= render 'projects/merge_requests/widget/open/build_failed'
- elsif !@merge_request.mergeable_discussions_state?
= render 'projects/merge_requests/widget/open/unresolved_discussions'
+ - elsif @pipeline&.blocked?
+ = render 'projects/merge_requests/widget/open/manual'
- elsif @merge_request.can_be_merged? || resolved_conflicts
= render 'projects/merge_requests/widget/open/accept'
diff --git a/app/views/projects/merge_requests/widget/open/_conflicts.html.haml b/app/views/projects/merge_requests/widget/open/_conflicts.html.haml
index c98b2c42597..621ee313026 100644
--- a/app/views/projects/merge_requests/widget/open/_conflicts.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_conflicts.html.haml
@@ -3,20 +3,24 @@
- can_merge = @merge_request.can_be_merged_via_command_line_by?(current_user)
%h4.has-conflicts
- = icon("exclamation-triangle")
- This merge request contains merge conflicts
+ %p
+ = icon("exclamation-triangle")
+ This merge request contains merge conflicts
-%p
- To merge this request, resolve these conflicts
- - if can_resolve && !can_resolve_in_ui
- locally
- or
- - unless can_merge
- ask someone with write access to this repository to
- merge it locally.
+.remove-message-pipes
+ %ul
+ %li
+ %span
+ To merge this request, resolve these conflicts
+ - if can_resolve && !can_resolve_in_ui
+ locally
+ or
+ - unless can_merge
+ ask someone with write access to this repository to
+ merge it locally.
- if (can_resolve && can_resolve_in_ui) || can_merge
- .btn-group
+ .merged-buttons.clearfix
- if can_resolve && can_resolve_in_ui
= link_to "Resolve conflicts", conflicts_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "btn"
- if can_merge
diff --git a/app/views/projects/merge_requests/widget/open/_manual.html.haml b/app/views/projects/merge_requests/widget/open/_manual.html.haml
new file mode 100644
index 00000000000..9078b7e21dd
--- /dev/null
+++ b/app/views/projects/merge_requests/widget/open/_manual.html.haml
@@ -0,0 +1,4 @@
+%h4
+ Pipeline blocked
+%p
+ The pipeline for this merge request requires a manual action to proceed.
diff --git a/app/views/projects/merge_requests/widget/open/_merge_when_pipeline_succeeds.html.haml b/app/views/projects/merge_requests/widget/open/_merge_when_pipeline_succeeds.html.haml
index 40a683d3fbd..5f347acce4d 100644
--- a/app/views/projects/merge_requests/widget/open/_merge_when_pipeline_succeeds.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_merge_when_pipeline_succeeds.html.haml
@@ -4,15 +4,20 @@
%h4
Set by #{link_to_member(@project, @merge_request.merge_user, avatar: true)}
to be merged automatically when the pipeline succeeds.
-%div
- %p
- = succeed '.' do
- The changes will be merged into
- %span.label-branch= @merge_request.target_branch
- - if @merge_request.remove_source_branch?
- The source branch will be removed.
- - else
- The source branch will not be removed.
+.remove-message-pipes
+ %ul
+ %li
+ %span
+ = succeed '.' do
+ The changes will be merged into #{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}
+ - if @merge_request.remove_source_branch?
+ %li
+ %span
+ The source branch will be removed.
+ - else
+ %li
+ %span
+ The source branch will not be removed.
- remove_source_branch_button = !@merge_request.remove_source_branch? && @merge_request.can_remove_source_branch?(current_user) && @merge_request.merge_user == current_user
- user_can_cancel_automatic_merge = @merge_request.can_cancel_merge_when_pipeline_succeeds?(current_user)
diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml
index b6595269b06..259d5bd63d6 100644
--- a/app/views/projects/pages/show.html.haml
+++ b/app/views/projects/pages/show.html.haml
@@ -1,4 +1,6 @@
- page_title 'Pages'
+= render "projects/settings/head"
+
%h3.page_title
Pages
diff --git a/app/views/projects/pipelines/_stage.html.haml b/app/views/projects/pipelines/_stage.html.haml
index a0b14a7274a..3feb99cfcd7 100644
--- a/app/views/projects/pipelines/_stage.html.haml
+++ b/app/views/projects/pipelines/_stage.html.haml
@@ -1,3 +1,5 @@
-- @stage.statuses.latest.each do |status|
- %li
- = render 'ci/status/dropdown_graph_badge', subject: status
+- grouped_statuses = @stage.statuses.latest_ordered.group_by(&:status)
+- HasStatus::ORDERED_STATUSES.each do |ordered_status|
+ - grouped_statuses.fetch(ordered_status, []).each do |status|
+ %li
+ = render 'ci/status/dropdown_graph_badge', subject: status
diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml
index 04b19a8c5a7..cf0db943865 100644
--- a/app/views/projects/protected_branches/_branches_list.html.haml
+++ b/app/views/projects/protected_branches/_branches_list.html.haml
@@ -23,6 +23,6 @@
- if can_admin_project
%th
%tbody
- = render partial: @protected_branches, locals: { can_admin_project: can_admin_project }
+ = render partial: 'projects/protected_branches/protected_branch', collection: @protected_branches, locals: { can_admin_project: can_admin_project}
= paginate @protected_branches, theme: 'gitlab'
diff --git a/app/views/projects/protected_branches/_create_protected_branch.html.haml b/app/views/projects/protected_branches/_create_protected_branch.html.haml
index e95a3b1b4c3..b8e885b4d9a 100644
--- a/app/views/projects/protected_branches/_create_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/_create_protected_branch.html.haml
@@ -10,7 +10,7 @@
= f.label :name, class: 'col-md-2 text-right' do
Branch:
.col-md-10
- = render partial: "dropdown", locals: { f: f }
+ = render partial: "projects/protected_branches/dropdown", locals: { f: f }
.help-block
= link_to 'Wildcards', help_page_path('user/project/protected_branches', anchor: 'wildcard-protected-branches')
such as
diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/_index.html.haml
index b3b419bd92d..2d8c519c025 100644
--- a/app/views/projects/protected_branches/index.html.haml
+++ b/app/views/projects/protected_branches/_index.html.haml
@@ -1,11 +1,10 @@
-- page_title "Protected branches"
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('protected_branches')
.row.prepend-top-default.append-bottom-default
.col-lg-3
%h4.prepend-top-0
- = page_title
+ Protected Branches
%p Keep stable branches secure and force developers to use merge requests.
%p.prepend-top-20
By default, protected branches are designed to:
@@ -17,6 +16,6 @@
%p.append-bottom-0 Read more about #{link_to "protected branches", help_page_path("user/project/protected_branches"), class: "underlined-link"} and #{link_to "project permissions", help_page_path("user/permissions"), class: "underlined-link"}.
.col-lg-9
- if can? current_user, :admin_project, @project
- = render 'create_protected_branch'
+ = render 'projects/protected_branches/create_protected_branch'
- = render "branches_list"
+ = render "projects/protected_branches/branches_list"
diff --git a/app/views/projects/protected_branches/_protected_branch.html.haml b/app/views/projects/protected_branches/_protected_branch.html.haml
index 0193800dedf..b2a6b8469a3 100644
--- a/app/views/projects/protected_branches/_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/_protected_branch.html.haml
@@ -14,7 +14,7 @@
- else
(branch was removed from repository)
- = render partial: 'update_protected_branch', locals: { protected_branch: protected_branch }
+ = render partial: 'projects/protected_branches/update_protected_branch', locals: { protected_branch: protected_branch }
- if can_admin_project
%td
diff --git a/app/views/projects/settings/_head.html.haml b/app/views/projects/settings/_head.html.haml
new file mode 100644
index 00000000000..88bcb541dac
--- /dev/null
+++ b/app/views/projects/settings/_head.html.haml
@@ -0,0 +1,33 @@
+= content_for :sub_nav do
+ .scrolling-tabs-container.sub-nav-scroll
+ = render 'shared/nav_scroll'
+ .nav-links.sub-nav.scrolling-tabs
+ %ul{ class: container_class }
+ - can_edit = can?(current_user, :admin_project, @project)
+ - if can_edit
+ = nav_link(controller: :projects) do
+ = link_to edit_project_path(@project), title: 'General' do
+ %span
+ General
+ = nav_link(controller: :members) do
+ = link_to project_settings_members_path(@project), title: 'Members' do
+ %span
+ Members
+ - if can_edit
+ = nav_link(controller: :integrations) do
+ = link_to project_settings_integrations_path(@project), title: 'Integrations' do
+ %span
+ Integrations
+ = nav_link(controller: :repository) do
+ = link_to namespace_project_settings_repository_path(@project.namespace, @project), title: 'Repository' do
+ %span
+ Repository
+ - if @project.feature_available?(:builds, current_user)
+ = nav_link(controller: :ci_cd) do
+ = link_to namespace_project_settings_ci_cd_path(@project.namespace, @project), title: 'CI/CD Pipelines' do
+ %span
+ CI/CD Pipelines
+ = nav_link(controller: :pages) do
+ = link_to namespace_project_pages_path(@project.namespace, @project), title: 'Pages' do
+ %span
+ Pages
diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml
index 52f5f7b81e2..e2603096014 100644
--- a/app/views/projects/settings/ci_cd/show.html.haml
+++ b/app/views/projects/settings/ci_cd/show.html.haml
@@ -1,4 +1,5 @@
- page_title "CI/CD Pipelines"
+= render "projects/settings/head"
= render 'projects/runners/index'
= render 'projects/variables/index'
diff --git a/app/views/projects/settings/integrations/show.html.haml b/app/views/projects/settings/integrations/show.html.haml
index aa38a889cdd..f69992566b5 100644
--- a/app/views/projects/settings/integrations/show.html.haml
+++ b/app/views/projects/settings/integrations/show.html.haml
@@ -1,3 +1,4 @@
- page_title 'Integrations'
+= render "projects/settings/head"
= render 'projects/hooks/index'
= render 'projects/services/index'
diff --git a/app/views/projects/settings/members/show.html.haml b/app/views/projects/settings/members/show.html.haml
index d81ed7bb609..20e1ad68244 100644
--- a/app/views/projects/settings/members/show.html.haml
+++ b/app/views/projects/settings/members/show.html.haml
@@ -1,4 +1,5 @@
- page_title "Members"
+= render "projects/settings/head"
= render "projects/project_members/index"
- if can?(current_user, :admin_project, @project)
diff --git a/app/views/projects/settings/repository/show.html.haml b/app/views/projects/settings/repository/show.html.haml
new file mode 100644
index 00000000000..4c02302e161
--- /dev/null
+++ b/app/views/projects/settings/repository/show.html.haml
@@ -0,0 +1,5 @@
+- page_title "Repository"
+= render "projects/settings/head"
+
+= render @deploy_keys
+= render "projects/protected_branches/index"
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 30d185e6556..de1229d58aa 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -74,8 +74,9 @@
Set up auto deploy
- if @repository.commit
- .project-last-commit{ class: container_class }
- = render 'projects/last_commit', commit: @repository.commit, ref: current_ref, project: @project
+ %div{ class: container_class }
+ .project-last-commit
+ = render 'projects/last_commit', commit: @repository.commit, ref: current_ref, project: @project
%div{ class: container_class }
- if @project.archived?
diff --git a/app/views/projects/triggers/_content.html.haml b/app/views/projects/triggers/_content.html.haml
new file mode 100644
index 00000000000..ea32eac2ae2
--- /dev/null
+++ b/app/views/projects/triggers/_content.html.haml
@@ -0,0 +1,14 @@
+%h4.prepend-top-0
+ Triggers
+%p.prepend-top-20
+ Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will
+ impersonate their associated user including their access to projects and their project
+ permissions.
+%p.prepend-top-20
+ Triggers with the
+ %span.label.label-primary legacy
+ label do not have an associated user and only have access to the current project.
+%p.append-bottom-0
+ = succeed '.' do
+ Learn more in the
+ = link_to 'triggers documentation', help_page_path('ci/triggers/README'), target: '_blank'
diff --git a/app/views/projects/triggers/_form.html.haml b/app/views/projects/triggers/_form.html.haml
new file mode 100644
index 00000000000..5f708b3a2ed
--- /dev/null
+++ b/app/views/projects/triggers/_form.html.haml
@@ -0,0 +1,11 @@
+= form_for [@project.namespace.becomes(Namespace), @project, @trigger], html: { class: 'gl-show-field-errors' } do |f|
+ = form_errors(@trigger)
+
+ - if @trigger.token
+ .form-group
+ %label.label-light Token
+ %p.form-control-static= @trigger.token
+ .form-group
+ = f.label :key, "Description", class: "label-light"
+ = f.text_field :description, class: "form-control", required: true, title: 'Trigger description is required.', placeholder: "Trigger description"
+ = f.submit btn_text, class: "btn btn-save"
diff --git a/app/views/projects/triggers/_index.html.haml b/app/views/projects/triggers/_index.html.haml
index 33883facf9b..cc74e50a5e3 100644
--- a/app/views/projects/triggers/_index.html.haml
+++ b/app/views/projects/triggers/_index.html.haml
@@ -1,35 +1,31 @@
-.row.prepend-top-default.append-bottom-default
+.row.prepend-top-default.append-bottom-default.triggers-container
.col-lg-3
- %h4.prepend-top-0
- Triggers
- %p.prepend-top-20
- Triggers can force a specific branch or tag to get rebuilt with an API call.
- %p.append-bottom-0
- = succeed '.' do
- Learn more in the
- = link_to 'triggers documentation', help_page_path('ci/triggers/README'), target: '_blank'
+ = render "projects/triggers/content"
.col-lg-9
.panel.panel-default
.panel-heading
%h4.panel-title
Manage your project's triggers
.panel-body
+ = render "projects/triggers/form", btn_text: "Add trigger"
+ %hr
- if @triggers.any?
- .table-responsive
+ .table-responsive.triggers-list
%table.table
%thead
%th
%strong Token
%th
+ %strong Description
+ %th
+ %strong Owner
+ %th
%strong Last used
%th
= render partial: 'projects/triggers/trigger', collection: @triggers, as: :trigger
- else
%p.settings-message.text-center.append-bottom-default
- No triggers have been created yet. Add one using the button below.
-
- = form_for @trigger, url: url_for(controller: '/projects/triggers', action: 'create') do |f|
- = f.submit "Add trigger", class: 'btn btn-success'
+ No triggers have been created yet. Add one using the form above.
.panel-footer
diff --git a/app/views/projects/triggers/_trigger.html.haml b/app/views/projects/triggers/_trigger.html.haml
index 112b51712ef..ed68e0ed56d 100644
--- a/app/views/projects/triggers/_trigger.html.haml
+++ b/app/views/projects/triggers/_trigger.html.haml
@@ -1,12 +1,42 @@
%tr
%td
- %span.monospace= trigger.token
+ - if can?(current_user, :admin_trigger, trigger)
+ %span= trigger.token
+ = clipboard_button(clipboard_text: trigger.token, title: "Copy trigger token to clipboard")
+ - else
+ %span= trigger.short_token
+
+ .label-container
+ - if trigger.legacy?
+ %span.label.label-primary.has-tooltip{ title: "Trigger makes use of deprecated functionality" } legacy
+ - if !trigger.can_access_project?
+ %span.label.label-danger.has-tooltip{ title: "Trigger user has insufficient permissions to project" } invalid
+
+ %td
+ - if trigger.description? && trigger.description.length > 15
+ %span.has-tooltip{ title: trigger.description }= truncate(trigger.description, length: 15)
+ - else
+ = trigger.description
+
+ %td
+ - if trigger.owner
+ .trigger-owner.sr-only= trigger.owner.name
+ = user_avatar(user: trigger.owner, size: 20)
%td
- - if trigger.last_trigger_request
- #{time_ago_in_words(trigger.last_trigger_request.created_at)} ago
+ - if trigger.last_used
+ #{time_ago_in_words(trigger.last_used)} ago
- else
Never
- %td.text-right
- = link_to 'Revoke', namespace_project_trigger_path(@project.namespace, @project, trigger), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-warning btn-sm"
+ %td.text-right.trigger-actions
+ - take_ownership_confirmation = "By taking ownership you will bind this trigger to your user account. With this the trigger will have access to all your projects as if it was you. Are you sure?"
+ - revoke_trigger_confirmation = "By revoking a trigger you will break any processes making use of it. Are you sure?"
+ - if trigger.owner != current_user && can?(current_user, :manage_trigger, trigger)
+ = link_to 'Take ownership', take_ownership_namespace_project_trigger_path(@project.namespace, @project, trigger), data: { confirm: take_ownership_confirmation }, method: :post, class: "btn btn-default btn-sm btn-trigger-take-ownership"
+ - if can?(current_user, :admin_trigger, trigger)
+ = link_to edit_namespace_project_trigger_path(@project.namespace, @project, trigger), method: :get, title: "Edit", class: "btn btn-default btn-sm" do
+ %i.fa.fa-pencil
+ - if can?(current_user, :manage_trigger, trigger)
+ = link_to namespace_project_trigger_path(@project.namespace, @project, trigger), data: { confirm: revoke_trigger_confirmation }, method: :delete, title: "Revoke", class: "btn btn-default btn-warning btn-sm btn-trigger-revoke" do
+ %i.fa.fa-trash
diff --git a/app/views/projects/triggers/edit.html.haml b/app/views/projects/triggers/edit.html.haml
new file mode 100644
index 00000000000..c35df322b9d
--- /dev/null
+++ b/app/views/projects/triggers/edit.html.haml
@@ -0,0 +1,9 @@
+- page_title "Trigger"
+
+.row.prepend-top-default.append-bottom-default
+ .col-lg-3
+ = render "content"
+ .col-lg-9
+ %h4.prepend-top-0
+ Update trigger
+ = render "form", btn_text: "Save trigger"
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index 22004ecacbc..02133d09cdf 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -11,7 +11,7 @@
.results.prepend-top-10
- if @scope == 'commits'
- %ul.content-list.commit-list.table-list.table-wide
+ %ul.content-list.commit-list
= render partial: "search/results/commit", collection: @search_objects
- else
.search-results
diff --git a/app/views/shared/_personal_access_tokens_form.html.haml b/app/views/shared/_personal_access_tokens_form.html.haml
new file mode 100644
index 00000000000..af4cc90f4a7
--- /dev/null
+++ b/app/views/shared/_personal_access_tokens_form.html.haml
@@ -0,0 +1,39 @@
+- type = impersonation ? "Impersonation" : "Personal Access"
+
+%h5.prepend-top-0
+ Add a #{type} Token
+%p.profile-settings-content
+ Pick a name for the application, and we'll give you a unique #{type} Token.
+
+= form_for token, url: path, method: :post, html: { class: 'js-requires-input' } do |f|
+
+ = form_errors(token)
+
+ .form-group
+ = f.label :name, class: 'label-light'
+ = f.text_field :name, class: "form-control", required: true
+
+ .form-group
+ = f.label :expires_at, class: 'label-light'
+ = f.text_field :expires_at, class: "datepicker form-control"
+
+ .form-group
+ = f.label :scopes, class: 'label-light'
+ = render 'shared/tokens/scopes_form', prefix: 'personal_access_token', token: token, scopes: scopes
+
+ .prepend-top-default
+ = f.submit "Create #{type} Token", class: "btn btn-create"
+
+:javascript
+ var $dateField = $('.datepicker');
+ var date = $dateField.val();
+
+ new Pikaday({
+ field: $dateField.get(0),
+ theme: 'gitlab-theme',
+ format: 'yyyy-mm-dd',
+ minDate: new Date(),
+ onSelect: function(dateText) {
+ $dateField.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
+ }
+ });
diff --git a/app/views/shared/_personal_access_tokens_table.html.haml b/app/views/shared/_personal_access_tokens_table.html.haml
new file mode 100644
index 00000000000..67a49815478
--- /dev/null
+++ b/app/views/shared/_personal_access_tokens_table.html.haml
@@ -0,0 +1,60 @@
+- type = impersonation ? "Impersonation" : "Personal Access"
+%hr
+
+%h5 Active #{type} Tokens (#{active_tokens.length})
+- if impersonation
+ %p.profile-settings-content
+ To see all the user's personal access tokens you must impersonate them first.
+
+- if active_tokens.present?
+ .table-responsive
+ %table.table.active-tokens
+ %thead
+ %tr
+ %th Name
+ %th Created
+ %th Expires
+ %th Scopes
+ - if impersonation
+ %th Token
+ %th
+ %tbody
+ - active_tokens.each do |token|
+ %tr
+ %td= token.name
+ %td= token.created_at.to_date.to_s(:medium)
+ %td
+ - if token.expires?
+ %span{ class: ('text-warning' if token.expires_soon?) }
+ In #{distance_of_time_in_words_to_now(token.expires_at)}
+ - else
+ %span.token-never-expires-label Never
+ %td= token.scopes.present? ? token.scopes.join(", ") : "<no scopes selected>"
+ - if impersonation
+ %td.token-token-container
+ = text_field_tag 'impersonation-token-token', token.token, readonly: true, class: "form-control"
+ = clipboard_button(clipboard_text: token.token)
+ - path = impersonation ? revoke_admin_user_impersonation_token_path(token.user, token) : revoke_profile_personal_access_token_path(token)
+ %td= link_to "Revoke", path, method: :put, class: "btn btn-danger pull-right", data: { confirm: "Are you sure you want to revoke this #{type} Token? This action cannot be undone." }
+- else
+ .settings-message.text-center
+ This user has no active #{type} Tokens.
+
+%hr
+
+%h5 Inactive #{type} Tokens (#{inactive_tokens.length})
+- if inactive_tokens.present?
+ .table-responsive
+ %table.table.inactive-tokens
+ %thead
+ %tr
+ %th Name
+ %th Created
+ %tbody
+ - inactive_tokens.each do |token|
+ %tr
+ %td= token.name
+ %td= token.created_at.to_date.to_s(:medium)
+- else
+ .settings-message.text-center
+ This user has no inactive #{type} Tokens.
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index 62f09cc2dc1..32128f3b3dc 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -11,10 +11,13 @@
class: "check_all_issues left"
.issues-other-filters.filtered-search-container
.filtered-search-input-container
- %input.form-control.filtered-search{ placeholder: 'Search or filter results...', 'data-id' => 'filtered-search', 'data-project-id' => @project.id, 'data-username-params' => @users.to_json(only: [:id, :username]), 'data-base-endpoint' => namespace_project_path(@project.namespace, @project) }
- = icon('filter')
- %button.clear-search.hidden{ type: 'button' }
- = icon('times')
+ .scroll-container
+ %ul.tokens-container.list-unstyled
+ %li.input-token
+ %input.form-control.filtered-search{ placeholder: 'Search or filter results...', 'data-id' => 'filtered-search', 'data-project-id' => @project.id, 'data-username-params' => @users.to_json(only: [:id, :username]), 'data-base-endpoint' => namespace_project_path(@project.namespace, @project) }
+ = icon('filter')
+ %button.clear-search.hidden{ type: 'button' }
+ = icon('times')
#js-dropdown-hint.dropdown-menu.hint-dropdown
%ul{ 'data-dropdown' => true }
%li.filter-dropdown-item{ 'data-action' => 'submit' }
@@ -112,7 +115,7 @@
= hidden_field_tag 'update[issuable_ids]', []
= hidden_field_tag :state_event, params[:state_event]
- .filter-item.inline
+ .filter-item.inline.update-issues-btn
= button_tag "Update #{type.to_s.humanize(capitalize: false)}", class: "btn update_selected_issues btn-save"
:javascript
diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml
index a47085230b8..7a21f19ded4 100644
--- a/app/views/shared/issuable/form/_metadata.html.haml
+++ b/app/views/shared/issuable/form/_metadata.html.haml
@@ -13,10 +13,10 @@
= form.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}"
.col-sm-10{ class: ("col-lg-8" if has_due_date) }
.issuable-form-select-holder
- - if issuable.assignee_id
- = form.hidden_field :assignee_id
+ = form.hidden_field :assignee_id
= dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: issuable.project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee"} })
+ = link_to 'Assign to me', '#', class: "assign-to-me-link #{'hide' if issuable.assignee_id == current_user.id}"
.form-group.issue-milestone
= form.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}"
.col-sm-10{ class: ("col-lg-8" if has_due_date) }
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index 2fff6b0105d..2cd87895c55 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -3,8 +3,8 @@ class PostReceive
include DedicatedSidekiqQueue
def perform(repo_path, identifier, changes)
- if path = Gitlab.config.repositories.storages.find { |p| repo_path.start_with?(p[1].to_s) }
- repo_path.gsub!(path[1].to_s, "")
+ if repository_storage = Gitlab.config.repositories.storages.find { |p| repo_path.start_with?(p[1]['path'].to_s) }
+ repo_path.gsub!(repository_storage[1]['path'].to_s, "")
else
log("Check gitlab.yml config for correct repositories.storages values. No repository storage path matches \"#{repo_path}\"")
end
diff --git a/app/workers/system_hook_push_worker.rb b/app/workers/system_hook_push_worker.rb
new file mode 100644
index 00000000000..e43bbe35de9
--- /dev/null
+++ b/app/workers/system_hook_push_worker.rb
@@ -0,0 +1,8 @@
+class SystemHookPushWorker
+ include Sidekiq::Worker
+ include DedicatedSidekiqQueue
+
+ def perform(push_data, hook_id)
+ SystemHooksService.new.execute_hooks(push_data, hook_id)
+ end
+end
diff --git a/app/workers/update_merge_requests_worker.rb b/app/workers/update_merge_requests_worker.rb
index acc4d858136..89ae17cef37 100644
--- a/app/workers/update_merge_requests_worker.rb
+++ b/app/workers/update_merge_requests_worker.rb
@@ -10,8 +10,5 @@ class UpdateMergeRequestsWorker
return unless user
MergeRequests::RefreshService.new(project, user).execute(oldrev, newrev, ref)
-
- push_data = Gitlab::DataBuilder::Push.build(project, user, oldrev, newrev, ref, [])
- SystemHooksService.new.execute_hooks(push_data, :push_hooks)
end
end
diff --git a/changelogs/unreleased/1381-present-commits-pagination-headers-correctly.yml b/changelogs/unreleased/1381-present-commits-pagination-headers-correctly.yml
new file mode 100644
index 00000000000..1b7e294bd67
--- /dev/null
+++ b/changelogs/unreleased/1381-present-commits-pagination-headers-correctly.yml
@@ -0,0 +1,4 @@
+---
+title: "GET 'projects/:id/repository/commits' endpoint improvements"
+merge_request: 9679
+author: George Andrinopoulos, Jordan Ryan Reuter
diff --git a/changelogs/unreleased/18962-update-issues-button-jumps.yml b/changelogs/unreleased/18962-update-issues-button-jumps.yml
new file mode 100644
index 00000000000..7be136ac4ff
--- /dev/null
+++ b/changelogs/unreleased/18962-update-issues-button-jumps.yml
@@ -0,0 +1,4 @@
+---
+title: Align bulk update issues button to the right
+merge_request:
+author:
diff --git a/changelogs/unreleased/23948-assign-to-me.yml b/changelogs/unreleased/23948-assign-to-me.yml
new file mode 100644
index 00000000000..d73aa92b0e9
--- /dev/null
+++ b/changelogs/unreleased/23948-assign-to-me.yml
@@ -0,0 +1,4 @@
+---
+title: Re-add Assign to me link to Merge Request and Issues
+merge_request:
+author:
diff --git a/changelogs/unreleased/25367-add-impersonation-token.yml b/changelogs/unreleased/25367-add-impersonation-token.yml
new file mode 100644
index 00000000000..4a30f960036
--- /dev/null
+++ b/changelogs/unreleased/25367-add-impersonation-token.yml
@@ -0,0 +1,4 @@
+---
+title: Manage user personal access tokens through api and add impersonation tokens
+merge_request: 9099
+author: Simon Vocella
diff --git a/changelogs/unreleased/26188-tag-creation-404-for-guests.yml b/changelogs/unreleased/26188-tag-creation-404-for-guests.yml
new file mode 100644
index 00000000000..fb00d46ea1f
--- /dev/null
+++ b/changelogs/unreleased/26188-tag-creation-404-for-guests.yml
@@ -0,0 +1,4 @@
+---
+title: Don't show links to tag a commit for users that are not permitted
+merge_request: 8407
+author:
diff --git a/changelogs/unreleased/26371-native-emojis-v3-code.yml b/changelogs/unreleased/26371-native-emojis-v3-code.yml
new file mode 100644
index 00000000000..88346711490
--- /dev/null
+++ b/changelogs/unreleased/26371-native-emojis-v3-code.yml
@@ -0,0 +1,4 @@
+---
+title: Use native unicode emojis
+merge_request:
+author:
diff --git a/changelogs/unreleased/26732-combine-deploy-keys-and-push-rules-and-mirror-repository-and-protect-branches-settings-pages.yml b/changelogs/unreleased/26732-combine-deploy-keys-and-push-rules-and-mirror-repository-and-protect-branches-settings-pages.yml
new file mode 100644
index 00000000000..6fc4615dab8
--- /dev/null
+++ b/changelogs/unreleased/26732-combine-deploy-keys-and-push-rules-and-mirror-repository-and-protect-branches-settings-pages.yml
@@ -0,0 +1,5 @@
+---
+title: Combined deploy keys, push rules, protect branches and mirror repository settings options into a single one called
+ Repository
+merge_request:
+author:
diff --git a/changelogs/unreleased/26790-label-color-todos.yml b/changelogs/unreleased/26790-label-color-todos.yml
new file mode 100644
index 00000000000..74084473d81
--- /dev/null
+++ b/changelogs/unreleased/26790-label-color-todos.yml
@@ -0,0 +1,4 @@
+---
+title: fix background color for labels mention in todo
+merge_request: 9155
+author: mhasbini
diff --git a/changelogs/unreleased/27568-refactor-very-slow-dropdown-asignee-spec.yml b/changelogs/unreleased/27568-refactor-very-slow-dropdown-asignee-spec.yml
new file mode 100644
index 00000000000..5c738af7704
--- /dev/null
+++ b/changelogs/unreleased/27568-refactor-very-slow-dropdown-asignee-spec.yml
@@ -0,0 +1,4 @@
+---
+title: Refactor dropdown_assignee_spec
+merge_request: 9711
+author: George Andrinopoulos
diff --git a/changelogs/unreleased/27936-make-all-uploads-require-revalidation-on-each-browser-fetch.yml b/changelogs/unreleased/27936-make-all-uploads-require-revalidation-on-each-browser-fetch.yml
new file mode 100644
index 00000000000..adc129d8dca
--- /dev/null
+++ b/changelogs/unreleased/27936-make-all-uploads-require-revalidation-on-each-browser-fetch.yml
@@ -0,0 +1,4 @@
+---
+title: Uploaded files which content can change now require revalidation on each page load
+merge_request: 9453
+author:
diff --git a/changelogs/unreleased/28019-make-builds-show-faster.yml b/changelogs/unreleased/28019-make-builds-show-faster.yml
new file mode 100644
index 00000000000..bbfea0e4c88
--- /dev/null
+++ b/changelogs/unreleased/28019-make-builds-show-faster.yml
@@ -0,0 +1,4 @@
+---
+title: Avoid calling Build#trace_with_state for performance
+merge_request: 9149
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/28447-hybrid-repository-storages.yml b/changelogs/unreleased/28447-hybrid-repository-storages.yml
new file mode 100644
index 00000000000..00dfc5781b9
--- /dev/null
+++ b/changelogs/unreleased/28447-hybrid-repository-storages.yml
@@ -0,0 +1,4 @@
+---
+title: Update storage settings to allow extra values per repository storage
+merge_request: 9597
+author:
diff --git a/changelogs/unreleased/28516-default-kubernetes-namespace.yml b/changelogs/unreleased/28516-default-kubernetes-namespace.yml
new file mode 100644
index 00000000000..9fa5c681a53
--- /dev/null
+++ b/changelogs/unreleased/28516-default-kubernetes-namespace.yml
@@ -0,0 +1,4 @@
+---
+title: Make a default namespace of Kubernetes service to contain project ID
+merge_request:
+author:
diff --git a/changelogs/unreleased/28538-restore-nav-shortcuts.yml b/changelogs/unreleased/28538-restore-nav-shortcuts.yml
new file mode 100644
index 00000000000..07b39cd50d1
--- /dev/null
+++ b/changelogs/unreleased/28538-restore-nav-shortcuts.yml
@@ -0,0 +1,4 @@
+---
+title: Restore keyboard shortcuts for "Activity" and "Charts"
+merge_request: 9680
+author:
diff --git a/changelogs/unreleased/28598-narrow-environment-payload-by-using-basic-project.yml b/changelogs/unreleased/28598-narrow-environment-payload-by-using-basic-project.yml
new file mode 100644
index 00000000000..ada726c9048
--- /dev/null
+++ b/changelogs/unreleased/28598-narrow-environment-payload-by-using-basic-project.yml
@@ -0,0 +1,4 @@
+---
+title: Narrow environment payload by using basic project details resource
+merge_request:
+author:
diff --git a/changelogs/unreleased/28609-fix-redirect-to-home-page-url.yml b/changelogs/unreleased/28609-fix-redirect-to-home-page-url.yml
deleted file mode 100644
index baf832d4495..00000000000
--- a/changelogs/unreleased/28609-fix-redirect-to-home-page-url.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix the redirect to custom home page URL
-merge_request: 9518
-author:
diff --git a/changelogs/unreleased/28835-jobs-head.yml b/changelogs/unreleased/28835-jobs-head.yml
new file mode 100644
index 00000000000..1580cfb19ba
--- /dev/null
+++ b/changelogs/unreleased/28835-jobs-head.yml
@@ -0,0 +1,4 @@
+---
+title: Fix jobs table header height
+merge_request:
+author:
diff --git a/changelogs/unreleased/28850-fix-broken-migration.yml b/changelogs/unreleased/28850-fix-broken-migration.yml
deleted file mode 100644
index 7f59a7708bc..00000000000
--- a/changelogs/unreleased/28850-fix-broken-migration.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix broken migration when upgrading straight to 8.17.1
-merge_request: 9613
-author:
diff --git a/changelogs/unreleased/add-changelog-filtered-search-visual-tokens.yml b/changelogs/unreleased/add-changelog-filtered-search-visual-tokens.yml
new file mode 100644
index 00000000000..d10e4cb7c87
--- /dev/null
+++ b/changelogs/unreleased/add-changelog-filtered-search-visual-tokens.yml
@@ -0,0 +1,4 @@
+---
+title: Add filtered search visual tokens
+merge_request: 8969
+author:
diff --git a/changelogs/unreleased/add-git-version-to-system-info.yml b/changelogs/unreleased/add-git-version-to-system-info.yml
new file mode 100644
index 00000000000..2827fcec28d
--- /dev/null
+++ b/changelogs/unreleased/add-git-version-to-system-info.yml
@@ -0,0 +1,4 @@
+---
+title: Add git version to gitlab:env:info
+merge_request: 9128
+author: Semyon Pupkov
diff --git a/changelogs/unreleased/add-pipeline-triggers.yml b/changelogs/unreleased/add-pipeline-triggers.yml
new file mode 100644
index 00000000000..81b11da0bb2
--- /dev/null
+++ b/changelogs/unreleased/add-pipeline-triggers.yml
@@ -0,0 +1,4 @@
+---
+title: Add pipeline trigger API with user permissions
+merge_request: 9277
+author:
diff --git a/changelogs/unreleased/clear-connections-before-starting-sidekiq.yml b/changelogs/unreleased/clear-connections-before-starting-sidekiq.yml
new file mode 100644
index 00000000000..8778fac6e9d
--- /dev/null
+++ b/changelogs/unreleased/clear-connections-before-starting-sidekiq.yml
@@ -0,0 +1,4 @@
+---
+title: Clear ActiveRecord connections before starting Sidekiq
+merge_request:
+author:
diff --git a/changelogs/unreleased/dm-dont-copy-toolip.yml b/changelogs/unreleased/dm-dont-copy-toolip.yml
deleted file mode 100644
index 2b134da66ab..00000000000
--- a/changelogs/unreleased/dm-dont-copy-toolip.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Don't copy tooltip when copying GFM
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-fix-api-create-file-on-empty-repo.yml b/changelogs/unreleased/dm-fix-api-create-file-on-empty-repo.yml
deleted file mode 100644
index 7ac25c0a83e..00000000000
--- a/changelogs/unreleased/dm-fix-api-create-file-on-empty-repo.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix creating a file in an empty repo using the API
-merge_request: 9632
-author:
diff --git a/changelogs/unreleased/dm-fix-cherry-pick.yml b/changelogs/unreleased/dm-fix-cherry-pick.yml
deleted file mode 100644
index e924b821d7e..00000000000
--- a/changelogs/unreleased/dm-fix-cherry-pick.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix cherry-picking or reverting through an MR
-merge_request:
-author:
diff --git a/changelogs/unreleased/dz-nested-groups-restrictions.yml b/changelogs/unreleased/dz-nested-groups-restrictions.yml
new file mode 100644
index 00000000000..2ffb6032525
--- /dev/null
+++ b/changelogs/unreleased/dz-nested-groups-restrictions.yml
@@ -0,0 +1,4 @@
+---
+title: Restrict nested group names to prevent ambiguous routes
+merge_request: 9738
+author:
diff --git a/changelogs/unreleased/feature-openid-connect.yml b/changelogs/unreleased/feature-openid-connect.yml
new file mode 100644
index 00000000000..e84eb7aff86
--- /dev/null
+++ b/changelogs/unreleased/feature-openid-connect.yml
@@ -0,0 +1,4 @@
+---
+title: Implement OpenID Connect identity provider
+merge_request: 8018
+author: Markus Koller
diff --git a/changelogs/unreleased/feature-runner-jobs-v4-api.yml b/changelogs/unreleased/feature-runner-jobs-v4-api.yml
new file mode 100644
index 00000000000..b24ea65266d
--- /dev/null
+++ b/changelogs/unreleased/feature-runner-jobs-v4-api.yml
@@ -0,0 +1,4 @@
+---
+title: Add Runner's jobs v4 API
+merge_request: 9273
+author:
diff --git a/changelogs/unreleased/feature-syshook_commits.yml b/changelogs/unreleased/feature-syshook_commits.yml
new file mode 100644
index 00000000000..1305f5cd414
--- /dev/null
+++ b/changelogs/unreleased/feature-syshook_commits.yml
@@ -0,0 +1,4 @@
+---
+title: Added commit array to Syshook json
+merge_request: 9685
+author: Gabriele Pongelli
diff --git a/changelogs/unreleased/fix-gb-deprecate-ci-config-types.yml b/changelogs/unreleased/fix-gb-deprecate-ci-config-types.yml
new file mode 100644
index 00000000000..605b5f01d0e
--- /dev/null
+++ b/changelogs/unreleased/fix-gb-deprecate-ci-config-types.yml
@@ -0,0 +1,4 @@
+---
+title: Deprecate usage of `types` configuration entry to describe CI/CD stages
+merge_request: 9766
+author:
diff --git a/changelogs/unreleased/issue_16834.yml b/changelogs/unreleased/issue_16834.yml
new file mode 100644
index 00000000000..06175579ac3
--- /dev/null
+++ b/changelogs/unreleased/issue_16834.yml
@@ -0,0 +1,4 @@
+---
+title: Update API endpoints for raw files
+merge_request:
+author:
diff --git a/changelogs/unreleased/priority-to-label-priority.yml b/changelogs/unreleased/priority-to-label-priority.yml
new file mode 100644
index 00000000000..2d9c58bfd9b
--- /dev/null
+++ b/changelogs/unreleased/priority-to-label-priority.yml
@@ -0,0 +1,4 @@
+---
+title: Rename priority sorting option to label priority
+merge_request:
+author:
diff --git a/changelogs/unreleased/remove-readme-option.yml b/changelogs/unreleased/remove-readme-option.yml
new file mode 100644
index 00000000000..1d4c862c00e
--- /dev/null
+++ b/changelogs/unreleased/remove-readme-option.yml
@@ -0,0 +1,4 @@
+---
+title: Remove readme-only project view preference
+merge_request:
+author:
diff --git a/changelogs/unreleased/rfr-20170307-change-default-project-number-limit.yml b/changelogs/unreleased/rfr-20170307-change-default-project-number-limit.yml
new file mode 100644
index 00000000000..e799dd3b48d
--- /dev/null
+++ b/changelogs/unreleased/rfr-20170307-change-default-project-number-limit.yml
@@ -0,0 +1,4 @@
+---
+title: Change project count limit from 10 to 100000
+merge_request:
+author:
diff --git a/changelogs/unreleased/set-default-cache-key-for-jobs.yml b/changelogs/unreleased/set-default-cache-key-for-jobs.yml
new file mode 100644
index 00000000000..b69348d2ece
--- /dev/null
+++ b/changelogs/unreleased/set-default-cache-key-for-jobs.yml
@@ -0,0 +1,4 @@
+---
+title: Set default cache key to "default" for jobs
+merge_request: 9666
+author:
diff --git a/changelogs/unreleased/settings-tab.yml b/changelogs/unreleased/settings-tab.yml
new file mode 100644
index 00000000000..69990c9a917
--- /dev/null
+++ b/changelogs/unreleased/settings-tab.yml
@@ -0,0 +1,4 @@
+---
+title: Moved project settings from the gear drop-down menu to a tab
+merge_request: 9786
+author:
diff --git a/changelogs/unreleased/sort-builds-in-stage-dropdown.yml b/changelogs/unreleased/sort-builds-in-stage-dropdown.yml
new file mode 100644
index 00000000000..646f25125b1
--- /dev/null
+++ b/changelogs/unreleased/sort-builds-in-stage-dropdown.yml
@@ -0,0 +1,4 @@
+---
+title: Sort builds in stage dropdown
+merge_request:
+author:
diff --git a/changelogs/unreleased/tc-api-pipeline-jobs.yml b/changelogs/unreleased/tc-api-pipeline-jobs.yml
new file mode 100644
index 00000000000..993c1b6526a
--- /dev/null
+++ b/changelogs/unreleased/tc-api-pipeline-jobs.yml
@@ -0,0 +1,4 @@
+---
+title: Add GET /projects/:id/pipelines/:pipeline_id/jobs endpoint
+merge_request: 9727
+author:
diff --git a/changelogs/unreleased/use-redis-channel-to-post-runner-notifcations.yml b/changelogs/unreleased/use-redis-channel-to-post-runner-notifcations.yml
new file mode 100644
index 00000000000..ff5a58f6232
--- /dev/null
+++ b/changelogs/unreleased/use-redis-channel-to-post-runner-notifcations.yml
@@ -0,0 +1,4 @@
+---
+title: Use redis channel to post notifications
+merge_request:
+author:
diff --git a/changelogs/unreleased/use-v3-api-on-frontend.yml b/changelogs/unreleased/use-v3-api-on-frontend.yml
deleted file mode 100644
index 467ad3c8276..00000000000
--- a/changelogs/unreleased/use-v3-api-on-frontend.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Make projects dropdown only show projects you are a member of
-merge_request: 9614
-author:
diff --git a/changelogs/unreleased/zj-variables-build-job.yml b/changelogs/unreleased/zj-variables-build-job.yml
new file mode 100644
index 00000000000..1cb0919f824
--- /dev/null
+++ b/changelogs/unreleased/zj-variables-build-job.yml
@@ -0,0 +1,4 @@
+---
+title: Rename job environment variables to new terminology
+merge_request: 9756
+author:
diff --git a/config/application.rb b/config/application.rb
index fce3d5bcd22..cdb93e50e66 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -91,7 +91,6 @@ module Gitlab
# Enable the asset pipeline
config.assets.enabled = true
- config.assets.paths << Gemojione.images_path
config.assets.paths << "vendor/assets/fonts"
config.assets.precompile << "*.png"
config.assets.precompile << "print.css"
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index be34a4000fa..720df0cac2d 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -461,7 +461,8 @@ production: &base
# gitlab-shell invokes Dir.pwd inside the repository path and that results
# real path not the symlink.
storages: # You must have at least a `default` storage path.
- default: /home/git/repositories/
+ default:
+ path: /home/git/repositories/
## Backup settings
backup:
@@ -574,7 +575,8 @@ test:
path: tmp/tests/gitlab-satellites/
repositories:
storages:
- default: tmp/tests/repositories/
+ default:
+ path: tmp/tests/repositories/
backup:
path: tmp/tests/backups
gitlab_shell:
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 933844e4ea6..b45d0e23080 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -83,7 +83,7 @@ class Settings < Settingslogic
def base_url(config)
custom_port = on_standard_port?(config) ? nil : ":#{config.port}"
-
+
[
config.protocol,
"://",
@@ -186,7 +186,7 @@ Settings['issues_tracker'] ||= {}
# GitLab
#
Settings['gitlab'] ||= Settingslogic.new({})
-Settings.gitlab['default_projects_limit'] ||= 10
+Settings.gitlab['default_projects_limit'] ||= 100000
Settings.gitlab['default_branch_protection'] ||= 2
Settings.gitlab['default_can_create_group'] = true if Settings.gitlab['default_can_create_group'].nil?
Settings.gitlab['host'] ||= ENV['GITLAB_HOST'] || 'localhost'
@@ -366,8 +366,13 @@ Settings.gitlab_shell['ssh_path_prefix'] ||= Settings.send(:build_gitlab_shell_s
#
Settings['repositories'] ||= Settingslogic.new({})
Settings.repositories['storages'] ||= {}
-# Setting gitlab_shell.repos_path is DEPRECATED and WILL BE REMOVED in version 9.0
-Settings.repositories.storages['default'] ||= Settings.gitlab_shell['repos_path'] || Settings.gitlab['user_home'] + '/repositories/'
+unless Settings.repositories.storages['default']
+ Settings.repositories.storages['default'] ||= {}
+ # We set the path only if the default storage doesn't exist, in case it exists
+ # but follows the pre-9.0 configuration structure. `6_validations.rb` initializer
+ # will validate all storages and throw a relevant error to the user if necessary.
+ Settings.repositories.storages['default']['path'] ||= Settings.gitlab['user_home'] + '/repositories/'
+end
#
# The repository_downloads_path is used to remove outdated repository
@@ -376,11 +381,11 @@ Settings.repositories.storages['default'] ||= Settings.gitlab_shell['repos_path'
# data-integrity issue. In this case, we sets it to the default
# repository_downloads_path value.
#
-repositories_storages_path = Settings.repositories.storages.values
+repositories_storages = Settings.repositories.storages.values
repository_downloads_path = Settings.gitlab['repository_downloads_path'].to_s.gsub(/\/$/, '')
repository_downloads_full_path = File.expand_path(repository_downloads_path, Settings.gitlab['user_home'])
-if repository_downloads_path.blank? || repositories_storages_path.any? { |path| [repository_downloads_path, repository_downloads_full_path].include?(path.gsub(/\/$/, '')) }
+if repository_downloads_path.blank? || repositories_storages.any? { |rs| [repository_downloads_path, repository_downloads_full_path].include?(rs['path'].gsub(/\/$/, '')) }
Settings.gitlab['repository_downloads_path'] = File.join(Settings.shared['path'], 'cache/archive')
end
diff --git a/config/initializers/6_validations.rb b/config/initializers/6_validations.rb
index d92f64e1647..abe570f430c 100644
--- a/config/initializers/6_validations.rb
+++ b/config/initializers/6_validations.rb
@@ -4,8 +4,8 @@ end
def find_parent_path(name, path)
parent = Pathname.new(path).realpath.parent
- Gitlab.config.repositories.storages.detect do |n, p|
- name != n && Pathname.new(p).realpath == parent
+ Gitlab.config.repositories.storages.detect do |n, rs|
+ name != n && Pathname.new(rs['path']).realpath == parent
end
end
@@ -16,10 +16,22 @@ end
def validate_storages
storage_validation_error('No repository storage path defined') if Gitlab.config.repositories.storages.empty?
- Gitlab.config.repositories.storages.each do |name, path|
+ Gitlab.config.repositories.storages.each do |name, repository_storage|
storage_validation_error("\"#{name}\" is not a valid storage name") unless storage_name_valid?(name)
- parent_name, _parent_path = find_parent_path(name, path)
+ if repository_storage.is_a?(String)
+ error = "#{name} is not a valid storage, because it has no `path` key. " \
+ "It may be configured as:\n\n#{name}:\n path: #{repository_storage}\n\n" \
+ "Refer to gitlab.yml.example for an updated example"
+
+ storage_validation_error(error)
+ end
+
+ if !repository_storage.is_a?(Hash) || repository_storage['path'].nil?
+ storage_validation_error("#{name} is not a valid storage, because it has no `path` key. Refer to gitlab.yml.example for an updated example")
+ end
+
+ parent_name, _parent_path = find_parent_path(name, repository_storage['path'])
if parent_name
storage_validation_error("#{name} is a nested path of #{parent_name}. Nested paths are not supported for repository storages")
end
diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb
index 88cd0f5f652..a5636765774 100644
--- a/config/initializers/doorkeeper.rb
+++ b/config/initializers/doorkeeper.rb
@@ -6,9 +6,14 @@ Doorkeeper.configure do
# This block will be called to check whether the resource owner is authenticated or not.
resource_owner_authenticator do
# Put your resource owner authentication logic here.
- # Ensure user is redirected to redirect_uri after login
- session[:user_return_to] = request.fullpath
- current_user || redirect_to(new_user_session_url)
+ if current_user
+ current_user
+ else
+ # Ensure user is redirected to redirect_uri after login
+ session[:user_return_to] = request.fullpath
+ redirect_to(new_user_session_url)
+ nil
+ end
end
resource_owner_from_credentials do |routes|
diff --git a/config/initializers/doorkeeper_openid_connect.rb b/config/initializers/doorkeeper_openid_connect.rb
new file mode 100644
index 00000000000..700ca25b884
--- /dev/null
+++ b/config/initializers/doorkeeper_openid_connect.rb
@@ -0,0 +1,36 @@
+Doorkeeper::OpenidConnect.configure do
+ issuer Gitlab.config.gitlab.url
+
+ jws_private_key Rails.application.secrets.jws_private_key
+
+ resource_owner_from_access_token do |access_token|
+ User.active.find_by(id: access_token.resource_owner_id)
+ end
+
+ auth_time_from_resource_owner do |user|
+ user.current_sign_in_at
+ end
+
+ reauthenticate_resource_owner do |user, return_to|
+ store_location_for user, return_to
+ sign_out user
+ redirect_to new_user_session_url
+ end
+
+ subject do |user|
+ # hash the user's ID with the Rails secret_key_base to avoid revealing it
+ Digest::SHA256.hexdigest "#{user.id}-#{Rails.application.secrets.secret_key_base}"
+ end
+
+ claims do
+ with_options scope: :openid do |o|
+ o.claim(:name) { |user| user.name }
+ o.claim(:nickname) { |user| user.username }
+ o.claim(:email) { |user| user.public_email }
+ o.claim(:email_verified) { |user| true if user.public_email? }
+ o.claim(:website) { |user| user.full_website_url if user.website_url? }
+ o.claim(:profile) { |user| Rails.application.routes.url_helpers.user_url user }
+ o.claim(:picture) { |user| user.avatar_url }
+ end
+ end
+end
diff --git a/config/initializers/rspec_profiling.rb b/config/initializers/rspec_profiling.rb
index 0ef9f51e5cf..ac353d14499 100644
--- a/config/initializers/rspec_profiling.rb
+++ b/config/initializers/rspec_profiling.rb
@@ -1,22 +1,41 @@
-module RspecProfilingConnection
- def establish_connection
- ::RspecProfiling::Collectors::PSQL::Result.establish_connection(ENV['RSPEC_PROFILING_POSTGRES_URL'])
+module RspecProfilingExt
+ module PSQL
+ def establish_connection
+ ::RspecProfiling::Collectors::PSQL::Result.establish_connection(ENV['RSPEC_PROFILING_POSTGRES_URL'])
+ end
end
-end
-module RspecProfilingGitBranchCi
- def branch
- ENV['CI_BUILD_REF_NAME'] || super
+ module Git
+ def branch
+ ENV['CI_BUILD_REF_NAME'] || super
+ end
+ end
+
+ module Run
+ def example_finished(*args)
+ super
+ rescue => err
+ return if @already_logged_example_finished_error
+
+ $stderr.puts "rspec_profiling couldn't collect an example: #{err}. Further warnings suppressed."
+ @already_logged_example_finished_error = true
+ end
+
+ alias_method :example_passed, :example_finished
+ alias_method :example_failed, :example_finished
end
end
if Rails.env.test?
RspecProfiling.configure do |config|
if ENV['RSPEC_PROFILING_POSTGRES_URL']
- RspecProfiling::Collectors::PSQL.prepend(RspecProfilingConnection)
+ RspecProfiling::Collectors::PSQL.prepend(RspecProfilingExt::PSQL)
config.collector = RspecProfiling::Collectors::PSQL
end
end
- RspecProfiling::VCS::Git.prepend(RspecProfilingGitBranchCi) if ENV.has_key?('CI')
+ if ENV.has_key?('CI')
+ RspecProfiling::VCS::Git.prepend(RspecProfilingExt::Git)
+ RspecProfiling::Run.prepend(RspecProfilingExt::Run)
+ end
end
diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb
index 291fa6c0abc..f9c1d2165d3 100644
--- a/config/initializers/secret_token.rb
+++ b/config/initializers/secret_token.rb
@@ -24,7 +24,8 @@ def create_tokens
defaults = {
secret_key_base: file_secret_key || generate_new_secure_token,
otp_key_base: env_secret_key || file_secret_key || generate_new_secure_token,
- db_key_base: generate_new_secure_token
+ db_key_base: generate_new_secure_token,
+ jws_private_key: generate_new_rsa_private_key
}
missing_secrets = set_missing_keys(defaults)
@@ -41,6 +42,10 @@ def generate_new_secure_token
SecureRandom.hex(64)
end
+def generate_new_rsa_private_key
+ OpenSSL::PKey::RSA.new(2048).to_pem
+end
+
def warn_missing_secret(secret)
warn "Missing Rails.application.secrets.#{secret} for #{Rails.env} environment. The secret will be generated and stored in config/secrets.yml."
end
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index 0c4516b70f0..2b018c68703 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -19,6 +19,12 @@ Sidekiq.configure_server do |config|
chain.add Gitlab::SidekiqStatus::ClientMiddleware
end
+ config.on :startup do
+ # Clear any connections that might have been obtained before starting
+ # Sidekiq (e.g. in an initializer).
+ ActiveRecord::Base.clear_all_connections!
+ end
+
# Sidekiq-cron: load recurring jobs from gitlab.yml
# UGLY Hack to get nested hash from settingslogic
cron_jobs = JSON.parse(Gitlab.config.cron_jobs.to_json)
diff --git a/config/locales/doorkeeper.en.yml b/config/locales/doorkeeper.en.yml
index 1d728282d90..14d49885fb3 100644
--- a/config/locales/doorkeeper.en.yml
+++ b/config/locales/doorkeeper.en.yml
@@ -60,6 +60,7 @@ en:
scopes:
api: Access your API
read_user: Read user information
+ openid: Authenticate using OpenID Connect
flash:
applications:
diff --git a/config/routes.rb b/config/routes.rb
index 06d565df469..1a851da6203 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -22,14 +22,13 @@ Rails.application.routes.draw do
authorizations: 'oauth/authorizations'
end
+ use_doorkeeper_openid_connect
+
# Autocomplete
get '/autocomplete/users' => 'autocomplete#users'
get '/autocomplete/users/:id' => 'autocomplete#user'
get '/autocomplete/projects' => 'autocomplete#projects'
- # Emojis
- resources :emojis, only: :index
-
# Search
get 'search' => 'search#show'
get 'search/autocomplete' => 'search#autocomplete', as: :search_autocomplete
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index 8e99239f350..486ce3c5c87 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -2,6 +2,11 @@ namespace :admin do
resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do
resources :keys, only: [:show, :destroy]
resources :identities, except: [:show]
+ resources :impersonation_tokens, only: [:index, :create] do
+ member do
+ put :revoke
+ end
+ end
member do
get :projects
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 7dc7963ab88..44b8ae7aedd 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -13,7 +13,6 @@ constraints(ProjectUrlConstrainer.new) do
resources :autocomplete_sources, only: [] do
collection do
- get 'emojis'
get 'members'
get 'issues'
get 'merge_requests'
@@ -136,7 +135,11 @@ constraints(ProjectUrlConstrainer.new) do
resources :protected_branches, only: [:index, :show, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
resources :variables, only: [:index, :show, :update, :create, :destroy]
- resources :triggers, only: [:index, :create, :destroy]
+ resources :triggers, only: [:index, :create, :edit, :update, :destroy] do
+ member do
+ post :take_ownership
+ end
+ end
resources :pipelines, only: [:index, :new, :create, :show] do
collection do
@@ -156,6 +159,7 @@ constraints(ProjectUrlConstrainer.new) do
member do
post :stop
get :terminal
+ get :metrics
get '/terminal.ws/authorize', to: 'environments#terminal_websocket_authorize', constraints: { format: nil }
end
@@ -325,6 +329,7 @@ constraints(ProjectUrlConstrainer.new) do
resource :members, only: [:show]
resource :ci_cd, only: [:show], controller: 'ci_cd'
resource :integrations, only: [:show]
+ resource :repository, only: [:show], controller: :repository
end
# Since both wiki and repository routing contains wildcard characters
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 824f99e687e..9d2066a6490 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -52,3 +52,4 @@
- [cronjob, 1]
- [default, 1]
- [pages, 1]
+ - [system_hook_push, 1]
diff --git a/config/webpack.config.js b/config/webpack.config.js
index d9fa70c29fb..7298e7109c6 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -132,7 +132,9 @@ var config = {
extensions: ['.js', '.es6', '.js.es6'],
alias: {
'~': path.join(ROOT_PATH, 'app/assets/javascripts'),
+ 'emoji-map$': path.join(ROOT_PATH, 'fixtures/emojis/digests.json'),
'emoji-aliases$': path.join(ROOT_PATH, 'fixtures/emojis/aliases.json'),
+ 'empty_states': path.join(ROOT_PATH, 'app/views/shared/empty_states'),
'icons': path.join(ROOT_PATH, 'app/views/shared/icons'),
'vendor': path.join(ROOT_PATH, 'vendor/assets/javascripts'),
'vue$': 'vue/dist/vue.common.js',
diff --git a/db/fixtures/development/15_award_emoji.rb b/db/fixtures/development/15_award_emoji.rb
index ea343c26b69..137a036edaf 100644
--- a/db/fixtures/development/15_award_emoji.rb
+++ b/db/fixtures/development/15_award_emoji.rb
@@ -1,7 +1,7 @@
require './spec/support/sidekiq'
Gitlab::Seeder.quiet do
- emoji = Gitlab::AwardEmoji.emojis.keys
+ emoji = Gitlab::Emoji.emojis.keys
Issue.order(Gitlab::Database.random).limit(Issue.count / 2).each do |issue|
project = issue.project
diff --git a/db/migrate/20140502125220_migrate_repo_size.rb b/db/migrate/20140502125220_migrate_repo_size.rb
index e8de7ccf3db..66203486d53 100644
--- a/db/migrate/20140502125220_migrate_repo_size.rb
+++ b/db/migrate/20140502125220_migrate_repo_size.rb
@@ -8,7 +8,7 @@ class MigrateRepoSize < ActiveRecord::Migration
project_data.each do |project|
id = project['id']
namespace_path = project['namespace_path'] || ''
- repos_path = Gitlab.config.gitlab_shell['repos_path'] || Gitlab.config.repositories.storages.default
+ repos_path = Gitlab.config.gitlab_shell['repos_path'] || Gitlab.config.repositories.storages.default['path']
path = File.join(repos_path, namespace_path, project['project_path'] + '.git')
begin
diff --git a/db/migrate/20160615142710_add_index_on_requested_at_to_members.rb b/db/migrate/20160615142710_add_index_on_requested_at_to_members.rb
index 63f7392e54f..7a8ed99c68f 100644
--- a/db/migrate/20160615142710_add_index_on_requested_at_to_members.rb
+++ b/db/migrate/20160615142710_add_index_on_requested_at_to_members.rb
@@ -1,9 +1,15 @@
class AddIndexOnRequestedAtToMembers < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
+ DOWNTIME = false
+
disable_ddl_transaction!
- def change
+ def up
add_concurrent_index :members, :requested_at
end
+
+ def down
+ remove_index :members, :requested_at if index_exists? :members, :requested_at
+ end
end
diff --git a/db/migrate/20160620115026_add_index_on_runners_locked.rb b/db/migrate/20160620115026_add_index_on_runners_locked.rb
index dfa5110dea4..6ca486c63d1 100644
--- a/db/migrate/20160620115026_add_index_on_runners_locked.rb
+++ b/db/migrate/20160620115026_add_index_on_runners_locked.rb
@@ -4,9 +4,15 @@
class AddIndexOnRunnersLocked < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
+ DOWNTIME = false
+
disable_ddl_transaction!
- def change
+ def up
add_concurrent_index :ci_runners, :locked
end
+
+ def down
+ remove_index :ci_runners, :locked if index_exists? :ci_runners, :locked
+ end
end
diff --git a/db/migrate/20160715134306_add_index_for_pipeline_user_id.rb b/db/migrate/20160715134306_add_index_for_pipeline_user_id.rb
index 7c991c6d998..a05a4c679e3 100644
--- a/db/migrate/20160715134306_add_index_for_pipeline_user_id.rb
+++ b/db/migrate/20160715134306_add_index_for_pipeline_user_id.rb
@@ -1,9 +1,15 @@
class AddIndexForPipelineUserId < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
+ DOWNTIME = false
+
disable_ddl_transaction!
- def change
+ def up
add_concurrent_index :ci_commits, :user_id
end
+
+ def down
+ remove_index :ci_commits, :user_id if index_exists? :ci_commits, :user_id
+ end
end
diff --git a/db/migrate/20160805041956_add_deleted_at_to_namespaces.rb b/db/migrate/20160805041956_add_deleted_at_to_namespaces.rb
index a853de3abfb..3f074723b4a 100644
--- a/db/migrate/20160805041956_add_deleted_at_to_namespaces.rb
+++ b/db/migrate/20160805041956_add_deleted_at_to_namespaces.rb
@@ -5,8 +5,15 @@ class AddDeletedAtToNamespaces < ActiveRecord::Migration
disable_ddl_transaction!
- def change
+ def up
add_column :namespaces, :deleted_at, :datetime
+
add_concurrent_index :namespaces, :deleted_at
end
+
+ def down
+ remove_index :namespaces, :deleted_at if index_exists? :namespaces, :deleted_at
+
+ remove_column :namespaces, :deleted_at
+ end
end
diff --git a/db/migrate/20160808085602_add_index_for_build_token.rb b/db/migrate/20160808085602_add_index_for_build_token.rb
index 10ef42afce1..6c5d7268e72 100644
--- a/db/migrate/20160808085602_add_index_for_build_token.rb
+++ b/db/migrate/20160808085602_add_index_for_build_token.rb
@@ -6,7 +6,11 @@ class AddIndexForBuildToken < ActiveRecord::Migration
disable_ddl_transaction!
- def change
+ def up
add_concurrent_index :ci_builds, :token, unique: true
end
+
+ def down
+ remove_index :ci_builds, :token, unique: true if index_exists? :ci_builds, :token, unique: true
+ end
end
diff --git a/db/migrate/20160819221631_add_index_to_note_discussion_id.rb b/db/migrate/20160819221631_add_index_to_note_discussion_id.rb
index b6e8bb18e7b..8f693e97a58 100644
--- a/db/migrate/20160819221631_add_index_to_note_discussion_id.rb
+++ b/db/migrate/20160819221631_add_index_to_note_discussion_id.rb
@@ -8,7 +8,11 @@ class AddIndexToNoteDiscussionId < ActiveRecord::Migration
disable_ddl_transaction!
- def change
+ def up
add_concurrent_index :notes, :discussion_id
end
+
+ def down
+ remove_index :notes, :discussion_id if index_exists? :notes, :discussion_id
+ end
end
diff --git a/db/migrate/20160819232256_add_incoming_email_token_to_users.rb b/db/migrate/20160819232256_add_incoming_email_token_to_users.rb
index f2cf956adc9..bcad3416d04 100644
--- a/db/migrate/20160819232256_add_incoming_email_token_to_users.rb
+++ b/db/migrate/20160819232256_add_incoming_email_token_to_users.rb
@@ -9,8 +9,15 @@ class AddIncomingEmailTokenToUsers < ActiveRecord::Migration
disable_ddl_transaction!
- def change
+ def up
add_column :users, :incoming_email_token, :string
+
add_concurrent_index :users, :incoming_email_token
end
+
+ def down
+ remove_index :users, :incoming_email_token if index_exists? :users, :incoming_email_token
+
+ remove_column :users, :incoming_email_token
+ end
end
diff --git a/db/migrate/20160919145149_add_group_id_to_labels.rb b/db/migrate/20160919145149_add_group_id_to_labels.rb
index d10f3a6d104..828b6afddb1 100644
--- a/db/migrate/20160919145149_add_group_id_to_labels.rb
+++ b/db/migrate/20160919145149_add_group_id_to_labels.rb
@@ -5,9 +5,15 @@ class AddGroupIdToLabels < ActiveRecord::Migration
disable_ddl_transaction!
- def change
+ def up
add_column :labels, :group_id, :integer
add_foreign_key :labels, :namespaces, column: :group_id, on_delete: :cascade # rubocop: disable Migration/AddConcurrentForeignKey
add_concurrent_index :labels, :group_id
end
+
+ def down
+ remove_index :labels, :group_id if index_exists? :labels, :group_id
+ remove_foreign_key :labels, :namespaces, column: :group_id
+ remove_column :labels, :group_id
+ end
end
diff --git a/db/migrate/20160920160832_add_index_to_labels_title.rb b/db/migrate/20160920160832_add_index_to_labels_title.rb
index b5de552b98c..19f7b1076a7 100644
--- a/db/migrate/20160920160832_add_index_to_labels_title.rb
+++ b/db/migrate/20160920160832_add_index_to_labels_title.rb
@@ -5,7 +5,11 @@ class AddIndexToLabelsTitle < ActiveRecord::Migration
disable_ddl_transaction!
- def change
+ def up
add_concurrent_index :labels, :title
end
+
+ def down
+ remove_index :labels, :title if index_exists? :labels, :title
+ end
end
diff --git a/db/migrate/20161020083353_add_pipeline_id_to_merge_request_metrics.rb b/db/migrate/20161020083353_add_pipeline_id_to_merge_request_metrics.rb
index 2abfe47b776..ad3eb4a26f9 100644
--- a/db/migrate/20161020083353_add_pipeline_id_to_merge_request_metrics.rb
+++ b/db/migrate/20161020083353_add_pipeline_id_to_merge_request_metrics.rb
@@ -25,9 +25,15 @@ class AddPipelineIdToMergeRequestMetrics < ActiveRecord::Migration
# comments:
# disable_ddl_transaction!
- def change
+ def up
add_column :merge_request_metrics, :pipeline_id, :integer
- add_concurrent_index :merge_request_metrics, :pipeline_id
add_foreign_key :merge_request_metrics, :ci_commits, column: :pipeline_id, on_delete: :cascade # rubocop: disable Migration/AddConcurrentForeignKey
+ add_concurrent_index :merge_request_metrics, :pipeline_id
+ end
+
+ def down
+ remove_index :merge_request_metrics, :pipeline_id if index_exists? :merge_request_metrics, :pipeline_id
+ remove_foreign_key :merge_request_metrics, :ci_commits, column: :pipeline_id
+ remove_column :merge_request_metrics, :pipeline_id
end
end
diff --git a/db/migrate/20161106185620_add_project_import_data_project_index.rb b/db/migrate/20161106185620_add_project_import_data_project_index.rb
index 750a6a8c51e..94b8ddd46f5 100644
--- a/db/migrate/20161106185620_add_project_import_data_project_index.rb
+++ b/db/migrate/20161106185620_add_project_import_data_project_index.rb
@@ -6,7 +6,11 @@ class AddProjectImportDataProjectIndex < ActiveRecord::Migration
disable_ddl_transaction!
- def change
+ def up
add_concurrent_index :project_import_data, :project_id
end
+
+ def down
+ remove_index :project_import_data, :project_id if index_exists? :project_import_data, :project_id
+ end
end
diff --git a/db/migrate/20161124111395_add_index_to_parent_id.rb b/db/migrate/20161124111395_add_index_to_parent_id.rb
index eab74c01dfd..73f9d92bb22 100644
--- a/db/migrate/20161124111395_add_index_to_parent_id.rb
+++ b/db/migrate/20161124111395_add_index_to_parent_id.rb
@@ -8,7 +8,11 @@ class AddIndexToParentId < ActiveRecord::Migration
disable_ddl_transaction!
- def change
+ def up
add_concurrent_index(:namespaces, [:parent_id, :id], unique: true)
end
+
+ def down
+ remove_index :namespaces, [:parent_id, :id] if index_exists? :namespaces, [:parent_id, :id]
+ end
end
diff --git a/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb b/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb
index 3e1f6b1627d..e5292cfba07 100644
--- a/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb
+++ b/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb
@@ -12,7 +12,7 @@ class MigrateProcessCommitWorkerJobs < ActiveRecord::Migration
end
def repository_storage_path
- Gitlab.config.repositories.storages[repository_storage]
+ Gitlab.config.repositories.storages[repository_storage]['path']
end
def repository_path
diff --git a/db/migrate/20161202152035_add_index_to_routes.rb b/db/migrate/20161202152035_add_index_to_routes.rb
index 4a51337bda6..6d6c8906204 100644
--- a/db/migrate/20161202152035_add_index_to_routes.rb
+++ b/db/migrate/20161202152035_add_index_to_routes.rb
@@ -9,8 +9,13 @@ class AddIndexToRoutes < ActiveRecord::Migration
disable_ddl_transaction!
- def change
+ def up
add_concurrent_index(:routes, :path, unique: true)
add_concurrent_index(:routes, [:source_type, :source_id], unique: true)
end
+
+ def down
+ remove_index(:routes, :path) if index_exists? :routes, :path
+ remove_index(:routes, [:source_type, :source_id]) if index_exists? :routes, [:source_type, :source_id]
+ end
end
diff --git a/db/migrate/20161209153400_add_unique_index_for_environment_slug.rb b/db/migrate/20161209153400_add_unique_index_for_environment_slug.rb
index e9fcef1cd45..d7ef1aa83d9 100644
--- a/db/migrate/20161209153400_add_unique_index_for_environment_slug.rb
+++ b/db/migrate/20161209153400_add_unique_index_for_environment_slug.rb
@@ -9,7 +9,11 @@ class AddUniqueIndexForEnvironmentSlug < ActiveRecord::Migration
disable_ddl_transaction!
- def change
+ def up
add_concurrent_index :environments, [:project_id, :slug], unique: true
end
+
+ def down
+ remove_index :environments, [:project_id, :slug], unique: true if index_exists? :environments, [:project_id, :slug]
+ end
end
diff --git a/db/migrate/20161209165216_create_doorkeeper_openid_connect_tables.rb b/db/migrate/20161209165216_create_doorkeeper_openid_connect_tables.rb
new file mode 100644
index 00000000000..e63d5927f86
--- /dev/null
+++ b/db/migrate/20161209165216_create_doorkeeper_openid_connect_tables.rb
@@ -0,0 +1,37 @@
+class CreateDoorkeeperOpenidConnectTables < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ create_table :oauth_openid_requests do |t|
+ t.integer :access_grant_id, null: false
+ t.string :nonce, null: false
+ end
+
+ if Gitlab::Database.postgresql?
+ # add foreign key without validation to avoid downtime on PostgreSQL,
+ # also see db/post_migrate/20170209140523_validate_foreign_keys_on_oauth_openid_requests.rb
+ execute %q{
+ ALTER TABLE "oauth_openid_requests"
+ ADD CONSTRAINT "fk_oauth_openid_requests_oauth_access_grants_access_grant_id"
+ FOREIGN KEY ("access_grant_id")
+ REFERENCES "oauth_access_grants" ("id")
+ NOT VALID;
+ }
+ else
+ execute %q{
+ ALTER TABLE oauth_openid_requests
+ ADD CONSTRAINT fk_oauth_openid_requests_oauth_access_grants_access_grant_id
+ FOREIGN KEY (access_grant_id)
+ REFERENCES oauth_access_grants (id);
+ }
+ end
+ end
+
+ def down
+ drop_table :oauth_openid_requests
+ end
+end
diff --git a/db/migrate/20161220141214_remove_dot_git_from_group_names.rb b/db/migrate/20161220141214_remove_dot_git_from_group_names.rb
index 241afc6b097..8fb1f9d5e73 100644
--- a/db/migrate/20161220141214_remove_dot_git_from_group_names.rb
+++ b/db/migrate/20161220141214_remove_dot_git_from_group_names.rb
@@ -60,7 +60,7 @@ class RemoveDotGitFromGroupNames < ActiveRecord::Migration
def move_namespace(group_id, path_was, path)
repository_storage_paths = select_all("SELECT distinct(repository_storage) FROM projects WHERE namespace_id = #{group_id}").map do |row|
- Gitlab.config.repositories.storages[row['repository_storage']]
+ Gitlab.config.repositories.storages[row['repository_storage']]['path']
end.compact
# Move the namespace directory in all storages paths used by member projects
diff --git a/db/migrate/20161226122833_remove_dot_git_from_usernames.rb b/db/migrate/20161226122833_remove_dot_git_from_usernames.rb
index a0ce927161f..61dcc8c54f5 100644
--- a/db/migrate/20161226122833_remove_dot_git_from_usernames.rb
+++ b/db/migrate/20161226122833_remove_dot_git_from_usernames.rb
@@ -71,7 +71,7 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration
route_exists = route_exists?(path)
Gitlab.config.repositories.storages.each_value do |storage|
- if route_exists || path_exists?(path, storage)
+ if route_exists || path_exists?(path, storage['path'])
counter += 1
path = "#{base}#{counter}"
@@ -84,7 +84,7 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration
def move_namespace(namespace_id, path_was, path)
repository_storage_paths = select_all("SELECT distinct(repository_storage) FROM projects WHERE namespace_id = #{namespace_id}").map do |row|
- Gitlab.config.repositories.storages[row['repository_storage']]
+ Gitlab.config.repositories.storages[row['repository_storage']]['path']
end.compact
# Move the namespace directory in all storages paths used by member projects
diff --git a/db/migrate/20161228124936_change_expires_at_to_date_in_personal_access_tokens.rb b/db/migrate/20161228124936_change_expires_at_to_date_in_personal_access_tokens.rb
new file mode 100644
index 00000000000..af1bac897cc
--- /dev/null
+++ b/db/migrate/20161228124936_change_expires_at_to_date_in_personal_access_tokens.rb
@@ -0,0 +1,18 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class ChangeExpiresAtToDateInPersonalAccessTokens < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = true
+ DOWNTIME_REASON = 'This migration requires downtime because it alters expires_at column from datetime to date'
+
+ def up
+ change_column :personal_access_tokens, :expires_at, :date
+ end
+
+ def down
+ change_column :personal_access_tokens, :expires_at, :datetime
+ end
+end
diff --git a/db/migrate/20161228135550_add_impersonation_to_personal_access_tokens.rb b/db/migrate/20161228135550_add_impersonation_to_personal_access_tokens.rb
new file mode 100644
index 00000000000..ea9caceaa2c
--- /dev/null
+++ b/db/migrate/20161228135550_add_impersonation_to_personal_access_tokens.rb
@@ -0,0 +1,18 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddImpersonationToPersonalAccessTokens < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ disable_ddl_transaction!
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def up
+ add_column_with_default :personal_access_tokens, :impersonation, :boolean, default: false, allow_null: false
+ end
+
+ def down
+ remove_column :personal_access_tokens, :impersonation
+ end
+end
diff --git a/db/migrate/20170131221752_add_relative_position_to_issues.rb b/db/migrate/20170131221752_add_relative_position_to_issues.rb
new file mode 100644
index 00000000000..1baad0893e3
--- /dev/null
+++ b/db/migrate/20170131221752_add_relative_position_to_issues.rb
@@ -0,0 +1,37 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddRelativePositionToIssues < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ # When a migration requires downtime you **must** uncomment the following
+ # constant and define a short and easy to understand explanation as to why the
+ # migration requires downtime.
+ # DOWNTIME_REASON = ''
+
+ # When using the methods "add_concurrent_index" or "add_column_with_default"
+ # you must disable the use of transactions as these methods can not run in an
+ # existing transaction. When using "add_concurrent_index" make sure that this
+ # method is the _only_ method called in the migration, any other changes
+ # should go in a separate migration. This ensures that upon failure _only_ the
+ # index creation fails and can be retried or reverted easily.
+ #
+ # To disable transactions uncomment the following line and remove these
+ # comments:
+ disable_ddl_transaction!
+
+ def up
+ add_column :issues, :relative_position, :integer
+
+ add_concurrent_index :issues, :relative_position
+ end
+
+ def down
+ remove_column :issues, :relative_position
+
+ remove_index :issues, :relative_position if index_exists? :issues, :relative_position
+ end
+end
diff --git a/db/migrate/20170204181513_add_index_to_labels_for_type_and_project.rb b/db/migrate/20170204181513_add_index_to_labels_for_type_and_project.rb
index 8f944930807..31ef458c44f 100644
--- a/db/migrate/20170204181513_add_index_to_labels_for_type_and_project.rb
+++ b/db/migrate/20170204181513_add_index_to_labels_for_type_and_project.rb
@@ -5,7 +5,11 @@ class AddIndexToLabelsForTypeAndProject < ActiveRecord::Migration
disable_ddl_transaction!
- def change
+ def up
add_concurrent_index :labels, [:type, :project_id]
end
+
+ def down
+ remove_index :labels, [:type, :project_id] if index_exists? :labels, [:type, :project_id]
+ end
end
diff --git a/db/migrate/20170210062829_add_index_to_labels_for_title_and_project.rb b/db/migrate/20170210062829_add_index_to_labels_for_title_and_project.rb
index f922ed209aa..70fb0ef12f9 100644
--- a/db/migrate/20170210062829_add_index_to_labels_for_title_and_project.rb
+++ b/db/migrate/20170210062829_add_index_to_labels_for_title_and_project.rb
@@ -5,8 +5,13 @@ class AddIndexToLabelsForTitleAndProject < ActiveRecord::Migration
disable_ddl_transaction!
- def change
+ def up
add_concurrent_index :labels, :title
add_concurrent_index :labels, :project_id
end
+
+ def down
+ remove_index :labels, :title if index_exists? :labels, :title
+ remove_index :labels, :project_id if index_exists? :labels, :project_id
+ end
end
diff --git a/db/migrate/20170210075922_add_index_to_ci_trigger_requests_for_commit_id.rb b/db/migrate/20170210075922_add_index_to_ci_trigger_requests_for_commit_id.rb
index 61e49c14fc0..07d4f8af27f 100644
--- a/db/migrate/20170210075922_add_index_to_ci_trigger_requests_for_commit_id.rb
+++ b/db/migrate/20170210075922_add_index_to_ci_trigger_requests_for_commit_id.rb
@@ -5,7 +5,11 @@ class AddIndexToCiTriggerRequestsForCommitId < ActiveRecord::Migration
disable_ddl_transaction!
- def change
+ def up
add_concurrent_index :ci_trigger_requests, :commit_id
end
+
+ def down
+ remove_index :ci_trigger_requests, :commit_id if index_exists? :ci_trigger_requests, :commit_id
+ end
end
diff --git a/db/migrate/20170210103609_add_index_to_user_agent_detail.rb b/db/migrate/20170210103609_add_index_to_user_agent_detail.rb
index c01753cfbd2..2d8329b7862 100644
--- a/db/migrate/20170210103609_add_index_to_user_agent_detail.rb
+++ b/db/migrate/20170210103609_add_index_to_user_agent_detail.rb
@@ -8,7 +8,11 @@ class AddIndexToUserAgentDetail < ActiveRecord::Migration
disable_ddl_transaction!
- def change
- add_concurrent_index(:user_agent_details, [:subject_id, :subject_type])
+ def up
+ add_concurrent_index :user_agent_details, [:subject_id, :subject_type]
+ end
+
+ def down
+ remove_index :user_agent_details, [:subject_id, :subject_type] if index_exists? :user_agent_details, [:subject_id, :subject_type]
end
end
diff --git a/db/migrate/20170216135621_add_index_for_latest_successful_pipeline.rb b/db/migrate/20170216135621_add_index_for_latest_successful_pipeline.rb
index 7b1e687977b..65adc90c2c1 100644
--- a/db/migrate/20170216135621_add_index_for_latest_successful_pipeline.rb
+++ b/db/migrate/20170216135621_add_index_for_latest_successful_pipeline.rb
@@ -4,7 +4,11 @@ class AddIndexForLatestSuccessfulPipeline < ActiveRecord::Migration
disable_ddl_transaction!
- def change
- add_concurrent_index(:ci_commits, [:gl_project_id, :ref, :status])
+ def up
+ add_concurrent_index :ci_commits, [:gl_project_id, :ref, :status]
+ end
+
+ def down
+ remove_index :ci_commits, [:gl_project_id, :ref, :status] if index_exists? :ci_commits, [:gl_project_id, :ref, :status]
end
end
diff --git a/db/post_migrate/20170209140523_validate_foreign_keys_on_oauth_openid_requests.rb b/db/post_migrate/20170209140523_validate_foreign_keys_on_oauth_openid_requests.rb
new file mode 100644
index 00000000000..e206f9af636
--- /dev/null
+++ b/db/post_migrate/20170209140523_validate_foreign_keys_on_oauth_openid_requests.rb
@@ -0,0 +1,20 @@
+class ValidateForeignKeysOnOauthOpenidRequests < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ if Gitlab::Database.postgresql?
+ execute %q{
+ ALTER TABLE "oauth_openid_requests"
+ VALIDATE CONSTRAINT "fk_oauth_openid_requests_oauth_access_grants_access_grant_id";
+ }
+ end
+ end
+
+ def down
+ # noop
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 624cf9432d0..3ec5461f600 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -61,6 +61,7 @@ ActiveRecord::Schema.define(version: 20170306170512) do
t.boolean "shared_runners_enabled", default: true, null: false
t.integer "max_artifacts_size", default: 100, null: false
t.string "runners_registration_token"
+ t.integer "max_pages_size", default: 100, null: false
t.boolean "require_two_factor_authentication", default: false
t.integer "two_factor_grace_period", default: 48
t.boolean "metrics_enabled", default: false
@@ -109,7 +110,6 @@ ActiveRecord::Schema.define(version: 20170306170512) do
t.boolean "html_emails_enabled", default: true
t.string "plantuml_url"
t.boolean "plantuml_enabled"
- t.integer "max_pages_size", default: 100, null: false
t.integer "terminal_max_session_time", default: 0, null: false
t.string "default_artifacts_expire_in", default: "0", null: false
t.integer "unique_ips_limit_per_user"
@@ -530,6 +530,7 @@ ActiveRecord::Schema.define(version: 20170306170512) do
t.text "title_html"
t.text "description_html"
t.integer "time_estimate"
+ t.integer "relative_position"
end
add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree
@@ -541,6 +542,7 @@ ActiveRecord::Schema.define(version: 20170306170512) do
add_index "issues", ["due_date"], name: "index_issues_on_due_date", using: :btree
add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree
add_index "issues", ["project_id", "iid"], name: "index_issues_on_project_id_and_iid", unique: true, using: :btree
+ add_index "issues", ["relative_position"], name: "index_issues_on_relative_position", using: :btree
add_index "issues", ["state"], name: "index_issues_on_state", using: :btree
add_index "issues", ["title"], name: "index_issues_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"}
@@ -771,8 +773,8 @@ ActiveRecord::Schema.define(version: 20170306170512) do
t.integer "visibility_level", default: 20, null: false
t.boolean "request_access_enabled", default: false, null: false
t.datetime "deleted_at"
- t.boolean "lfs_enabled"
t.text "description_html"
+ t.boolean "lfs_enabled"
t.integer "parent_id"
end
@@ -878,6 +880,11 @@ ActiveRecord::Schema.define(version: 20170306170512) do
add_index "oauth_applications", ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree
add_index "oauth_applications", ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree
+ create_table "oauth_openid_requests", force: :cascade do |t|
+ t.integer "access_grant_id", null: false
+ t.string "nonce", null: false
+ end
+
create_table "pages_domains", force: :cascade do |t|
t.integer "project_id"
t.text "certificate"
@@ -894,10 +901,11 @@ ActiveRecord::Schema.define(version: 20170306170512) do
t.string "token", null: false
t.string "name", null: false
t.boolean "revoked", default: false
- t.datetime "expires_at"
+ t.date "expires_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "scopes", default: "--- []\n", null: false
+ t.boolean "impersonation", default: false, null: false
end
add_index "personal_access_tokens", ["token"], name: "index_personal_access_tokens_on_token", unique: true, using: :btree
@@ -1374,6 +1382,7 @@ ActiveRecord::Schema.define(version: 20170306170512) do
add_foreign_key "merge_request_metrics", "merge_requests", on_delete: :cascade
add_foreign_key "merge_requests_closing_issues", "issues", on_delete: :cascade
add_foreign_key "merge_requests_closing_issues", "merge_requests", on_delete: :cascade
+ add_foreign_key "oauth_openid_requests", "oauth_access_grants", column: "access_grant_id", name: "fk_oauth_openid_requests_oauth_access_grants_access_grant_id"
add_foreign_key "personal_access_tokens", "users"
add_foreign_key "project_authorizations", "projects", on_delete: :cascade
add_foreign_key "project_authorizations", "users", on_delete: :cascade
diff --git a/doc/administration/auth/crowd.md b/doc/administration/auth/crowd.md
new file mode 100644
index 00000000000..2c289c67a6d
--- /dev/null
+++ b/doc/administration/auth/crowd.md
@@ -0,0 +1,68 @@
+# Atlassian Crowd OmniAuth Provider
+
+## Configure a new Crowd application
+
+1. Choose 'Applications' in the top menu, then 'Add application'.
+1. Go through the 'Add application' steps, entering the appropriate details.
+ The screenshot below shows an example configuration.
+
+ ![Example Crowd application configuration](img/crowd_application.png)
+
+## Configure GitLab
+
+1. On your GitLab server, open the configuration file.
+
+ **Omnibus:**
+
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
+
+ **Source:**
+
+ ```sh
+ cd /home/git/gitlab
+
+ sudo -u git -H editor config/gitlab.yml
+ ```
+
+1. See [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration)
+ for initial settings.
+
+1. Add the provider configuration:
+
+ **Omnibus:**
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "crowd",
+ "args" => {
+ "crowd_server_url" => "CROWD_SERVER_URL",
+ "application_name" => "YOUR_APP_NAME",
+ "application_password" => "YOUR_APP_PASSWORD"
+ }
+ }
+ ]
+ ```
+
+ **Source:**
+
+ ```
+ - { name: 'crowd',
+ args: {
+ crowd_server_url: 'CROWD_SERVER_URL',
+ application_name: 'YOUR_APP_NAME',
+ application_password: 'YOUR_APP_PASSWORD' } }
+ ```
+1. Change `CROWD_SERVER_URL` to the URL of your Crowd server.
+1. Change `YOUR_APP_NAME` to the application name from Crowd applications page.
+1. Change `YOUR_APP_PASSWORD` to the application password you've set.
+1. Save the configuration file.
+1. [Reconfigure][] or [restart][] for the changes to take effect if you
+ installed GitLab via Omnibus or from source respectively.
+
+On the sign in page there should now be a Crowd tab in the sign in form.
+
+[reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart]: ../restart_gitlab.md#installations-from-source
diff --git a/doc/administration/auth/img/crowd_application.png b/doc/administration/auth/img/crowd_application.png
new file mode 100644
index 00000000000..7deea9dac8e
--- /dev/null
+++ b/doc/administration/auth/img/crowd_application.png
Binary files differ
diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md
index 28e413ef447..f707039827b 100644
--- a/doc/administration/container_registry.md
+++ b/doc/administration/container_registry.md
@@ -512,6 +512,62 @@ Currently, there is no storage limitation, which means a user can upload an
infinite amount of Docker images with arbitrary sizes. This setting will be
configurable in future releases.
+## Configure Container Registry notifications
+
+You can configure the Container Registry to send webhook notifications in
+response to events happening within the registry.
+
+Read more about the Container Registry notifications config options in the
+[Docker Registry notifications documentation][notifications-config].
+
+>**Note:**
+Multiple endpoints can be configured for the Container Registry.
+
+
+**Omnibus GitLab installations**
+
+To configure a notification endpoint in Omnibus:
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ registry['notifications'] = [
+ {
+ 'name' => 'test_endpoint',
+ 'url' => 'https://gitlab.example.com/notify',
+ 'timeout' => '500ms',
+ 'threshold' => 5,
+ 'backoff' => '1s',
+ 'headers' => {
+ "Authorization" => ["AUTHORIZATION_EXAMPLE_TOKEN"]
+ }
+ }
+ ]
+ ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+---
+
+**Installations from source**
+
+Configuring the notification endpoint is done in your registry config YML file created
+when you [deployed your docker registry][registry-deploy].
+
+Example:
+
+```
+notifications:
+ endpoints:
+ - name: alistener
+ disabled: false
+ url: https://my.listener.com/event
+ headers: <http.Header>
+ timeout: 500
+ threshold: 5
+ backoff: 1000
+```
+
## Changelog
**GitLab 8.8 ([source docs][8-8-docs])**
@@ -532,3 +588,5 @@ configurable in future releases.
[registry-ssl]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/support/nginx/registry-ssl
[existing-domain]: #configure-container-registry-under-an-existing-gitlab-domain
[new-domain]: #configure-container-registry-under-its-own-domain
+[notifications-config]: https://docs.docker.com/registry/notifications/
+[registry-notifications-config]: https://docs.docker.com/registry/configuration/#notifications \ No newline at end of file
diff --git a/doc/administration/repository_storage_paths.md b/doc/administration/repository_storage_paths.md
index d6aa6101026..55a45119525 100644
--- a/doc/administration/repository_storage_paths.md
+++ b/doc/administration/repository_storage_paths.md
@@ -52,9 +52,12 @@ respectively.
# Paths where repositories can be stored. Give the canonicalized absolute pathname.
# NOTE: REPOS PATHS MUST NOT CONTAIN ANY SYMLINK!!!
storages: # You must have at least a 'default' storage path.
- default: /home/git/repositories
- nfs: /mnt/nfs/repositories
- cephfs: /mnt/cephfs/repositories
+ default:
+ path: /home/git/repositories
+ nfs:
+ path: /mnt/nfs/repositories
+ cephfs:
+ path: /mnt/cephfs/repositories
```
1. [Restart GitLab] for the changes to take effect.
@@ -75,9 +78,9 @@ working, you can remove the `repos_path` line.
```ruby
git_data_dirs({
- "default" => "/var/opt/gitlab/git-data",
- "nfs" => "/mnt/nfs/git-data",
- "cephfs" => "/mnt/cephfs/git-data"
+ "default" => { "path" => "/var/opt/gitlab/git-data" },
+ "nfs" => { "path" => "/mnt/nfs/git-data" },
+ "cephfs" => { "path" => "/mnt/cephfs/git-data" }
})
```
diff --git a/doc/api/README.md b/doc/api/README.md
index 285cd2435ac..58d090b8f5e 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -11,7 +11,6 @@ following locations:
- [Award Emoji](award_emoji.md)
- [Branches](branches.md)
- [Broadcast Messages](broadcast_messages.md)
-- [Builds](builds.md)
- [Build Variables](build_variables.md)
- [Commits](commits.md)
- [Deployments](deployments.md)
@@ -23,6 +22,7 @@ following locations:
- [Group Members](members.md)
- [Issues](issues.md)
- [Issue Boards](boards.md)
+- [Jobs](jobs.md)
- [Keys](keys.md)
- [Labels](labels.md)
- [Merge Requests](merge_requests.md)
@@ -221,6 +221,14 @@ GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=23
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "SUDO: 23" "https://gitlab.example.com/api/v4/projects"
```
+## Impersonation Tokens
+
+Impersonation Tokens are a type of Personal Access Token that can only be created by an admin for a specific user. These can be used by automated tools
+to authenticate with the API as a specific user, as a better alternative to using the user's password or private token directly, which may change over time,
+and to using the [Sudo](#sudo) feature, which requires the tool to know an admin's password or private token, which can change over time as well and are extremely powerful.
+
+For more information about the usage please refer to the [Users](users.md) page
+
## Pagination
Sometimes the returned result will span across many pages. When listing
diff --git a/doc/api/award_emoji.md b/doc/api/award_emoji.md
index 3470f8ce497..f57928d3c93 100644
--- a/doc/api/award_emoji.md
+++ b/doc/api/award_emoji.md
@@ -14,17 +14,17 @@ requests, snippets, and notes/comments. Issues, merge requests, snippets, and no
Gets a list of all award emoji
```
-GET /projects/:id/issues/:issue_id/award_emoji
-GET /projects/:id/merge_requests/:merge_request_id/award_emoji
+GET /projects/:id/issues/:issue_iid/award_emoji
+GET /projects/:id/merge_requests/:merge_request_iid/award_emoji
GET /projects/:id/snippets/:snippet_id/award_emoji
```
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `awardable_id` | integer | yes | The ID of an awardable |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `awardable_id` | integer | yes | The ID (`iid` for merge requests/issues, `id` for snippets) of an awardable |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji
@@ -74,18 +74,18 @@ Example Response:
Gets a single award emoji from an issue, snippet, or merge request.
```
-GET /projects/:id/issues/:issue_id/award_emoji/:award_id
-GET /projects/:id/merge_requests/:merge_request_id/award_emoji/:award_id
+GET /projects/:id/issues/:issue_iid/award_emoji/:award_id
+GET /projects/:id/merge_requests/:merge_request_iid/award_emoji/:award_id
GET /projects/:id/snippets/:snippet_id/award_emoji/:award_id
```
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `awardable_id` | integer | yes | The ID of an awardable |
-| `award_id` | integer | yes | The ID of the award emoji |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `awardable_id` | integer | yes | The ID (`iid` for merge requests/issues, `id` for snippets) of an awardable |
+| `award_id` | integer | yes | The ID of the award emoji |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji/1
@@ -117,18 +117,18 @@ Example Response:
This end point creates an award emoji on the specified resource
```
-POST /projects/:id/issues/:issue_id/award_emoji
-POST /projects/:id/merge_requests/:merge_request_id/award_emoji
+POST /projects/:id/issues/:issue_iid/award_emoji
+POST /projects/:id/merge_requests/:merge_request_iid/award_emoji
POST /projects/:id/snippets/:snippet_id/award_emoji
```
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `awardable_id` | integer | yes | The ID of an awardable |
-| `name` | string | yes | The name of the emoji, without colons |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `awardable_id` | integer | yes | The ID (`iid` for merge requests/issues, `id` for snippets) of an awardable |
+| `name` | string | yes | The name of the emoji, without colons |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji?name=blowfish
@@ -161,18 +161,18 @@ Sometimes its just not meant to be, and you'll have to remove your award. Only a
admins or the author of the award.
```
-DELETE /projects/:id/issues/:issue_id/award_emoji/:award_id
-DELETE /projects/:id/merge_requests/:merge_request_id/award_emoji/:award_id
+DELETE /projects/:id/issues/:issue_iid/award_emoji/:award_id
+DELETE /projects/:id/merge_requests/:merge_request_iid/award_emoji/:award_id
DELETE /projects/:id/snippets/:snippet_id/award_emoji/:award_id
```
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `issue_id` | integer | yes | The ID of an issue |
-| `award_id` | integer | yes | The ID of a award_emoji |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_iid` | integer | yes | The internal ID of an issue |
+| `award_id` | integer | yes | The ID of a award_emoji |
```bash
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji/344
@@ -188,16 +188,16 @@ easily adapted for notes on a Merge Request.
### List a note's award emoji
```
-GET /projects/:id/issues/:issue_id/notes/:note_id/award_emoji
+GET /projects/:id/issues/:issue_iid/notes/:note_id/award_emoji
```
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `issue_id` | integer | yes | The ID of an issue |
-| `note_id` | integer | yes | The ID of an note |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_iid` | integer | yes | The internal ID of an issue |
+| `note_id` | integer | yes | The ID of an note |
```bash
@@ -230,17 +230,17 @@ Example Response:
### Get single note's award emoji
```
-GET /projects/:id/issues/:issue_id/notes/:note_id/award_emoji/:award_id
+GET /projects/:id/issues/:issue_iid/notes/:note_id/award_emoji/:award_id
```
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `issue_id` | integer | yes | The ID of an issue |
-| `note_id` | integer | yes | The ID of a note |
-| `award_id` | integer | yes | The ID of the award emoji |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_iid` | integer | yes | The internal ID of an issue |
+| `note_id` | integer | yes | The ID of a note |
+| `award_id` | integer | yes | The ID of the award emoji |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/notes/1/award_emoji/2
@@ -270,17 +270,17 @@ Example Response:
### Award a new emoji on a note
```
-POST /projects/:id/issues/:issue_id/notes/:note_id/award_emoji
+POST /projects/:id/issues/:issue_iid/notes/:note_id/award_emoji
```
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `issue_id` | integer | yes | The ID of an issue |
-| `note_id` | integer | yes | The ID of a note |
-| `name` | string | yes | The name of the emoji, without colons |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_iid` | integer | yes | The internal ID of an issue |
+| `note_id` | integer | yes | The ID of a note |
+| `name` | string | yes | The name of the emoji, without colons |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/notes/1/award_emoji?name=rocket
@@ -313,17 +313,17 @@ Sometimes its just not meant to be, and you'll have to remove your award. Only a
admins or the author of the award.
```
-DELETE /projects/:id/issues/:issue_id/notes/:note_id/award_emoji/:award_id
+DELETE /projects/:id/issues/:issue_iid/notes/:note_id/award_emoji/:award_id
```
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `issue_id` | integer | yes | The ID of an issue |
-| `note_id` | integer | yes | The ID of a note |
-| `award_id` | integer | yes | The ID of a award_emoji |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_iid` | integer | yes | The internal ID of an issue |
+| `note_id` | integer | yes | The ID of a note |
+| `award_id` | integer | yes | The ID of a award_emoji |
```bash
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji/345
diff --git a/doc/api/builds.md b/doc/api/builds.md
new file mode 100644
index 00000000000..a6edda68bc4
--- /dev/null
+++ b/doc/api/builds.md
@@ -0,0 +1 @@
+This document was moved to [another location](jobs.md).
diff --git a/doc/api/ci/builds.md b/doc/api/ci/builds.md
index b6d79706a84..c8374d94716 100644
--- a/doc/api/ci/builds.md
+++ b/doc/api/ci/builds.md
@@ -5,7 +5,7 @@ API used by runners to receive and update builds.
>**Note:**
This API is intended to be used only by Runners as their own
communication channel. For the consumer API see the
-[Builds API](../builds.md).
+[Jobs API](../jobs.md).
## Authentication
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 4047ff14af2..e25841926f8 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -261,13 +261,13 @@ Example response:
Get a single project issue.
```
-GET /projects/:id/issues/:issue_id
+GET /projects/:id/issues/:issue_iid
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `issue_id`| integer | yes | The ID of a project's issue |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues/41
@@ -385,22 +385,22 @@ Updates an existing project issue. This call is also used to mark an issue as
closed.
```
-PUT /projects/:id/issues/:issue_id
+PUT /projects/:id/issues/:issue_iid
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `issue_id` | integer | yes | The ID of a project's issue |
-| `title` | string | no | The title of an issue |
-| `description` | string | no | The description of an issue |
-| `confidential` | boolean | no | Updates an issue to be confidential |
-| `assignee_id` | integer | no | The ID of a user to assign the issue to |
-| `milestone_id` | integer | no | The ID of a milestone to assign the issue to |
-| `labels` | string | no | Comma-separated label names for an issue |
-| `state_event` | string | no | The state event of an issue. Set `close` to close the issue and `reopen` to reopen it |
-| `updated_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` (requires admin or project owner rights) |
-| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_iid` | integer | yes | The internal ID of a project's issue |
+| `title` | string | no | The title of an issue |
+| `description` | string | no | The description of an issue |
+| `confidential` | boolean | no | Updates an issue to be confidential |
+| `assignee_id` | integer | no | The ID of a user to assign the issue to |
+| `milestone_id` | integer | no | The ID of a milestone to assign the issue to |
+| `labels` | string | no | Comma-separated label names for an issue |
+| `state_event` | string | no | The state event of an issue. Set `close` to close the issue and `reopen` to reopen it |
+| `updated_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` (requires admin or project owner rights) |
+| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` |
```bash
curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues/85?state_event=close
@@ -444,13 +444,13 @@ Example response:
Only for admins and project owners. Soft deletes the issue in question.
```
-DELETE /projects/:id/issues/:issue_id
+DELETE /projects/:id/issues/:issue_iid
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `issue_id` | integer | yes | The ID of a project's issue |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues/85
@@ -466,14 +466,14 @@ If a given label and/or milestone with the same name also exists in the target
project, it will then be assigned to the issue that is being moved.
```
-POST /projects/:id/issues/:issue_id/move
+POST /projects/:id/issues/:issue_iid/move
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `issue_id` | integer | yes | The ID of a project's issue |
-| `to_project_id` | integer | yes | The ID of the new project |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_iid` | integer | yes | The internal ID of a project's issue |
+| `to_project_id` | integer | yes | The ID of the new project |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues/85/move
@@ -522,13 +522,13 @@ If the user is already subscribed to the issue, the status code `304`
is returned.
```
-POST /projects/:id/issues/:issue_id/subscribe
+POST /projects/:id/issues/:issue_iid/subscribe
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `issue_id` | integer | yes | The ID of a project's issue |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/subscribe
@@ -577,13 +577,13 @@ from it. If the user is not subscribed to the issue, the
status code `304` is returned.
```
-POST /projects/:id/issues/:issue_id/unsubscribe
+POST /projects/:id/issues/:issue_iid/unsubscribe
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `issue_id` | integer | yes | The ID of a project's issue |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/unsubscribe
@@ -596,13 +596,13 @@ there already exists a todo for the user on that issue, status code `304` is
returned.
```
-POST /projects/:id/issues/:issue_id/todo
+POST /projects/:id/issues/:issue_iid/todo
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `issue_id` | integer | yes | The ID of a project's issue |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/todo
@@ -687,14 +687,14 @@ Example response:
Sets an estimated time of work for this issue.
```
-POST /projects/:id/issues/:issue_id/time_estimate
+POST /projects/:id/issues/:issue_iid/time_estimate
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `issue_id` | integer | yes | The ID of a project's issue |
-| `duration` | string | yes | The duration in human format. e.g: 3h30m |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_iid` | integer | yes | The internal ID of a project's issue |
+| `duration` | string | yes | The duration in human format. e.g: 3h30m |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/time_estimate?duration=3h30m
@@ -716,13 +716,13 @@ Example response:
Resets the estimated time for this issue to 0 seconds.
```
-POST /projects/:id/issues/:issue_id/reset_time_estimate
+POST /projects/:id/issues/:issue_iid/reset_time_estimate
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `issue_id` | integer | yes | The ID of a project's issue |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/reset_time_estimate
@@ -744,14 +744,14 @@ Example response:
Adds spent time for this issue
```
-POST /projects/:id/issues/:issue_id/add_spent_time
+POST /projects/:id/issues/:issue_iid/add_spent_time
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `issue_id` | integer | yes | The ID of a project's issue |
-| `duration` | string | yes | The duration in human format. e.g: 3h30m |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_iid` | integer | yes | The internal ID of a project's issue |
+| `duration` | string | yes | The duration in human format. e.g: 3h30m |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/add_spent_time?duration=1h
@@ -773,13 +773,13 @@ Example response:
Resets the total spent time for this issue to 0 seconds.
```
-POST /projects/:id/issues/:issue_id/reset_spent_time
+POST /projects/:id/issues/:issue_iid/reset_spent_time
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `issue_id` | integer | yes | The ID of a project's issue |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/reset_spent_time
@@ -799,13 +799,13 @@ Example response:
## Get time tracking stats
```
-GET /projects/:id/issues/:issue_id/time_stats
+GET /projects/:id/issues/:issue_iid/time_stats
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `issue_id` | integer | yes | The ID of a project's issue |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/time_stats
diff --git a/doc/api/jobs.md b/doc/api/jobs.md
index 296f1d025dd..7340123e09d 100644
--- a/doc/api/jobs.md
+++ b/doc/api/jobs.md
@@ -1,6 +1,6 @@
# Jobs API
-## List project jobs
+## List project jobs
Get a list of jobs in a project.
@@ -14,7 +14,123 @@ GET /projects/:id/jobs
| `scope` | string **or** array of strings | no | The scope of jobs to show, one or array of: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`; showing all jobs if none provided |
```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/1/jobs?scope%5B0%5D=pending&scope%5B1%5D=running'
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/1/jobs?scope[]=pending&scope[]=running'
+```
+
+Example of response
+
+```json
+[
+ {
+ "commit": {
+ "author_email": "admin@example.com",
+ "author_name": "Administrator",
+ "created_at": "2015-12-24T16:51:14.000+01:00",
+ "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd",
+ "message": "Test the CI integration.",
+ "short_id": "0ff3ae19",
+ "title": "Test the CI integration."
+ },
+ "coverage": null,
+ "created_at": "2015-12-24T15:51:21.802Z",
+ "artifacts_file": {
+ "filename": "artifacts.zip",
+ "size": 1000
+ },
+ "finished_at": "2015-12-24T17:54:27.895Z",
+ "id": 7,
+ "name": "teaspoon",
+ "pipeline": {
+ "id": 6,
+ "ref": "master",
+ "sha": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd",
+ "status": "pending"
+ },
+ "ref": "master",
+ "runner": null,
+ "stage": "test",
+ "started_at": "2015-12-24T17:54:27.722Z",
+ "status": "failed",
+ "tag": false,
+ "user": {
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "bio": null,
+ "created_at": "2015-12-21T13:14:24.077Z",
+ "id": 1,
+ "is_admin": true,
+ "linkedin": "",
+ "name": "Administrator",
+ "skype": "",
+ "state": "active",
+ "twitter": "",
+ "username": "root",
+ "web_url": "http://gitlab.dev/root",
+ "website_url": ""
+ }
+ },
+ {
+ "commit": {
+ "author_email": "admin@example.com",
+ "author_name": "Administrator",
+ "created_at": "2015-12-24T16:51:14.000+01:00",
+ "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd",
+ "message": "Test the CI integration.",
+ "short_id": "0ff3ae19",
+ "title": "Test the CI integration."
+ },
+ "coverage": null,
+ "created_at": "2015-12-24T15:51:21.727Z",
+ "artifacts_file": null,
+ "finished_at": "2015-12-24T17:54:24.921Z",
+ "id": 6,
+ "name": "spinach:other",
+ "pipeline": {
+ "id": 6,
+ "ref": "master",
+ "sha": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd",
+ "status": "pending"
+ },
+ "ref": "master",
+ "runner": null,
+ "stage": "test",
+ "started_at": "2015-12-24T17:54:24.729Z",
+ "status": "failed",
+ "tag": false,
+ "user": {
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "bio": null,
+ "created_at": "2015-12-21T13:14:24.077Z",
+ "id": 1,
+ "is_admin": true,
+ "linkedin": "",
+ "name": "Administrator",
+ "skype": "",
+ "state": "active",
+ "twitter": "",
+ "username": "root",
+ "web_url": "http://gitlab.dev/root",
+ "website_url": ""
+ }
+ }
+]
+```
+
+## List pipeline jobs
+
+Get a list of jobs for a pipeline.
+
+```
+GET /projects/:id/pipeline/:pipeline_id/jobs
+```
+
+| Attribute | Type | Required | Description |
+|---------------|--------------------------------|----------|----------------------|
+| `id` | integer | yes | The ID of a project |
+| `pipeline_id` | integer | yes | The ID of a pipeline |
+| `scope` | string **or** array of strings | no | The scope of jobs to show, one or array of: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`; showing all jobs if none provided |
+
+```
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/1/pipelines/6/jobs?scope[]=pending&scope[]=running'
```
Example of response
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 09d23cd2ff6..2e0545da1c4 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -82,13 +82,13 @@ Parameters:
Shows information about a single merge request.
```
-GET /projects/:id/merge_requests/:merge_request_id
+GET /projects/:id/merge_requests/:merge_request_iid
```
Parameters:
- `id` (required) - The ID of a project
-- `merge_request_id` (required) - The ID of MR
+- `merge_request_iid` (required) - The internal ID of the merge request
```json
{
@@ -150,13 +150,13 @@ Parameters:
Get a list of merge request commits.
```
-GET /projects/:id/merge_requests/:merge_request_id/commits
+GET /projects/:id/merge_requests/:merge_request_iid/commits
```
Parameters:
- `id` (required) - The ID of a project
-- `merge_request_id` (required) - The ID of MR
+- `merge_request_iid` (required) - The internal ID of the merge request
```json
@@ -187,13 +187,13 @@ Parameters:
Shows information about the merge request including its files and changes.
```
-GET /projects/:id/merge_requests/:merge_request_id/changes
+GET /projects/:id/merge_requests/:merge_request_iid/changes
```
Parameters:
- `id` (required) - The ID of a project
-- `merge_request_id` (required) - The ID of MR
+- `merge_request_iid` (required) - The internal ID of the merge request
```json
{
@@ -269,18 +269,18 @@ Creates a new merge request.
POST /projects/:id/merge_requests
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | string | yes | The ID of a project |
-| `source_branch` | string | yes | The source branch |
-| `target_branch` | string | yes | The target branch |
-| `title` | string | yes | Title of MR |
-| `assignee_id` | integer | no | Assignee user ID |
-| `description` | string | no | Description of MR |
-| `target_project_id` | integer | no | The target project (numeric id) |
-| `labels` | string | no | Labels for MR as a comma-separated list |
-| `milestone_id` | integer | no | The ID of a milestone |
-| `remove_source_branch` | boolean | no | Flag indicating if a merge request should remove the source branch when merging |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | string | yes | The ID of a project |
+| `source_branch` | string | yes | The source branch |
+| `target_branch` | string | yes | The target branch |
+| `title` | string | yes | Title of MR |
+| `assignee_id` | integer | no | Assignee user ID |
+| `description` | string | no | Description of MR |
+| `target_project_id` | integer | no | The target project (numeric id) |
+| `labels` | string | no | Labels for MR as a comma-separated list |
+| `milestone_id` | integer | no | The ID of a milestone |
+| `remove_source_branch` | boolean | no | Flag indicating if a merge request should remove the source branch when merging |
```json
{
@@ -342,21 +342,21 @@ POST /projects/:id/merge_requests
Updates an existing merge request. You can change the target branch, title, or even close the MR.
```
-PUT /projects/:id/merge_requests/:merge_request_id
+PUT /projects/:id/merge_requests/:merge_request_iid
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | string | yes | The ID of a project |
-| `merge_request_id` | integer | yes | The ID of a merge request |
-| `target_branch` | string | no | The target branch |
-| `title` | string | no | Title of MR |
-| `assignee_id` | integer | no | Assignee user ID |
-| `description` | string | no | Description of MR |
-| `state_event` | string | no | New state (close/reopen) |
-| `labels` | string | no | Labels for MR as a comma-separated list |
-| `milestone_id` | integer | no | The ID of a milestone |
-| `remove_source_branch` | boolean | no | Flag indicating if a merge request should remove the source branch when merging |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | string | yes | The ID of a project |
+| `merge_request_iid` | integer | yes | The ID of a merge request |
+| `target_branch` | string | no | The target branch |
+| `title` | string | no | Title of MR |
+| `assignee_id` | integer | no | Assignee user ID |
+| `description` | string | no | Description of MR |
+| `state_event` | string | no | New state (close/reopen) |
+| `labels` | string | no | Labels for MR as a comma-separated list |
+| `milestone_id` | integer | no | The ID of a milestone |
+| `remove_source_branch` | boolean | no | Flag indicating if a merge request should remove the source branch when merging |
Must include at least one non-required attribute from above.
@@ -419,13 +419,13 @@ Must include at least one non-required attribute from above.
Only for admins and project owners. Soft deletes the merge request in question.
```
-DELETE /projects/:id/merge_requests/:merge_request_id
+DELETE /projects/:id/merge_requests/:merge_request_iid
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `merge_request_id` | integer | yes | The ID of a project's merge request |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/merge_requests/85
@@ -445,13 +445,13 @@ If the `sha` parameter is passed and does not match the HEAD of the source - you
If you don't have permissions to accept this merge request - you'll get a `401`
```
-PUT /projects/:id/merge_requests/:merge_request_id/merge
+PUT /projects/:id/merge_requests/:merge_request_iid/merge
```
Parameters:
- `id` (required) - The ID of a project
-- `merge_request_id` (required) - ID of MR
+- `merge_request_iid` (required) - Internal ID of MR
- `merge_commit_message` (optional) - Custom merge commit message
- `should_remove_source_branch` (optional) - if `true` removes the source branch
- `merge_when_pipeline_succeeds` (optional) - if `true` the MR is merged when the pipeline succeeds
@@ -520,12 +520,12 @@ If the merge request is already merged or closed - you get `405` and error messa
In case the merge request is not set to be merged when the pipeline succeeds, you'll also get a `406` error.
```
-PUT /projects/:id/merge_requests/:merge_request_id/cancel_merge_when_pipeline_succeeds
+PUT /projects/:id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_succeeds
```
Parameters:
- `id` (required) - The ID of a project
-- `merge_request_id` (required) - ID of MR
+- `merge_request_iid` (required) - Internal ID of MR
```json
{
@@ -591,13 +591,13 @@ Comments are done via the [notes](notes.md) resource.
Get all the issues that would be closed by merging the provided merge request.
```
-GET /projects/:id/merge_requests/:merge_request_id/closes_issues
+GET /projects/:id/merge_requests/:merge_request_iid/closes_issues
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `merge_request_id` | integer | yes | The ID of the merge request |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/76/merge_requests/1/closes_issues
@@ -666,13 +666,13 @@ Subscribes the authenticated user to a merge request to receive notification. If
status code `304` is returned.
```
-POST /projects/:id/merge_requests/:merge_request_id/subscribe
+POST /projects/:id/merge_requests/:merge_request_iid/subscribe
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `merge_request_id` | integer | yes | The ID of the merge request |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/17/subscribe
@@ -740,13 +740,13 @@ notifications from that merge request. If the user is
not subscribed to the merge request, the status code `304` is returned.
```
-POST /projects/:id/merge_requests/:merge_request_id/unsubscribe
+POST /projects/:id/merge_requests/:merge_request_iid/unsubscribe
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `merge_request_id` | integer | yes | The ID of the merge request |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/17/unsubscribe
@@ -814,13 +814,13 @@ If there already exists a todo for the user on that merge request,
status code `304` is returned.
```
-POST /projects/:id/merge_requests/:merge_request_id/todo
+POST /projects/:id/merge_requests/:merge_request_iid/todo
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `merge_request_id` | integer | yes | The ID of the merge request |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/27/todo
@@ -914,13 +914,13 @@ Example response:
Get a list of merge request diff versions.
```
-GET /projects/:id/merge_requests/:merge_request_id/versions
+GET /projects/:id/merge_requests/:merge_request_iid/versions
```
-| Attribute | Type | Required | Description |
-| --------- | ------- | -------- | --------------------- |
-| `id` | String | yes | The ID of the project |
-| `merge_request_id` | integer | yes | The ID of the merge request |
+| Attribute | Type | Required | Description |
+| --------- | ------- | -------- | --------------------- |
+| `id` | String | yes | The ID of the project |
+| `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/merge_requests/1/versions
@@ -955,14 +955,14 @@ Example response:
Get a single merge request diff version.
```
-GET /projects/:id/merge_requests/:merge_request_id/versions/:version_id
+GET /projects/:id/merge_requests/:merge_request_iid/versions/:version_id
```
-| Attribute | Type | Required | Description |
-| --------- | ------- | -------- | --------------------- |
-| `id` | String | yes | The ID of the project |
-| `merge_request_id` | integer | yes | The ID of the merge request |
-| `version_id` | integer | yes | The ID of the merge request diff version |
+| Attribute | Type | Required | Description |
+| --------- | ------- | -------- | --------------------- |
+| `id` | String | yes | The ID of the project |
+| `merge_request_iid` | integer | yes | The internal ID of the merge request |
+| `version_id` | integer | yes | The ID of the merge request diff version |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/merge_requests/1/versions/1
@@ -1022,14 +1022,14 @@ Example response:
Sets an estimated time of work for this merge request.
```
-POST /projects/:id/merge_requests/:merge_request_id/time_estimate
+POST /projects/:id/merge_requests/:merge_request_iid/time_estimate
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `merge_request_id` | integer | yes | The ID of a project's merge request |
-| `duration` | string | yes | The duration in human format. e.g: 3h30m |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `merge_request_iid` | integer | yes | The internal ID of the merge request |
+| `duration` | string | yes | The duration in human format. e.g: 3h30m |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/93/time_estimate?duration=3h30m
@@ -1051,13 +1051,13 @@ Example response:
Resets the estimated time for this merge request to 0 seconds.
```
-POST /projects/:id/merge_requests/:merge_request_id/reset_time_estimate
+POST /projects/:id/merge_requests/:merge_request_iid/reset_time_estimate
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `merge_request_id` | integer | yes | The ID of a project's merge_request |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `merge_request_iid` | integer | yes | The internal ID of a project's merge_request |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/93/reset_time_estimate
@@ -1079,14 +1079,14 @@ Example response:
Adds spent time for this merge request
```
-POST /projects/:id/merge_requests/:merge_request_id/add_spent_time
+POST /projects/:id/merge_requests/:merge_request_iid/add_spent_time
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `merge_request_id` | integer | yes | The ID of a project's merge request |
-| `duration` | string | yes | The duration in human format. e.g: 3h30m |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `merge_request_iid` | integer | yes | The internal ID of the merge request |
+| `duration` | string | yes | The duration in human format. e.g: 3h30m |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/93/add_spent_time?duration=1h
@@ -1108,13 +1108,13 @@ Example response:
Resets the total spent time for this merge request to 0 seconds.
```
-POST /projects/:id/merge_requests/:merge_request_id/reset_spent_time
+POST /projects/:id/merge_requests/:merge_request_iid/reset_spent_time
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `merge_request_id` | integer | yes | The ID of a project's merge_request |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `merge_request_iid` | integer | yes | The internal ID of a project's merge_request |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/93/reset_spent_time
@@ -1134,13 +1134,13 @@ Example response:
## Get time tracking stats
```
-GET /projects/:id/merge_requests/:merge_request_id/time_stats
+GET /projects/:id/merge_requests/:merge_request_iid/time_stats
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `merge_request_id` | integer | yes | The ID of a project's merge request |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/93/time_stats
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 28e4bfe39dc..686f3dba35d 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -20,7 +20,7 @@ Constants for project visibility levels are next:
## List projects
-Get a list of projects for which the authenticated user is a member.
+Get a list of visible projects for authenticated user. When being accessed without authentication, all public projects are returned.
```
GET /projects
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index ddd11bb2a14..b1bf9ca07cc 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -15,7 +15,7 @@ Parameters:
- `id` (required) - The ID of a project
- `path` (optional) - The path inside repository. Used to get contend of subdirectories
-- `ref_name` (optional) - The name of a repository branch or tag or if not given the default branch
+- `ref` (optional) - The name of a repository branch or tag or if not given the default branch
- `recursive` (optional) - Boolean value used to get a recursive tree (false by default)
```json
@@ -72,10 +72,11 @@ Parameters:
]
```
-## Raw file content
+## Get a blob from repository
-Get the raw file contents for a file by commit SHA and path. This endpoint can
-be accessed without authentication if the repository is publicly accessible.
+Allows you to receive information about blob in repository like size and
+content. Note that blob content is Base64 encoded. This endpoint can be accessed
+without authentication if the repository is publicly accessible.
```
GET /projects/:id/repository/blobs/:sha
@@ -85,7 +86,6 @@ Parameters:
- `id` (required) - The ID of a project
- `sha` (required) - The commit or branch name
-- `filepath` (required) - The path the file
## Raw blob content
@@ -93,7 +93,7 @@ Get the raw file contents for a blob by blob SHA. This endpoint can be accessed
without authentication if the repository is publicly accessible.
```
-GET /projects/:id/repository/raw_blobs/:sha
+GET /projects/:id/repository/blobs/:sha/raw
```
Parameters:
diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md
index ec56d0efa1c..aec91abd390 100644
--- a/doc/api/repository_files.md
+++ b/doc/api/repository_files.md
@@ -11,11 +11,11 @@ content. Note that file content is Base64 encoded. This endpoint can be accessed
without authentication if the repository is publicly accessible.
```
-GET /projects/:id/repository/files
+GET /projects/:id/repository/files/:file_path
```
```bash
-curl --request GET --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files?file_path=app/models/key.rb&ref=master'
+curl --request GET --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fmodels%2Fkey%2Erb?ref=master'
```
Example response:
@@ -36,17 +36,32 @@ Example response:
Parameters:
-- `file_path` (required) - Full path to new file. Ex. lib/class.rb
+- `file_path` (required) - Url encoded full path to new file. Ex. lib%2Fclass%2Erb
+- `ref` (required) - The name of branch, tag or commit
+
+## Get raw file from repository
+
+```
+GET /projects/:id/repository/files/:file_path/raw
+```
+
+```bash
+curl --request GET --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fmodels%2Fkey%2Erb/raw?ref=master'
+```
+
+Parameters:
+
+- `file_path` (required) - Url encoded full path to new file. Ex. lib%2Fclass%2Erb
- `ref` (required) - The name of branch, tag or commit
## Create new file in repository
```
-POST /projects/:id/repository/files
+POST /projects/:id/repository/files/:file_path
```
```bash
-curl --request POST --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files?file_path=app/project.rb&branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20content&commit_message=create%20a%20new%20file'
+curl --request POST --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/app%2Fprojectrb%2E?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20content&commit_message=create%20a%20new%20file'
```
Example response:
@@ -60,7 +75,7 @@ Example response:
Parameters:
-- `file_path` (required) - Full path to new file. Ex. lib/class.rb
+- `file_path` (required) - Url encoded full path to new file. Ex. lib%2Fclass%2Erb
- `branch` (required) - The name of branch
- `encoding` (optional) - Change encoding to 'base64'. Default is text.
- `author_email` (optional) - Specify the commit author's email address
@@ -71,11 +86,11 @@ Parameters:
## Update existing file in repository
```
-PUT /projects/:id/repository/files
+PUT /projects/:id/repository/files/:file_path
```
```bash
-curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files?file_path=app/project.rb&branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20other%20content&commit_message=update%20file'
+curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/app%2Fproject%2Erb?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20other%20content&commit_message=update%20file'
```
Example response:
@@ -89,7 +104,7 @@ Example response:
Parameters:
-- `file_path` (required) - Full path to file. Ex. lib/class.rb
+- `file_path` (required) - Url encoded full path to new file. Ex. lib%2Fclass%2Erb
- `branch` (required) - The name of branch
- `encoding` (optional) - Change encoding to 'base64'. Default is text.
- `author_email` (optional) - Specify the commit author's email address
@@ -109,11 +124,11 @@ Currently gitlab-shell has a boolean return code, preventing GitLab from specify
## Delete existing file in repository
```
-DELETE /projects/:id/repository/files
+DELETE /projects/:id/repository/files/:file_path
```
```bash
-curl --request DELETE --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files?file_path=app/project.rb&branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&commit_message=delete%20file'
+curl --request DELETE --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/app%2Fproject%2Erb?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&commit_message=delete%20file'
```
Example response:
@@ -127,7 +142,7 @@ Example response:
Parameters:
-- `file_path` (required) - Full path to file. Ex. lib/class.rb
+- `file_path` (required) - Url encoded full path to new file. Ex. lib%2Fclass%2Erb
- `branch` (required) - The name of branch
- `author_email` (optional) - Specify the commit author's email address
- `author_name` (optional) - Specify the commit author's name
diff --git a/doc/api/settings.md b/doc/api/settings.md
index 38a37cd920c..ad975e2e325 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -20,7 +20,7 @@ Example response:
```json
{
- "default_projects_limit" : 10,
+ "default_projects_limit" : 100000,
"signup_enabled" : true,
"id" : 1,
"default_branch_protection" : 2,
@@ -60,7 +60,7 @@ PUT /application/settings
| Attribute | Type | Required | Description |
| --------- | ---- | :------: | ----------- |
-| `default_projects_limit` | integer | no | Project limit per user. Default is `10` |
+| `default_projects_limit` | integer | no | Project limit per user. Default is `100000` |
| `signup_enabled` | boolean | no | Enable registration. Default is `true`. |
| `signin_enabled` | boolean | no | Enable login via a GitLab account. Default is `true`. |
| `gravatar_enabled` | boolean | no | Enable Gravatar |
@@ -98,7 +98,7 @@ Example response:
```json
{
"id": 1,
- "default_projects_limit": 10,
+ "default_projects_limit": 100000,
"signup_enabled": true,
"signin_enabled": true,
"gravatar_enabled": true,
diff --git a/doc/api/users.md b/doc/api/users.md
index 95f6bcfccb6..14b5c6c713e 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -827,3 +827,99 @@ Example response:
}
]
```
+
+## Retrieve user impersonation tokens
+
+It retrieves every impersonation token of the user. Note that only administrators can do this.
+This function takes pagination parameters `page` and `per_page` to restrict the list of impersonation tokens.
+
+```
+GET /users/:user_id/impersonation_tokens
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `user_id` | integer | yes | The ID of the user |
+| `state` | string | no | filter tokens based on state (all, active, inactive) |
+
+Example response:
+```json
+[
+ {
+ "id": 1,
+ "name": "mytoken",
+ "revoked": false,
+ "expires_at": "2017-01-04",
+ "scopes": ['api'],
+ "active": true,
+ "impersonation": true,
+ "token": "9koXpg98eAheJpvBs5tK"
+ }
+]
+```
+
+## Show a user's impersonation token
+
+It shows a user's impersonation token. Note that only administrators can do this.
+
+```
+GET /users/:user_id/impersonation_tokens/:impersonation_token_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `user_id` | integer | yes | The ID of the user |
+| `impersonation_token_id` | integer | yes | The ID of the impersonation token |
+
+## Create a impersonation token
+
+It creates a new impersonation token. Note that only administrators can do this.
+You are only able to create impersonation tokens to impersonate the user and perform
+both API calls and Git reads and writes. The user will not see these tokens in his profile
+settings page.
+
+```
+POST /users/:user_id/impersonation_tokens
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `user_id` | integer | yes | The ID of the user |
+| `name` | string | yes | The name of the impersonation token |
+| `expires_at` | date | no | The expiration date of the impersonation token |
+| `scopes` | array | no | The array of scopes of the impersonation token (api, read_user) |
+
+Example response:
+```json
+{
+ "id": 1,
+ "name": "mytoken",
+ "revoked": false,
+ "expires_at": "2017-01-04",
+ "scopes": ['api'],
+ "active": true,
+ "impersonation": true,
+ "token": "9koXpg98eAheJpvBs5tK"
+}
+```
+
+## Revoke an impersonation token
+
+It revokes an impersonation token. Note that only administrators can revoke impersonation tokens.
+
+```
+DELETE /users/:user_id/impersonation_tokens/:impersonation_token_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `user_id` | integer | yes | The ID of the user |
+| `impersonation_token_id` | integer | yes | The ID of the impersonation token |
diff --git a/doc/api/v3_to_v4.md b/doc/api/v3_to_v4.md
index e5ef64fa8dc..0794156bc39 100644
--- a/doc/api/v3_to_v4.md
+++ b/doc/api/v3_to_v4.md
@@ -1,8 +1,10 @@
# V3 to V4 version
-Our V4 API version is currently available as *Beta*! It means that V3
-will still be supported and remain unchanged for now, but be aware that the following
-changes are in V4:
+Since GitLab 9.0, API V4 is the preferred version to be used.
+
+V3 will remain working until at least GitLab 9.3. The V3 API documentation is still [available](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-16-stable/doc/api/README.md).
+
+Below are the changes made between V3 and V4.
### 8.17
@@ -68,3 +70,13 @@ changes are in V4:
- Rename Build Triggers to be Pipeline Triggers API [!9713](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9713)
- `POST /projects/:id/trigger/builds` to `POST /projects/:id/trigger/pipeline`
- Require description when creating a new trigger `POST /projects/:id/triggers`
+- Simplify project payload exposed on Environment endpoints [!9675](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9675)
+- API uses merge request `IID`s (internal ID, as in the web UI) rather than `ID`s. This affects the merge requests, award emoji, todos, and time tracking APIs. [!9530](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9530)
+- API uses issue `IID`s (internal ID, as in the web UI) rather than `ID`s. This affects the issues, award emoji, todos, and time tracking APIs. [!9530](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9530)
+- Change initial page from `0` to `1` on `GET projects/:id/repository/commits` (like on the rest of the API) [!9679] (https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9679)
+- Return correct `Link` header data for `GET projects/:id/repository/commits` [!9679] (https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9679)
+- Update endpoints for repository files [!9637](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9637)
+ - Moved `/projects/:id/repository/files?file_path=:file_path` to `/projects/:id/repository/files/:file_path` (`:file_path` should be URL-encoded)
+ - `/projects/:id/repository/blobs/:sha` now returns JSON attributes for the blob identified by `:sha`, instead of finding the commit identified by `:sha` and returning the raw content of the blob in that commit identified by the required `?filepath=:filepath`
+ - Moved `/projects/:id/repository/commits/:sha/blob?file_path=:file_path` and `/projects/:id/repository/blobs/:sha?file_path=:file_path` to `/projects/:id/repository/files/:file_path/raw?ref=:sha`
+ - `/projects/:id/repository/tree` parameter `ref_name` has been renamed to `ref` for consistency
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index a9e25187b88..4c3e7c4e86e 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -35,17 +35,28 @@ version of Runner required.
| **CI_SERVER_NAME** | all | all | The name of CI server that is used to coordinate jobs |
| **CI_SERVER_VERSION** | all | all | GitLab version that is used to schedule jobs |
| **CI_SERVER_REVISION** | all | all | GitLab revision that is used to schedule jobs |
-| **CI_BUILD_ID** | all | all | The unique id of the current job that GitLab CI uses internally |
-| **CI_BUILD_REF** | all | all | The commit revision for which project is built |
-| **CI_BUILD_TAG** | all | 0.5 | The commit tag name. Present only when building tags. |
-| **CI_BUILD_NAME** | all | 0.5 | The name of the job as defined in `.gitlab-ci.yml` |
-| **CI_BUILD_STAGE** | all | 0.5 | The name of the stage as defined in `.gitlab-ci.yml` |
-| **CI_BUILD_REF_NAME** | all | all | The branch or tag name for which project is built |
-| **CI_BUILD_REF_SLUG** | 8.15 | all | `$CI_BUILD_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. |
-| **CI_BUILD_REPO** | all | all | The URL to clone the Git repository |
-| **CI_BUILD_TRIGGERED** | all | 0.5 | The flag to indicate that job was [triggered] |
-| **CI_BUILD_MANUAL** | 8.12 | all | The flag to indicate that job was manually started |
-| **CI_BUILD_TOKEN** | all | 1.2 | Token used for authenticating with the GitLab Container Registry |
+| **CI_BUILD_ID** | all | all | The unique id of the current job that GitLab CI uses internally. Deprecated, use CI_JOB_ID |
+| **CI_JOB_ID** | 9.0 | all | The unique id of the current job that GitLab CI uses internally |
+| **CI_BUILD_REF** | all | all | The commit revision for which project is built. Deprecated, use CI_COMMIT_REF |
+| **CI_COMMIT_SHA** | 9.0 | all | The commit revision for which project is built |
+| **CI_BUILD_TAG** | all | 0.5 | The commit tag name. Present only when building tags. Deprecated, use CI_COMMIT_TAG |
+| **CI_COMMIT_TAG** | 9.0 | 0.5 | The commit tag name. Present only when building tags. |
+| **CI_BUILD_NAME** | all | 0.5 | The name of the job as defined in `.gitlab-ci.yml`. Deprecated, use CI_JOB_NAME |
+| **CI_JOB_NAME** | 9.0 | 0.5 | The name of the job as defined in `.gitlab-ci.yml` |
+| **CI_BUILD_STAGE** | all | 0.5 | The name of the stage as defined in `.gitlab-ci.yml`. Deprecated, use CI_JOB_STAGE |
+| **CI_JOB_STAGE** | 9.0 | 0.5 | The name of the stage as defined in `.gitlab-ci.yml` |
+| **CI_BUILD_REF_NAME** | all | all | The branch or tag name for which project is built. Deprecated, use CI_COMMIT_REF_NAME |
+| **CI_COMMIT_REF_NAME** | 9.0 | all | The branch or tag name for which project is built |
+| **CI_BUILD_REF_SLUG** | 8.15 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. Deprecated, use CI_COMMIT_REF_SLUG |
+| **CI_COMMIT_REF_SLUG** | 9.0 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. |
+| **CI_BUILD_REPO** | all | all | The URL to clone the Git repository. Deprecated, use CI_REPOSITORY |
+| **CI_REPOSITORY_URL** | 9.0 | all | The URL to clone the Git repository |
+| **CI_BUILD_TRIGGERED** | all | 0.5 | The flag to indicate that job was [triggered]. Deprecated, use CI_PIPELINE_TRIGGERED |
+| **CI_PIPELINE_TRIGGERED** | all | all | The flag to indicate that job was [triggered] |
+| **CI_BUILD_MANUAL** | 8.12 | all | The flag to indicate that job was manually started. Deprecated, use CI_JOB_MANUAL |
+| **CI_JOB_MANUAL** | 8.12 | all | The flag to indicate that job was manually started |
+| **CI_BUILD_TOKEN** | all | 1.2 | Token used for authenticating with the GitLab Container Registry. Deprecated, use CI_JOB_TOKEN |
+| **CI_JOB_TOKEN** | 9.0 | 1.2 | Token used for authenticating with the GitLab Container Registry |
| **CI_PIPELINE_ID** | 8.10 | 0.5 | The unique id of the current pipeline that GitLab CI uses internally |
| **CI_PROJECT_ID** | all | all | The unique id of the current project that GitLab CI uses internally |
| **CI_PROJECT_NAME** | 8.10 | 0.5 | The project name that is currently being built |
@@ -66,21 +77,22 @@ version of Runner required.
| **RESTORE_CACHE_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to restore the cache running a job |
| **GITLAB_USER_ID** | 8.12 | all | The id of the user who started the job |
| **GITLAB_USER_EMAIL** | 8.12 | all | The email of the user who started the job |
-
+| **CI_REGISTRY_USER** | 9.0 | all | The username to use to push containers to the GitLab Container Registry |
+| **CI_REGISTRY_PASSWORD** | 9.0 | all | The password to use to push containers to the GitLab Container Registry |
Example values:
```bash
-export CI_BUILD_ID="50"
-export CI_BUILD_REF="1ecfd275763eff1d6b4844ea3168962458c9f27a"
-export CI_BUILD_REF_NAME="master"
-export CI_BUILD_REPO="https://gitab-ci-token:abcde-1234ABCD5678ef@example.com/gitlab-org/gitlab-ce.git"
-export CI_BUILD_TAG="1.0.0"
-export CI_BUILD_NAME="spec:other"
-export CI_BUILD_STAGE="test"
-export CI_BUILD_MANUAL="true"
-export CI_BUILD_TRIGGERED="true"
-export CI_BUILD_TOKEN="abcde-1234ABCD5678ef"
+export CI_JOB_ID="50"
+export CI_COMMIT_SHA="1ecfd275763eff1d6b4844ea3168962458c9f27a"
+export CI_COMMIT_REF_NAME="master"
+export CI_REPOSITORY="https://gitab-ci-token:abcde-1234ABCD5678ef@example.com/gitlab-org/gitlab-ce.git"
+export CI_COMMIT_TAG="1.0.0"
+export CI_JOB_NAME="spec:other"
+export CI_JOB_STAGE="test"
+export CI_JOB_MANUAL="true"
+export CI_JOB_TRIGGERED="true"
+export CI_JOB_TOKEN="abcde-1234ABCD5678ef"
export CI_PIPELINE_ID="1000"
export CI_PROJECT_ID="34"
export CI_PROJECT_DIR="/builds/gitlab-org/gitlab-ce"
@@ -99,8 +111,30 @@ export CI_SERVER_REVISION="70606bf"
export CI_SERVER_VERSION="8.9.0"
export GITLAB_USER_ID="42"
export GITLAB_USER_EMAIL="user@example.com"
+export CI_REGISTRY_USER="gitlab-ci-token"
+export CI_REGISTRY_PASSWORD="longalfanumstring"
```
+## 9.0 Renaming
+
+To follow conventions of naming across GitLab, and to futher move away from the
+`build` term and toward `job` CI variables have been renamed for the 9.0
+release.
+
+| 8.X name | 9.0 name |
+|----------|----------|
+| CI_BUILD_ID | CI_JOB_ID |
+| CI_BUILD_REF | CI_COMMIT_SHA |
+| CI_BUILD_TAG | CI_COMMIT_TAG |
+| CI_BUILD_REF_NAME | CI_COMMIT_REF_NAME |
+| CI_BUILD_REF_SLUG | CI_COMMIT_REF_SLUG |
+| CI_BUILD_NAME | CI_JOB_NAME |
+| CI_BUILD_STAGE | CI_JOB_STAGE |
+| CI_BUILD_REPO | CI_REPOSITORY |
+| CI_BUILD_TRIGGERED | CI_PIPELINE_TRIGGERED |
+| CI_BUILD_MANUAL | CI_JOB_MANUAL |
+| CI_BUILD_TOKEN | CI_JOB_TOKEN |
+
## `.gitlab-ci.yaml` defined variables
>**Note:**
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index fd1171eff7e..49fa8761e5e 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -70,7 +70,7 @@ There are a few reserved `keywords` that **cannot** be used as job names:
| image | no | Use docker image, covered in [Use Docker](../docker/README.md) |
| services | no | Use docker services, covered in [Use Docker](../docker/README.md) |
| stages | no | Define build stages |
-| types | no | Alias for `stages` |
+| types | no | Alias for `stages` (deprecated) |
| before_script | no | Define commands that run before each job's script |
| after_script | no | Define commands that run after each job's script |
| variables | no | Define build variables |
@@ -130,6 +130,8 @@ There are also two edge cases worth mentioning:
### types
+> Deprecated, and will be removed in 10.0. Use [stages](#stages) instead.
+
Alias for [stages](#stages).
### variables
@@ -166,10 +168,11 @@ which can be set in GitLab's UI.
cached between jobs. You can only use paths that are within the project
workspace.
-**By default the caching is enabled per-job and per-branch.**
+**By default caching is enabled and shared between pipelines and jobs,
+starting from GitLab 9.0**
-If `cache` is defined outside the scope of the jobs, it means it is set
-globally and all jobs will use its definition.
+If `cache` is defined outside the scope of jobs, it means it is set
+globally and all jobs will use that definition.
Cache all files in `binaries` and `.config`:
@@ -202,7 +205,7 @@ rspec:
- binaries/
```
-Locally defined cache overwrites globally defined options. The following `rspec`
+Locally defined cache overrides globally defined options. The following `rspec`
job will cache only `binaries/`:
```yaml
@@ -213,10 +216,15 @@ cache:
rspec:
script: test
cache:
+ key: rspec
paths:
- binaries/
```
+Note that since cache is shared between jobs, if you're using different
+paths for different jobs, you should also set a different **cache:key**
+otherwise cache content can be overwritten.
+
The cache is provided on a best-effort basis, so don't expect that the cache
will be always present. For implementation details, please check GitLab Runner.
@@ -233,6 +241,9 @@ different jobs or even different branches.
The `cache:key` variable can use any of the [predefined variables](../variables/README.md).
+The default key is **default** across the project, therefore everything is
+shared between each pipelines and jobs by default, starting from GitLab 9.0.
+
---
**Example configurations**
diff --git a/doc/integration/README.md b/doc/integration/README.md
index 22bdf33443d..e56e58498a6 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -12,6 +12,7 @@ See the documentation below for details on how to configure these services.
- [SAML](saml.md) Configure GitLab as a SAML 2.0 Service Provider
- [CAS](cas.md) Configure GitLab to sign in using CAS
- [OAuth2 provider](oauth_provider.md) OAuth2 application creation
+- [OpenID Connect](openid_connect_provider.md) Use GitLab as an identity provider
- [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages
- [reCAPTCHA](recaptcha.md) Configure GitLab to use Google reCAPTCHA for new users
- [Akismet](akismet.md) Configure Akismet to stop spam
diff --git a/doc/integration/crowd.md b/doc/integration/crowd.md
index f8370cd349e..2bc526dc3db 100644
--- a/doc/integration/crowd.md
+++ b/doc/integration/crowd.md
@@ -1,63 +1 @@
-# Crowd OmniAuth Provider
-
-To enable the Crowd OmniAuth provider you must register your application with Crowd. To configure Crowd integration you need an application name and password.
-
-1. On your GitLab server, open the configuration file.
-
- For omnibus package:
-
- ```sh
- sudo editor /etc/gitlab/gitlab.rb
- ```
-
- For installations from source:
-
- ```sh
- cd /home/git/gitlab
-
- sudo -u git -H editor config/gitlab.yml
- ```
-
-1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
-
-1. Add the provider configuration:
-
- For omnibus package:
-
- ```ruby
- gitlab_rails['omniauth_providers'] = [
- {
- "name" => "crowd",
- "args" => {
- "crowd_server_url" => "CROWD",
- "application_name" => "YOUR_APP_NAME",
- "application_password" => "YOUR_APP_PASSWORD"
- }
- }
- ]
- ```
-
- For installations from source:
-
- ```
- - { name: 'crowd',
- args: {
- crowd_server_url: 'CROWD SERVER URL',
- application_name: 'YOUR_APP_NAME',
- application_password: 'YOUR_APP_PASSWORD' } }
- ```
-
-1. Change 'YOUR_APP_NAME' to the application name from Crowd applications page.
-
-1. Change 'YOUR_APP_PASSWORD' to the application password you've set.
-
-1. Save the configuration file.
-
-1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
- installed GitLab via Omnibus or from source respectively.
-
-On the sign in page there should now be a Crowd tab in the sign in form.
-
-[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
-[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
-
+This document was moved to [`administration/auth/crowd`](../administration/auth/crowd.md).
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index 47e20d7566a..6c11f46a70a 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -27,7 +27,7 @@ contains some settings that are common for all providers.
- [Twitter](twitter.md)
- [Shibboleth](shibboleth.md)
- [SAML](saml.md)
-- [Crowd](crowd.md)
+- [Crowd](../administration/auth/crowd.md)
- [Azure](azure.md)
- [Auth0](auth0.md)
- [Authentiq](../administration/auth/authentiq.md)
diff --git a/doc/integration/openid_connect_provider.md b/doc/integration/openid_connect_provider.md
new file mode 100644
index 00000000000..56f367d841e
--- /dev/null
+++ b/doc/integration/openid_connect_provider.md
@@ -0,0 +1,47 @@
+# GitLab as OpenID Connect identity provider
+
+This document is about using GitLab as an OpenID Connect identity provider
+to sign in to other services.
+
+## Introduction to OpenID Connect
+
+[OpenID Connect] \(OIC) is a simple identity layer on top of the
+OAuth 2.0 protocol. It allows clients to verify the identity of the end-user
+based on the authentication performed by GitLab, as well as to obtain
+basic profile information about the end-user in an interoperable and
+REST-like manner. OIC performs many of the same tasks as OpenID 2.0,
+but does so in a way that is API-friendly, and usable by native and
+mobile applications.
+
+On the client side, you can use [omniauth-openid-connect] for Rails
+applications, or any of the other available [client implementations].
+
+GitLab's implementation uses the [doorkeeper-openid_connect] gem, refer
+to its README for more details about which parts of the specifications
+are supported.
+
+## Enabling OpenID Connect for OAuth applications
+
+Refer to the [OAuth guide] for basic information on how to set up OAuth
+applications in GitLab. To enable OIC for an application, all you have to do
+is select the `openid` scope in the application settings.
+
+Currently the following user information is shared with clients:
+
+| Claim | Type | Description |
+|:-----------------|:----------|:------------|
+| `sub` | `string` | An opaque token that uniquely identifies the user
+| `auth_time` | `integer` | The timestamp for the user's last authentication
+| `name` | `string` | The user's full name
+| `nickname` | `string` | The user's GitLab username
+| `email` | `string` | The user's public email address
+| `email_verified` | `boolean` | Whether the user's public email address was verified
+| `website` | `string` | URL for the user's website
+| `profile` | `string` | URL for the user's GitLab profile
+| `picture` | `string` | URL for the user's GitLab avatar
+
+[OpenID Connect]: http://openid.net/connect/ "OpenID Connect website"
+[doorkeeper-openid_connect]: https://github.com/doorkeeper-gem/doorkeeper-openid_connect "Doorkeeper::OpenidConnect website"
+[OAuth guide]: oauth_provider.md "GitLab as OAuth2 authentication service provider"
+[omniauth-openid-connect]: https://github.com/jjbohn/omniauth-openid-connect/ "OmniAuth::OpenIDConnect website"
+[client implementations]: http://openid.net/developers/libraries#connect "List of available client implementations"
diff --git a/doc/update/8.17-to-9.0.md b/doc/update/8.17-to-9.0.md
index 7b934ecd87a..4cc8be752c4 100644
--- a/doc/update/8.17-to-9.0.md
+++ b/doc/update/8.17-to-9.0.md
@@ -1,3 +1,66 @@
+#### Configuration changes for repository storages
+
+This version introduces a new configuration structure for repository storages.
+Update your current configuration as follows, replacing with your storages names and paths:
+
+**For installations from source**
+
+1. Update your `gitlab.yml`, from
+
+ ```yaml
+ repositories:
+ storages: # You must have at least a 'default' storage path.
+ default: /home/git/repositories
+ nfs: /mnt/nfs/repositories
+ cephfs: /mnt/cephfs/repositories
+ ```
+
+ to
+
+ ```yaml
+ repositories:
+ storages: # You must have at least a 'default' storage path.
+ default:
+ path: /home/git/repositories
+ nfs:
+ path: /mnt/nfs/repositories
+ cephfs:
+ path: /mnt/cephfs/repositories
+ ```
+
+**For Omnibus installations**
+
+1. Upate your `/etc/gitlab/gitlab.rb`, from
+
+ ```ruby
+ git_data_dirs({
+ "default" => "/var/opt/gitlab/git-data",
+ "nfs" => "/mnt/nfs/git-data",
+ "cephfs" => "/mnt/cephfs/git-data"
+ })
+ ```
+
+ to
+
+ ```ruby
+ git_data_dirs({
+ "default" => { "path" => "/var/opt/gitlab/git-data" },
+ "nfs" => { "path" => "/mnt/nfs/git-data" },
+ "cephfs" => { "path" => "/mnt/cephfs/git-data" }
+ })
+ ```
+
+#### Git configuration
+
+Configure Git to generate packfile bitmaps (introduced in Git 2.0) on
+the GitLab server during `git gc`.
+
+```sh
+cd /home/git/gitlab
+
+sudo -u git -H git config --global repack.writeBitmaps true
+```
+
#### Nginx configuration
Ensure you're still up-to-date with the latest NGINX configuration changes:
@@ -12,7 +75,7 @@ git diff origin/8-17-stable:lib/support/nginx/gitlab-ssl origin/9-0-stable:lib/s
git diff origin/8-17-stable:lib/support/nginx/gitlab origin/9-0-stable:lib/support/nginx/gitlab
```
-If you are using Strict-Transport-Security in your installation to continue using it you must enable it in your Nginx
+If you are using Strict-Transport-Security in your installation to continue using it you must enable it in your Nginx
configuration as GitLab application no longer handles setting it.
If you are using Apache instead of NGINX please see the updated [Apache templates].
diff --git a/doc/workflow/shortcuts.md b/doc/workflow/shortcuts.md
index 65e67aa1512..7aa9b46081a 100644
--- a/doc/workflow/shortcuts.md
+++ b/doc/workflow/shortcuts.md
@@ -42,10 +42,12 @@ You can see GitLab's keyboard shortcuts by using 'shift + ?'
| Keyboard Shortcut | Description |
| ----------------- | ----------- |
| <kbd>g</kbd> + <kbd>p</kbd> | Go to the project's home page |
+| <kbd>g</kbd> + <kbd>e</kbd> | Go to the project's activity feed |
| <kbd>g</kbd> + <kbd>f</kbd> | Go to files |
| <kbd>g</kbd> + <kbd>c</kbd> | Go to commits |
| <kbd>g</kbd> + <kbd>b</kbd> | Go to jobs |
| <kbd>g</kbd> + <kbd>n</kbd> | Go to network graph |
+| <kbd>g</kbd> + <kbd>g</kbd> | Go to repository charts |
| <kbd>g</kbd> + <kbd>i</kbd> | Go to issues |
| <kbd>g</kbd> + <kbd>m</kbd> | Go to merge requests |
| <kbd>g</kbd> + <kbd>s</kbd> | Go to snippets |
diff --git a/features/project/active_tab.feature b/features/project/active_tab.feature
index 1dd2bdd9b36..0d6f7350181 100644
--- a/features/project/active_tab.feature
+++ b/features/project/active_tab.feature
@@ -7,8 +7,9 @@ Feature: Project Active Tab
Scenario: On Project Home
Given I visit my project's home page
- Then the active main tab should be Home
- And no other main tabs should be active
+ Then the active sub tab should be Home
+ And no other sub tabs should be active
+ And the active main tab should be Project
Scenario: On Project Repository
Given I visit my project's files page
@@ -34,36 +35,45 @@ Feature: Project Active Tab
Scenario: On Project Home/Show
Given I visit my project's home page
- Then the active main tab should be Home
+ Then the active sub tab should be Home
+ And no other sub tabs should be active
+ And the active main tab should be Project
And no other main tabs should be active
+ Scenario: On Project Home/Activity
+ Given I visit my project's home page
+ And I click the "Activity" tab
+ Then the active sub tab should be Activity
+ And no other sub tabs should be active
+ And the active main tab should be Project
+
# Sub Tabs: Settings
Scenario: On Project Settings/Integrations
Given I visit my project's settings page
And I click the "Integrations" tab
- Then the active sub nav should be Integrations
- And no other sub navs should be active
+ Then the active sub tab should be Integrations
+ And no other sub tabs should be active
And the active main tab should be Settings
- Scenario: On Project Settings/Deploy Keys
+ Scenario: On Project Settings/Repository
Given I visit my project's settings page
- And I click the "Deploy Keys" tab
- Then the active sub nav should be Deploy Keys
- And no other sub navs should be active
+ And I click the "Repository" tab
+ Then the active sub tab should be Repository
+ And no other sub tabs should be active
And the active main tab should be Settings
Scenario: On Project Settings/Pages
Given I visit my project's settings page
And I click the "Pages" tab
- Then the active sub nav should be Pages
- And no other sub navs should be active
+ Then the active sub tab should be Pages
+ And no other sub tabs should be active
And the active main tab should be Settings
Scenario: On Project Members
Given I visit my project's members page
- Then the active sub nav should be Members
- And no other sub navs should be active
+ Then the active sub tab should be Members
+ And no other sub tabs should be active
And the active main tab should be Settings
# Sub Tabs: Repository
@@ -93,6 +103,13 @@ Feature: Project Active Tab
And no other sub tabs should be active
And the active main tab should be Repository
+ Scenario: On Project Repository/Charts
+ Given I visit my project's commits page
+ And I click the "Charts" tab
+ Then the active sub tab should be Charts
+ And no other sub tabs should be active
+ And the active main tab should be Repository
+
Scenario: On Project Repository/Branches
Given I visit my project's commits page
And I click the "Branches" tab
diff --git a/features/project/shortcuts.feature b/features/project/shortcuts.feature
index 95de63ba21a..b47fca31ef2 100644
--- a/features/project/shortcuts.feature
+++ b/features/project/shortcuts.feature
@@ -25,6 +25,12 @@ Feature: Project Shortcuts
And the active main tab should be Repository
@javascript
+ Scenario: Navigate to repository charts tab
+ Given I press "g" and "g"
+ Then the active sub tab should be Charts
+ And the active main tab should be Repository
+
+ @javascript
Scenario: Navigate to issues tab
Given I press "g" and "i"
Then the active main tab should be Issues
@@ -47,4 +53,11 @@ Feature: Project Shortcuts
@javascript
Scenario: Navigate to project home
Given I press "g" and "p"
- Then the active main tab should be Home
+ Then the active sub tab should be Home
+ And the active main tab should be Project
+
+ @javascript
+ Scenario: Navigate to project feed
+ Given I press "g" and "e"
+ Then the active sub tab should be Activity
+ And the active main tab should be Project
diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb
index e842d7bec2b..4befd49ac81 100644
--- a/features/steps/project/active_tab.rb
+++ b/features/steps/project/active_tab.rb
@@ -22,37 +22,53 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
end
step 'I click the "Edit Project"' do
- page.within '.layout-nav .controls' do
+ page.within '.sub-nav' do
click_link('Edit Project')
end
end
step 'I click the "Integrations" tab' do
- click_link('Integrations')
+ page.within '.sub-nav' do
+ click_link('Integrations')
+ end
end
- step 'I click the "Deploy Keys" tab' do
- click_link('Deploy Keys')
+ step 'I click the "Repository" tab' do
+ page.within '.sub-nav' do
+ click_link('Repository')
+ end
end
step 'I click the "Pages" tab' do
- click_link('Pages')
+ page.within '.sub-nav' do
+ click_link('Pages')
+ end
end
- step 'the active sub nav should be Members' do
- ensure_active_sub_nav('Members')
+ step 'I click the "Activity" tab' do
+ page.within '.sub-nav' do
+ click_link('Activity')
+ end
end
- step 'the active sub nav should be Integrations' do
- ensure_active_sub_nav('Integrations')
+ step 'the active sub tab should be Members' do
+ ensure_active_sub_tab('Members')
end
- step 'the active sub nav should be Deploy Keys' do
- ensure_active_sub_nav('Deploy Keys')
+ step 'the active sub tab should be Integrations' do
+ ensure_active_sub_tab('Integrations')
end
- step 'the active sub nav should be Pages' do
- ensure_active_sub_nav('Pages')
+ step 'the active sub tab should be Repository' do
+ ensure_active_sub_tab('Repository')
+ end
+
+ step 'the active sub tab should be Pages' do
+ ensure_active_sub_tab('Pages')
+ end
+
+ step 'the active sub tab should be Activity' do
+ ensure_active_sub_tab('Activity')
end
# Sub Tabs: Commits
@@ -71,6 +87,12 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
click_link('Tags')
end
+ step 'I click the "Charts" tab' do
+ page.within '.sub-nav' do
+ click_link('Charts')
+ end
+ end
+
step 'the active sub tab should be Compare' do
ensure_active_sub_tab('Compare')
end
diff --git a/features/steps/project/deploy_keys.rb b/features/steps/project/deploy_keys.rb
index edf78f62f9a..580a19494c2 100644
--- a/features/steps/project/deploy_keys.rb
+++ b/features/steps/project/deploy_keys.rb
@@ -36,7 +36,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
end
step 'I should be on deploy keys page' do
- expect(current_path).to eq namespace_project_deploy_keys_path(@project.namespace, @project)
+ expect(current_path).to eq namespace_project_settings_repository_path(@project.namespace, @project)
end
step 'I should see newly created deploy key' do
diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb
index dd7a58b454a..1762d5bdf95 100644
--- a/features/steps/project/issues/award_emoji.rb
+++ b/features/steps/project/issues/award_emoji.rb
@@ -90,7 +90,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
step 'I see search result for "hand"' do
page.within '.emoji-menu-content' do
- expect(page).to have_selector '[data-emoji="raised_hand"]'
+ expect(page).to have_selector '[data-name="raised_hand"]'
end
end
diff --git a/features/steps/project/project_shortcuts.rb b/features/steps/project/project_shortcuts.rb
index 02c08b784bc..8143b01ca40 100644
--- a/features/steps/project/project_shortcuts.rb
+++ b/features/steps/project/project_shortcuts.rb
@@ -34,4 +34,9 @@ class Spinach::Features::ProjectShortcuts < Spinach::FeatureSteps
find('body').native.send_key('g')
find('body').native.send_key('w')
end
+
+ step 'I press "g" and "e"' do
+ find('body').native.send_key('g')
+ find('body').native.send_key('e')
+ end
end
diff --git a/features/steps/shared/project_tab.rb b/features/steps/shared/project_tab.rb
index 83446afe424..0cb9229dbae 100644
--- a/features/steps/shared/project_tab.rb
+++ b/features/steps/shared/project_tab.rb
@@ -4,7 +4,7 @@ module SharedProjectTab
include Spinach::DSL
include SharedActiveTab
- step 'the active main tab should be Home' do
+ step 'the active main tab should be Project' do
ensure_active_main_tab('Project')
end
@@ -16,8 +16,8 @@ module SharedProjectTab
ensure_active_main_tab('Issues')
end
- step 'the active main tab should be Members' do
- ensure_active_main_tab('Members')
+ step 'the active sub tab should be Members' do
+ ensure_active_sub_tab('Members')
end
step 'the active main tab should be Merge Requests' do
@@ -33,7 +33,7 @@ module SharedProjectTab
end
step 'the active main tab should be Settings' do
- expect(page).to have_selector('.layout-nav .nav-links > li.active', count: 0)
+ ensure_active_main_tab('Settings')
end
step 'the active sub tab should be Graph' do
@@ -47,4 +47,16 @@ module SharedProjectTab
step 'the active sub tab should be Commits' do
ensure_active_sub_tab('Commits')
end
+
+ step 'the active sub tab should be Home' do
+ ensure_active_sub_tab('Home')
+ end
+
+ step 'the active sub tab should be Activity' do
+ ensure_active_sub_tab('Activity')
+ end
+
+ step 'the active sub tab should be Charts' do
+ ensure_active_sub_tab('Charts')
+ end
end
diff --git a/fixtures/emojis/digests.json b/fixtures/emojis/digests.json
index 078d3413f33..3cbc4702dac 100644
--- a/fixtures/emojis/digests.json
+++ b/fixtures/emojis/digests.json
@@ -1,11622 +1,10748 @@
-[
- {
- "name": "100",
- "unicode": "1F4AF",
+{
+ "100": {
+ "category": "symbols",
+ "moji": "💯",
+ "unicodeVersion": "6.0",
"digest": "add3bd7d06b6dd445788b277f8c9e5dcf42a54d3ec8b7fb9e7a39695dd95d094"
},
- {
- "name": "1234",
- "unicode": "1F522",
+ "1234": {
+ "category": "symbols",
+ "moji": "🔢",
+ "unicodeVersion": "6.0",
"digest": "c5ac5c8147f5bfd644fad6b470432bba86ffc7bcee04a0e0d277cd1ca485207f"
},
- {
- "name": "8ball",
- "unicode": "1F3B1",
+ "8ball": {
+ "category": "activity",
+ "moji": "🎱",
+ "unicodeVersion": "6.0",
"digest": "a6e6855775b66c505adee65926a264103ebddf2e2d963db7c009b4fec3a24178"
},
- {
- "name": "a",
- "unicode": "1F170",
+ "a": {
+ "category": "symbols",
+ "moji": "🅰",
+ "unicodeVersion": "6.0",
"digest": "bddbb39e8a1d35d42b7c08e7d47f63988cb4d8614b79f74e70b9c67c221896cc"
},
- {
- "name": "ab",
- "unicode": "1F18E",
+ "ab": {
+ "category": "symbols",
+ "moji": "🆎",
+ "unicodeVersion": "6.0",
"digest": "67430fe5fce981160e2ea9052962e49f264322d3abfc2828fbc311b6cdf67ae8"
},
- {
- "name": "abc",
- "unicode": "1F524",
+ "abc": {
+ "category": "symbols",
+ "moji": "🔤",
+ "unicodeVersion": "6.0",
"digest": "282c817ee3414d77a74b815962c33dd9fe71fabaea8c7a9cec466100fbe32187"
},
- {
- "name": "abcd",
- "unicode": "1F521",
+ "abcd": {
+ "category": "symbols",
+ "moji": "🔡",
+ "unicodeVersion": "6.0",
"digest": "686728c759f4683c64762ee4eda0a91bf2041f0ae4f358aacf6c09bf51892eff"
},
- {
- "name": "accept",
- "unicode": "1F251",
+ "accept": {
+ "category": "symbols",
+ "moji": "🉑",
+ "unicodeVersion": "6.0",
"digest": "7208d34c761f10a7fd28f98e25535eba13ff91a64442fc282a98bb77722614f1"
},
- {
- "name": "aerial_tramway",
- "unicode": "1F6A1",
+ "aerial_tramway": {
+ "category": "travel",
+ "moji": "🚡",
+ "unicodeVersion": "6.0",
"digest": "98df666f34370fc34ce280d84bba5a7e617f733fbbfe66caa424b2afa6ab6777"
},
- {
- "name": "airplane",
- "unicode": "2708",
+ "airplane": {
+ "category": "travel",
+ "moji": "✈",
+ "unicodeVersion": "1.1",
"digest": "cc12cf259ef88e57717620cd2bd5aa6a02a8631ee532a3bde24bee78edc5de33"
},
- {
- "name": "airplane_arriving",
- "unicode": "1F6EC",
+ "airplane_arriving": {
+ "category": "travel",
+ "moji": "🛬",
+ "unicodeVersion": "7.0",
"digest": "80d5b4675f91c4cff06d146d795a065b0ce2a74557df4d9e3314e3d3b5c4ae82"
},
- {
- "name": "airplane_departure",
- "unicode": "1F6EB",
+ "airplane_departure": {
+ "category": "travel",
+ "moji": "🛫",
+ "unicodeVersion": "7.0",
"digest": "5544eace06b8e1b6ea91940e893e013d33d6b166e14e6d128a87f2cd2de88332"
},
- {
- "name": "airplane_small",
- "unicode": "1F6E9",
+ "airplane_small": {
+ "category": "travel",
+ "moji": "🛩",
+ "unicodeVersion": "7.0",
"digest": "1a2e07abbbe90d05cee7ff8dd52f443d595ccb38959f3089fe016b77e5d6de7d"
},
- {
- "name": "small_airplane",
- "unicode": "1F6E9",
- "digest": "1a2e07abbbe90d05cee7ff8dd52f443d595ccb38959f3089fe016b77e5d6de7d"
- },
- {
- "name": "alarm_clock",
- "unicode": "23F0",
+ "alarm_clock": {
+ "category": "objects",
+ "moji": "⏰",
+ "unicodeVersion": "6.0",
"digest": "fef05a3cd1cddbeca4de8091b94bddb93790b03fa213da86c0eec420f8c49599"
},
- {
- "name": "alembic",
- "unicode": "2697",
+ "alembic": {
+ "category": "objects",
+ "moji": "⚗",
+ "unicodeVersion": "4.1",
"digest": "c94b2a4bf24ccf4db27a22c9725cfe900f4a99ec49ef2411d67952bcb2ca1bfb"
},
- {
- "name": "alien",
- "unicode": "1F47D",
+ "alien": {
+ "category": "people",
+ "moji": "👽",
+ "unicodeVersion": "6.0",
"digest": "856ba98202b244c13a5ee3014a6f7ad592d8c119a30d79e4fc790b74b0e321f7"
},
- {
- "name": "ambulance",
- "unicode": "1F691",
+ "ambulance": {
+ "category": "travel",
+ "moji": "🚑",
+ "unicodeVersion": "6.0",
"digest": "d9b3c1873de496a4554e715342c72290fb69a9c6766d7885f38bfe9491d052da"
},
- {
- "name": "amphora",
- "unicode": "1F3FA",
+ "amphora": {
+ "category": "objects",
+ "moji": "🏺",
+ "unicodeVersion": "8.0",
"digest": "4015f907b649b5e348502cc0e3685ed184e180dca5cc81c43ec516e14df127bf"
},
- {
- "name": "anchor",
- "unicode": "2693",
+ "anchor": {
+ "category": "travel",
+ "moji": "⚓",
+ "unicodeVersion": "4.1",
"digest": "2b29b34ef896ebab70016301e3d1880209bbc3c5a5b8d832e43afff9b17ad792"
},
- {
- "name": "angel",
- "unicode": "1F47C",
+ "angel": {
+ "category": "people",
+ "moji": "👼",
+ "unicodeVersion": "6.0",
"digest": "db75c2460aaf9cd07cb41fe22c8a6079f3667ffe612a71611358720e2b5512a4"
},
- {
- "name": "angel_tone1",
- "unicode": "1F47C-1F3FB",
+ "angel_tone1": {
+ "category": "people",
+ "moji": "👼🏻",
+ "unicodeVersion": "8.0",
"digest": "5871a622469b96296365adaf77d83167759692124c20e5a6e062a525af33472a"
},
- {
- "name": "angel_tone2",
- "unicode": "1F47C-1F3FC",
+ "angel_tone2": {
+ "category": "people",
+ "moji": "👼🏼",
+ "unicodeVersion": "8.0",
"digest": "f5993198a5d9daf39e761c783461f07bca237f4e9b739ac300bb8ca001a69a1a"
},
- {
- "name": "angel_tone3",
- "unicode": "1F47C-1F3FD",
+ "angel_tone3": {
+ "category": "people",
+ "moji": "👼🏽",
+ "unicodeVersion": "8.0",
"digest": "f0c97a7c4354626267d6ab0f388e4297ad255ab9b061f9c68fbcaa0abfc52783"
},
- {
- "name": "angel_tone4",
- "unicode": "1F47C-1F3FE",
+ "angel_tone4": {
+ "category": "people",
+ "moji": "👼🏾",
+ "unicodeVersion": "8.0",
"digest": "6e5dc724c1939d1b0d1a91343662b5bd61ced7709c97802977145ffab6a1f7ac"
},
- {
- "name": "angel_tone5",
- "unicode": "1F47C-1F3FF",
+ "angel_tone5": {
+ "category": "people",
+ "moji": "👼🏿",
+ "unicodeVersion": "8.0",
"digest": "52186e1de350c27d25d6010edf44f64a30338b65912ca178429fbcfbd88113c2"
},
- {
- "name": "anger",
- "unicode": "1F4A2",
+ "anger": {
+ "category": "symbols",
+ "moji": "💢",
+ "unicodeVersion": "6.0",
"digest": "332493913891aa0eda2743b4bb16c4682400f249998bf34eb292246c9009e17f"
},
- {
- "name": "anger_right",
- "unicode": "1F5EF",
+ "anger_right": {
+ "category": "symbols",
+ "moji": "🗯",
+ "unicodeVersion": "7.0",
"digest": "8b049511ef3b1b28325841e2f87c60773eaf2f65cabba58d8b0ec3de9b10c0ae"
},
- {
- "name": "right_anger_bubble",
- "unicode": "1F5EF",
- "digest": "8b049511ef3b1b28325841e2f87c60773eaf2f65cabba58d8b0ec3de9b10c0ae"
- },
- {
- "name": "angry",
- "unicode": "1F620",
+ "angry": {
+ "category": "people",
+ "moji": "😠",
+ "unicodeVersion": "6.0",
"digest": "7e09e7e821f511606341fb5ce4011a8ed9809766ab86b7983ffa6ea352b39ec1"
},
- {
- "name": "anguished",
- "unicode": "1F627",
- "digest": "a2b6f052996969a17150249d9ef5db742da3d6585bd38ca61eb14c4c13cda54f"
- },
- {
- "name": "ant",
- "unicode": "1F41C",
+ "ant": {
+ "category": "nature",
+ "moji": "🐜",
+ "unicodeVersion": "6.0",
"digest": "929abeaff7ba21ab71cd1ab798af7a6b611e3b3ce1af80cede09a116b223e442"
},
- {
- "name": "apple",
- "unicode": "1F34E",
+ "apple": {
+ "category": "food",
+ "moji": "🍎",
+ "unicodeVersion": "6.0",
"digest": "2a1b85ce57e3d236ae7777dcf332ec37d03bfd7b19806521a353bc532083224d"
},
- {
- "name": "aquarius",
- "unicode": "2652",
+ "aquarius": {
+ "category": "symbols",
+ "moji": "♒",
+ "unicodeVersion": "1.1",
"digest": "fdc42cd41b0dace5eae6baba3143f1e40295d48a29e7103a5bba1d84a056c39d"
},
- {
- "name": "aries",
- "unicode": "2648",
+ "aries": {
+ "category": "symbols",
+ "moji": "♈",
+ "unicodeVersion": "1.1",
"digest": "deb135debcde0a98f40361a84ab64d57c18b5b445cd2f4199e8936f052899737"
},
- {
- "name": "arrow_backward",
- "unicode": "25C0",
+ "arrow_backward": {
+ "category": "symbols",
+ "moji": "◀",
+ "unicodeVersion": "1.1",
"digest": "e162ac82e90d1e925d479fa5c45b9340e0a53287be04e43cbbb2a89c7e7e45e4"
},
- {
- "name": "arrow_double_down",
- "unicode": "23EC",
+ "arrow_double_down": {
+ "category": "symbols",
+ "moji": "⏬",
+ "unicodeVersion": "6.0",
"digest": "03ca890b05338d40972c7a056d672df620a203c6ca52ff3ff530f1a710905507"
},
- {
- "name": "arrow_double_up",
- "unicode": "23EB",
+ "arrow_double_up": {
+ "category": "symbols",
+ "moji": "⏫",
+ "unicodeVersion": "6.0",
"digest": "e753f05bce993d62d5dc79e33c441ced059381b6ce21fa3ea4200f1b3236e59d"
},
- {
- "name": "arrow_down",
- "unicode": "2B07",
+ "arrow_down": {
+ "category": "symbols",
+ "moji": "⬇",
+ "unicodeVersion": "4.0",
"digest": "9bf1bd2ea652ca9321087de58c7a112ea04c35676a6ee0766154183f8b95af6c"
},
- {
- "name": "arrow_down_small",
- "unicode": "1F53D",
+ "arrow_down_small": {
+ "category": "symbols",
+ "moji": "🔽",
+ "unicodeVersion": "6.0",
"digest": "7766198bc60cf59d6cdaeeaa700c2282bfff2f0fdeb22cf4581ca284b87a3bb7"
},
- {
- "name": "arrow_forward",
- "unicode": "25B6",
+ "arrow_forward": {
+ "category": "symbols",
+ "moji": "▶",
+ "unicodeVersion": "1.1",
"digest": "db77d9accd1e02224f5d612f79cd691e6befdf22063475204836be6572510fb7"
},
- {
- "name": "arrow_heading_down",
- "unicode": "2935",
+ "arrow_heading_down": {
+ "category": "symbols",
+ "moji": "⤵",
+ "unicodeVersion": "3.2",
"digest": "f5396069c8f63c13e6c3e0ecd34267c932451309ade9c1171d410563153bf909"
},
- {
- "name": "arrow_heading_up",
- "unicode": "2934",
+ "arrow_heading_up": {
+ "category": "symbols",
+ "moji": "⤴",
+ "unicodeVersion": "3.2",
"digest": "1cad71923fa3df24cf543cae4ce775b0f74936f2edd685fd86a7525c41a14568"
},
- {
- "name": "arrow_left",
- "unicode": "2B05",
+ "arrow_left": {
+ "category": "symbols",
+ "moji": "⬅",
+ "unicodeVersion": "4.0",
"digest": "b629bb3dbe161ef89cfcfced0c7968a68e44a019ad509132987e4973bdc874e7"
},
- {
- "name": "arrow_lower_left",
- "unicode": "2199",
+ "arrow_lower_left": {
+ "category": "symbols",
+ "moji": "↙",
+ "unicodeVersion": "1.1",
"digest": "879136ba0e24e6bf3be70118abcb716d71bd74f7b62347bc052b6533c0ea534d"
},
- {
- "name": "arrow_lower_right",
- "unicode": "2198",
+ "arrow_lower_right": {
+ "category": "symbols",
+ "moji": "↘",
+ "unicodeVersion": "1.1",
"digest": "86d52ac9b961991e3aaa6a9f9b5ace4db6ffd1b5c171c09c23b516473b55066d"
},
- {
- "name": "arrow_right",
- "unicode": "27A1",
+ "arrow_right": {
+ "category": "symbols",
+ "moji": "➡",
+ "unicodeVersion": "1.1",
"digest": "45f26a1cbb0f00ed3609b39da52e9d9e896a77e361c4c8036b1bf8038171bd49"
},
- {
- "name": "arrow_right_hook",
- "unicode": "21AA",
+ "arrow_right_hook": {
+ "category": "symbols",
+ "moji": "↪",
+ "unicodeVersion": "1.1",
"digest": "4f452679c71bcea4fc4a701c55156fef3ddc1ebbc70570bedfc9d3a029637ab1"
},
- {
- "name": "arrow_up",
- "unicode": "2B06",
+ "arrow_up": {
+ "category": "symbols",
+ "moji": "⬆",
+ "unicodeVersion": "4.0",
"digest": "982b988ef6651d8a71867ba7c87f640f62dd0eeb0b7c358f5a5c37e8fe507b8b"
},
- {
- "name": "arrow_up_down",
- "unicode": "2195",
+ "arrow_up_down": {
+ "category": "symbols",
+ "moji": "↕",
+ "unicodeVersion": "1.1",
"digest": "645ed8fb6646f49bfd95af1752336deacdadbe5cba13904023a704288f3b0e2c"
},
- {
- "name": "arrow_up_small",
- "unicode": "1F53C",
+ "arrow_up_small": {
+ "category": "symbols",
+ "moji": "🔼",
+ "unicodeVersion": "6.0",
"digest": "4a8c5789c13a852517e639e7a62c2d331464e6fb0358985aa97c1515e97b5e8b"
},
- {
- "name": "arrow_upper_left",
- "unicode": "2196",
+ "arrow_upper_left": {
+ "category": "symbols",
+ "moji": "↖",
+ "unicodeVersion": "1.1",
"digest": "79026f828d6ceb7c55a9542770962ba6dcd08203995f6ceeb70333a12307d376"
},
- {
- "name": "arrow_upper_right",
- "unicode": "2197",
+ "arrow_upper_right": {
+ "category": "symbols",
+ "moji": "↗",
+ "unicodeVersion": "1.1",
"digest": "7e0f33dfbe65628991c170130d366a3e2cedaf8862ddfcaf3960f395d3da1926"
},
- {
- "name": "arrows_clockwise",
- "unicode": "1F503",
+ "arrows_clockwise": {
+ "category": "symbols",
+ "moji": "🔃",
+ "unicodeVersion": "6.0",
"digest": "88669679977f7157f0acaa9d6a1b77ccf84d25eb78c5bc8afcde38d3635e7144"
},
- {
- "name": "arrows_counterclockwise",
- "unicode": "1F504",
+ "arrows_counterclockwise": {
+ "category": "symbols",
+ "moji": "🔄",
+ "unicodeVersion": "6.0",
"digest": "a2c6a6d3643c128aee3304cd03bb3d7cfe4d35d3ba825bc9c1142d7832b4426e"
},
- {
- "name": "art",
- "unicode": "1F3A8",
+ "art": {
+ "category": "activity",
+ "moji": "🎨",
+ "unicodeVersion": "6.0",
"digest": "b6bc6c4bfb594aadcbb641d006031867678504764bbe0ab84e7b08567a9498da"
},
- {
- "name": "articulated_lorry",
- "unicode": "1F69B",
+ "articulated_lorry": {
+ "category": "travel",
+ "moji": "🚛",
+ "unicodeVersion": "6.0",
"digest": "c115e6613ebd718268aa31d265e017138b9fb58bbb8201eb3f40de2380e460aa"
},
- {
- "name": "asterisk",
- "unicode": "002A-20E3",
+ "asterisk": {
+ "category": "symbols",
+ "moji": "*⃣",
+ "unicodeVersion": "3.0",
"digest": "33d92093f2914448d5a939cf62e8ee3e32931923abdef5f0210e8a8150fa312d"
},
- {
- "name": "keycap_asterisk",
- "unicode": "002A-20E3",
- "digest": "33d92093f2914448d5a939cf62e8ee3e32931923abdef5f0210e8a8150fa312d"
- },
- {
- "name": "astonished",
- "unicode": "1F632",
+ "astonished": {
+ "category": "people",
+ "moji": "😲",
+ "unicodeVersion": "6.0",
"digest": "f8531bdda5070d10492709085f4ff652b8be9be6458758940358b9fc594a1f14"
},
- {
- "name": "athletic_shoe",
- "unicode": "1F45F",
+ "athletic_shoe": {
+ "category": "people",
+ "moji": "👟",
+ "unicodeVersion": "6.0",
"digest": "1f90dc390e0dea679085465b7f9e786dfd7dd56a3b219987144ed37ab1e9bf95"
},
- {
- "name": "atm",
- "unicode": "1F3E7",
+ "atm": {
+ "category": "symbols",
+ "moji": "🏧",
+ "unicodeVersion": "6.0",
"digest": "7d3ce6a6afb4951546883404b8e36904179f88f1aa533706cf7bf0bbe0d6fd3c"
},
- {
- "name": "atom",
- "unicode": "269B",
- "digest": "6b6bb83b00707a314e46ff8eefbda40978a291ec7881caba1b1ee273f49c1368"
- },
- {
- "name": "atom_symbol",
- "unicode": "269B",
+ "atom": {
+ "category": "symbols",
+ "moji": "⚛",
+ "unicodeVersion": "4.1",
"digest": "6b6bb83b00707a314e46ff8eefbda40978a291ec7881caba1b1ee273f49c1368"
},
- {
- "name": "avocado",
- "unicode": "1F951",
+ "avocado": {
+ "category": "food",
+ "moji": "🥑",
+ "unicodeVersion": "9.0",
"digest": "bc1fb203d63b18985598400925de24050bb192afda1cbf0813f85cb139869eff"
},
- {
- "name": "b",
- "unicode": "1F171",
+ "b": {
+ "category": "symbols",
+ "moji": "🅱",
+ "unicodeVersion": "6.0",
"digest": "722f9db9442e7c0fc0d0ac0f5291fbf47c6a0ac4d8abd42e97957da705fb82bf"
},
- {
- "name": "baby",
- "unicode": "1F476",
+ "baby": {
+ "category": "people",
+ "moji": "👶",
+ "unicodeVersion": "6.0",
"digest": "219ae5a571aaf90c060956cd1c56dcc27708c827cecdca3ba1122058a3c4847b"
},
- {
- "name": "baby_bottle",
- "unicode": "1F37C",
+ "baby_bottle": {
+ "category": "food",
+ "moji": "🍼",
+ "unicodeVersion": "6.0",
"digest": "4fb71689e9d634e8d1699cf454a71e43f2b5b1a5dbab0bf186626934fdf5b782"
},
- {
- "name": "baby_chick",
- "unicode": "1F424",
+ "baby_chick": {
+ "category": "nature",
+ "moji": "🐤",
+ "unicodeVersion": "6.0",
"digest": "14119874e9b5548028dfb9cc593a541efc1d075ac839a565b92e0c3253cffe7e"
},
- {
- "name": "baby_symbol",
- "unicode": "1F6BC",
+ "baby_symbol": {
+ "category": "symbols",
+ "moji": "🚼",
+ "unicodeVersion": "6.0",
"digest": "fb4db66868cda45ea3879ffc2ff4f763c56d2d889ae0ab17fe171129ede02f98"
},
- {
- "name": "baby_tone1",
- "unicode": "1F476-1F3FB",
+ "baby_tone1": {
+ "category": "people",
+ "moji": "👶🏻",
+ "unicodeVersion": "8.0",
"digest": "cd3faf223a298c34e05d469d9d0db08438d97df7fd82c0973f8a9e07d553f5b1"
},
- {
- "name": "baby_tone2",
- "unicode": "1F476-1F3FC",
+ "baby_tone2": {
+ "category": "people",
+ "moji": "👶🏼",
+ "unicodeVersion": "8.0",
"digest": "5b4539e22e0dd726c27eb8af2357f9240a52aed3f710f3234571cff029cc6198"
},
- {
- "name": "baby_tone3",
- "unicode": "1F476-1F3FD",
+ "baby_tone3": {
+ "category": "people",
+ "moji": "👶🏽",
+ "unicodeVersion": "8.0",
"digest": "720e740e1ac63c6372269132b1fb6e07a6b91f5c808cc3adef59f0b4500e5e72"
},
- {
- "name": "baby_tone4",
- "unicode": "1F476-1F3FE",
+ "baby_tone4": {
+ "category": "people",
+ "moji": "👶🏾",
+ "unicodeVersion": "8.0",
"digest": "5e43b69c509bd526ad6f081764578c30b6f3285fb7442222e05ccf62e53bfb64"
},
- {
- "name": "baby_tone5",
- "unicode": "1F476-1F3FF",
+ "baby_tone5": {
+ "category": "people",
+ "moji": "👶🏿",
+ "unicodeVersion": "8.0",
"digest": "85bba6e0940ccfb99999fe124e815f9dd340d00a5568e13967b02245a62dbf54"
},
- {
- "name": "back",
- "unicode": "1F519",
+ "back": {
+ "category": "symbols",
+ "moji": "🔙",
+ "unicodeVersion": "6.0",
"digest": "083e4e48b51092c28efb4532e840e1091b5d4b685c6e0f221aa0228f061cd91e"
},
- {
- "name": "bacon",
- "unicode": "1F953",
+ "bacon": {
+ "category": "food",
+ "moji": "🥓",
+ "unicodeVersion": "9.0",
"digest": "18ad3817f1f88a69706db5727a58e763dde6c21a2d4f184c3d728c32dc5fa05a"
},
- {
- "name": "badminton",
- "unicode": "1F3F8",
+ "badminton": {
+ "category": "activity",
+ "moji": "🏸",
+ "unicodeVersion": "8.0",
"digest": "353eb7ee93decd9fe0072e4d78a5618d5e2d9e77a6e4de9fe171870d75e02a66"
},
- {
- "name": "baggage_claim",
- "unicode": "1F6C4",
+ "baggage_claim": {
+ "category": "symbols",
+ "moji": "🛄",
+ "unicodeVersion": "6.0",
"digest": "7d6bceca92c266da6d2b91dfcf244546fc11022e039e7da8e6888c1696bb2186"
},
- {
- "name": "balloon",
- "unicode": "1F388",
+ "balloon": {
+ "category": "objects",
+ "moji": "🎈",
+ "unicodeVersion": "6.0",
"digest": "65760aedc1503b426927cff78c24449d563843a274961d962718fa9638375d54"
},
- {
- "name": "ballot_box",
- "unicode": "1F5F3",
+ "ballot_box": {
+ "category": "objects",
+ "moji": "🗳",
+ "unicodeVersion": "7.0",
"digest": "4175a56eca5c6458574a681e109b1403fbb143cf27f69ae6c1917650f3e08892"
},
- {
- "name": "ballot_box_with_ballot",
- "unicode": "1F5F3",
- "digest": "4175a56eca5c6458574a681e109b1403fbb143cf27f69ae6c1917650f3e08892"
- },
- {
- "name": "ballot_box_with_check",
- "unicode": "2611",
+ "ballot_box_with_check": {
+ "category": "symbols",
+ "moji": "☑",
+ "unicodeVersion": "1.1",
"digest": "c98d6f3588dd87e2f318bbfe6c646399a905450edfd814edae4e5b1bddef2134"
},
- {
- "name": "bamboo",
- "unicode": "1F38D",
+ "bamboo": {
+ "category": "nature",
+ "moji": "🎍",
+ "unicodeVersion": "6.0",
"digest": "e4ee65088df43d7081b1ce6fd996f66f3e0accd88840855c47a98a22997823dd"
},
- {
- "name": "banana",
- "unicode": "1F34C",
+ "banana": {
+ "category": "food",
+ "moji": "🍌",
+ "unicodeVersion": "6.0",
"digest": "f9e8ff910c282c20a8907ff64926b5de4ee250529a1ed718fb33302e6fff8dd9"
},
- {
- "name": "bangbang",
- "unicode": "203C",
+ "bangbang": {
+ "category": "symbols",
+ "moji": "‼",
+ "unicodeVersion": "1.1",
"digest": "76536fee63fe964a3f3839d309b1f45028fb0c43f4d1eeee495f17e1532b4def"
},
- {
- "name": "bank",
- "unicode": "1F3E6",
+ "bank": {
+ "category": "travel",
+ "moji": "🏦",
+ "unicodeVersion": "6.0",
"digest": "f5d2976bf6d521638ccacc74be06bd4abfeab06c5d898a9d245edad45a5b6306"
},
- {
- "name": "bar_chart",
- "unicode": "1F4CA",
+ "bar_chart": {
+ "category": "objects",
+ "moji": "📊",
+ "unicodeVersion": "6.0",
"digest": "65a328a1b2d7a5332dd4d93f4dbca13d976f0a505b00835c3fc458e394804240"
},
- {
- "name": "barber",
- "unicode": "1F488",
+ "barber": {
+ "category": "objects",
+ "moji": "💈",
+ "unicodeVersion": "6.0",
"digest": "5e8053d3bb3765a8632fd1cbfe21163f74ed79f6be377eb9603eaaf883d8dc46"
},
- {
- "name": "baseball",
- "unicode": "26BE",
+ "baseball": {
+ "category": "activity",
+ "moji": "⚾",
+ "unicodeVersion": "5.2",
"digest": "46ac16f8b5455b942f6dbff9483a6fd277721e6719d2731573baabd21c44b34f"
},
- {
- "name": "basketball",
- "unicode": "1F3C0",
+ "basketball": {
+ "category": "activity",
+ "moji": "🏀",
+ "unicodeVersion": "6.0",
"digest": "cc83e2aea8fcd2e9a5789e1932ee3766c40843c142fd3565c4e77dafb21ec7d7"
},
- {
- "name": "basketball_player",
- "unicode": "26F9",
- "digest": "793ba53c95e8def769383b612037bc9b9bceecaf1e0430c50a4cc128ad18d9b9"
- },
- {
- "name": "person_with_ball",
- "unicode": "26F9",
+ "basketball_player": {
+ "category": "activity",
+ "moji": "⛹",
+ "unicodeVersion": "5.2",
"digest": "793ba53c95e8def769383b612037bc9b9bceecaf1e0430c50a4cc128ad18d9b9"
},
- {
- "name": "basketball_player_tone1",
- "unicode": "26F9-1F3FB",
+ "basketball_player_tone1": {
+ "category": "activity",
+ "moji": "⛹🏻",
+ "unicodeVersion": "8.0",
"digest": "2a06522b971e68ee5b8777a58253009b548f4da2fb723c638acb3d7b04edba8f"
},
- {
- "name": "person_with_ball_tone1",
- "unicode": "26F9-1F3FB",
- "digest": "2a06522b971e68ee5b8777a58253009b548f4da2fb723c638acb3d7b04edba8f"
- },
- {
- "name": "basketball_player_tone2",
- "unicode": "26F9-1F3FC",
- "digest": "ecc0e44ab9bc478ba45a055fd69a3a38377b917aac5047963fe80ff8ae5fd8e3"
- },
- {
- "name": "person_with_ball_tone2",
- "unicode": "26F9-1F3FC",
+ "basketball_player_tone2": {
+ "category": "activity",
+ "moji": "⛹🏼",
+ "unicodeVersion": "8.0",
"digest": "ecc0e44ab9bc478ba45a055fd69a3a38377b917aac5047963fe80ff8ae5fd8e3"
},
- {
- "name": "basketball_player_tone3",
- "unicode": "26F9-1F3FD",
+ "basketball_player_tone3": {
+ "category": "activity",
+ "moji": "⛹🏽",
+ "unicodeVersion": "8.0",
"digest": "2d38f1851c685d29532c042461d7b5b996e5f04f0ed54857c66073c62a99ceac"
},
- {
- "name": "person_with_ball_tone3",
- "unicode": "26F9-1F3FD",
- "digest": "2d38f1851c685d29532c042461d7b5b996e5f04f0ed54857c66073c62a99ceac"
- },
- {
- "name": "basketball_player_tone4",
- "unicode": "26F9-1F3FE",
+ "basketball_player_tone4": {
+ "category": "activity",
+ "moji": "⛹🏾",
+ "unicodeVersion": "8.0",
"digest": "09e957c6e9ffc196415f28073aa261feba8efba0bdc694dc08f8f7cd1f88f720"
},
- {
- "name": "person_with_ball_tone4",
- "unicode": "26F9-1F3FE",
- "digest": "09e957c6e9ffc196415f28073aa261feba8efba0bdc694dc08f8f7cd1f88f720"
- },
- {
- "name": "basketball_player_tone5",
- "unicode": "26F9-1F3FF",
+ "basketball_player_tone5": {
+ "category": "activity",
+ "moji": "⛹🏿",
+ "unicodeVersion": "8.0",
"digest": "c631cefc5d2a0a31bdb9f0a0d97ea68b1c6928e565468998403034644572a0b0"
},
- {
- "name": "person_with_ball_tone5",
- "unicode": "26F9-1F3FF",
- "digest": "c631cefc5d2a0a31bdb9f0a0d97ea68b1c6928e565468998403034644572a0b0"
- },
- {
- "name": "bat",
- "unicode": "1F987",
+ "bat": {
+ "category": "nature",
+ "moji": "🦇",
+ "unicodeVersion": "9.0",
"digest": "8fc19e0d7d6f80906bdbc06d616a810de66180d96cf28070a53fa61b88904535"
},
- {
- "name": "bath",
- "unicode": "1F6C0",
+ "bath": {
+ "category": "activity",
+ "moji": "🛀",
+ "unicodeVersion": "6.0",
"digest": "33b371832f90aad50baf5296f3ad4cc081c319b279f989c74409903d8568e917"
},
- {
- "name": "bath_tone1",
- "unicode": "1F6C0-1F3FB",
+ "bath_tone1": {
+ "category": "activity",
+ "moji": "🛀🏻",
+ "unicodeVersion": "8.0",
"digest": "7ae2989e47788ba71359d52da68feec95aaff68a77d5a6556957df1617af8536"
},
- {
- "name": "bath_tone2",
- "unicode": "1F6C0-1F3FC",
+ "bath_tone2": {
+ "category": "activity",
+ "moji": "🛀🏼",
+ "unicodeVersion": "8.0",
"digest": "2e86f8edad54d15a7094cd52160cbe51d10aa1750cfb0b3b58e93533f070e327"
},
- {
- "name": "bath_tone3",
- "unicode": "1F6C0-1F3FD",
+ "bath_tone3": {
+ "category": "activity",
+ "moji": "🛀🏽",
+ "unicodeVersion": "8.0",
"digest": "654c0cd083a67ff330a38d07352876d265390e5399e5352598d64a6c7e5eeba7"
},
- {
- "name": "bath_tone4",
- "unicode": "1F6C0-1F3FE",
+ "bath_tone4": {
+ "category": "activity",
+ "moji": "🛀🏾",
+ "unicodeVersion": "8.0",
"digest": "adad88c6830f31c4b5be194d1987d6aadf4adf45e4cb7f2e4657f0d20c0d663a"
},
- {
- "name": "bath_tone5",
- "unicode": "1F6C0-1F3FF",
+ "bath_tone5": {
+ "category": "activity",
+ "moji": "🛀🏿",
+ "unicodeVersion": "8.0",
"digest": "952c4c9bf24e001e23a33ebf97bd92969cd9143e28ce93f9aafc708a8f966903"
},
- {
- "name": "bathtub",
- "unicode": "1F6C1",
+ "bathtub": {
+ "category": "objects",
+ "moji": "🛁",
+ "unicodeVersion": "6.0",
"digest": "844dffb87ef872594195069b0d0df27c3fe51f3967ccbc8b2df811a086dd483a"
},
- {
- "name": "battery",
- "unicode": "1F50B",
+ "battery": {
+ "category": "objects",
+ "moji": "🔋",
+ "unicodeVersion": "6.0",
"digest": "949ae06648667fb13d9121a6dfdd03bf8692794b28c36e9a8e8ac4515664449a"
},
- {
- "name": "beach",
- "unicode": "1F3D6",
- "digest": "37fa2158977d470186caaa1aa06669b6dc5026ba49a0c44c5255541f8e974e26"
- },
- {
- "name": "beach_with_umbrella",
- "unicode": "1F3D6",
+ "beach": {
+ "category": "travel",
+ "moji": "🏖",
+ "unicodeVersion": "7.0",
"digest": "37fa2158977d470186caaa1aa06669b6dc5026ba49a0c44c5255541f8e974e26"
},
- {
- "name": "beach_umbrella",
- "unicode": "26F1",
+ "beach_umbrella": {
+ "category": "objects",
+ "moji": "⛱",
+ "unicodeVersion": "5.2",
"digest": "d045f1de10038b9fb1eaa2529b2f80b7e3be1cff503efcc2d680663d1fbbc18f"
},
- {
- "name": "umbrella_on_ground",
- "unicode": "26F1",
- "digest": "d045f1de10038b9fb1eaa2529b2f80b7e3be1cff503efcc2d680663d1fbbc18f"
- },
- {
- "name": "bear",
- "unicode": "1F43B",
+ "bear": {
+ "category": "nature",
+ "moji": "🐻",
+ "unicodeVersion": "6.0",
"digest": "a4b9066eaa5681e6af06e596a96a5217037460ffc3b013e8db4d34d762413246"
},
- {
- "name": "bed",
- "unicode": "1F6CF",
+ "bed": {
+ "category": "objects",
+ "moji": "🛏",
+ "unicodeVersion": "7.0",
"digest": "08f6e20db51b1fb650b390a0a3074938646772f3fcee8c295d47742e44fe1e30"
},
- {
- "name": "bee",
- "unicode": "1F41D",
+ "bee": {
+ "category": "nature",
+ "moji": "🐝",
+ "unicodeVersion": "6.0",
"digest": "5beb9a1650681b4adf69999d4808231c38f41a3ec693480b807cda86f964c570"
},
- {
- "name": "beer",
- "unicode": "1F37A",
+ "beer": {
+ "category": "food",
+ "moji": "🍺",
+ "unicodeVersion": "6.0",
"digest": "69e227104976548ee0f37375fe1526fd65ef0a328d2d92db2feb1edfd7032bd4"
},
- {
- "name": "beers",
- "unicode": "1F37B",
+ "beers": {
+ "category": "food",
+ "moji": "🍻",
+ "unicodeVersion": "6.0",
"digest": "db8b32d93bf6d161a3b027e55651d8f51231b13928b3610987ef62bb634d7501"
},
- {
- "name": "beetle",
- "unicode": "1F41E",
+ "beetle": {
+ "category": "nature",
+ "moji": "🐞",
+ "unicodeVersion": "6.0",
"digest": "5aaa428e3f63f7cd1696839ab05be03fa0cd0cbed30a05c36cb270da330c3849"
},
- {
- "name": "beginner",
- "unicode": "1F530",
+ "beginner": {
+ "category": "symbols",
+ "moji": "🔰",
+ "unicodeVersion": "6.0",
"digest": "2de4fdf92f182c42b12b7527034eaf767d996848b61f31ee69167728411ca0b1"
},
- {
- "name": "bell",
- "unicode": "1F514",
+ "bell": {
+ "category": "symbols",
+ "moji": "🔔",
+ "unicodeVersion": "6.0",
"digest": "18d419417746ead408072b78fe2edb6314cdb49492873966fa9f9f06be09899b"
},
- {
- "name": "bellhop",
- "unicode": "1F6CE",
- "digest": "b8187bc4059f6a0924a47fe3f6c07f656bed0334bbcbfa1e89f800fe6594ff08"
- },
- {
- "name": "bellhop_bell",
- "unicode": "1F6CE",
+ "bellhop": {
+ "category": "objects",
+ "moji": "🛎",
+ "unicodeVersion": "7.0",
"digest": "b8187bc4059f6a0924a47fe3f6c07f656bed0334bbcbfa1e89f800fe6594ff08"
},
- {
- "name": "bento",
- "unicode": "1F371",
+ "bento": {
+ "category": "food",
+ "moji": "🍱",
+ "unicodeVersion": "6.0",
"digest": "d46d4f681c5da7f7678b51be3445454a8ed18d917e132ae79077f05310e485f1"
},
- {
- "name": "bicyclist",
- "unicode": "1F6B4",
+ "bicyclist": {
+ "category": "activity",
+ "moji": "🚴",
+ "unicodeVersion": "6.0",
"digest": "3302147b6b47c16adb97d78b7b761a1ca80e6d0b41d0b60f4da338d2f55f968b"
},
- {
- "name": "bicyclist_tone1",
- "unicode": "1F6B4-1F3FB",
+ "bicyclist_tone1": {
+ "category": "activity",
+ "moji": "🚴🏻",
+ "unicodeVersion": "8.0",
"digest": "27eaae0eb61f5e7b3cd9faf02c042d6643a368051a7c9d7da4e0fb9802d39242"
},
- {
- "name": "bicyclist_tone2",
- "unicode": "1F6B4-1F3FC",
+ "bicyclist_tone2": {
+ "category": "activity",
+ "moji": "🚴🏼",
+ "unicodeVersion": "8.0",
"digest": "39ee9e1071700da7079ad0146bf5711c3a222991eeca8b29b72a65677604444d"
},
- {
- "name": "bicyclist_tone3",
- "unicode": "1F6B4-1F3FD",
+ "bicyclist_tone3": {
+ "category": "activity",
+ "moji": "🚴🏽",
+ "unicodeVersion": "8.0",
"digest": "03e1d2c4232c896147a9d4bf43becd61edbb5c84fc7193ecea474c0f9fb36817"
},
- {
- "name": "bicyclist_tone4",
- "unicode": "1F6B4-1F3FE",
+ "bicyclist_tone4": {
+ "category": "activity",
+ "moji": "🚴🏾",
+ "unicodeVersion": "8.0",
"digest": "61393d9c4805be0379d86dd5bec9a1b02314433ab36cfd85bb48dfd073746617"
},
- {
- "name": "bicyclist_tone5",
- "unicode": "1F6B4-1F3FF",
+ "bicyclist_tone5": {
+ "category": "activity",
+ "moji": "🚴🏿",
+ "unicodeVersion": "8.0",
"digest": "2b46d5f8303e5710dbf5db3a4edc9d88a032fe123fe79158024c9f51df5458c6"
},
- {
- "name": "bike",
- "unicode": "1F6B2",
+ "bike": {
+ "category": "travel",
+ "moji": "🚲",
+ "unicodeVersion": "6.0",
"digest": "b41daa7c549d483e2336186a28baaa8ecb11986f490c0c54c793c44900c8f652"
},
- {
- "name": "bikini",
- "unicode": "1F459",
+ "bikini": {
+ "category": "people",
+ "moji": "👙",
+ "unicodeVersion": "6.0",
"digest": "07fe156f64673818d69ce3bf03950ca59e3b5d346e45ca541da4078ab791f5ae"
},
- {
- "name": "biohazard",
- "unicode": "2623",
- "digest": "96163e31f0b8dc5a59772133ede9cc2f40f94330d0b15e3d044b28747e2be788"
- },
- {
- "name": "biohazard_sign",
- "unicode": "2623",
+ "biohazard": {
+ "category": "symbols",
+ "moji": "☣",
+ "unicodeVersion": "1.1",
"digest": "96163e31f0b8dc5a59772133ede9cc2f40f94330d0b15e3d044b28747e2be788"
},
- {
- "name": "bird",
- "unicode": "1F426",
+ "bird": {
+ "category": "nature",
+ "moji": "🐦",
+ "unicodeVersion": "6.0",
"digest": "f916eaf8f271b3767ade9eabb69594c0479f45472d471cabaf59f6e965c161e0"
},
- {
- "name": "birthday",
- "unicode": "1F382",
+ "birthday": {
+ "category": "food",
+ "moji": "🎂",
+ "unicodeVersion": "6.0",
"digest": "89e7c4c598ebee8ec8ab11ebe4ccc6defb7c4d2987ee2379a19b3b59827dd98a"
},
- {
- "name": "black_circle",
- "unicode": "26AB",
+ "black_circle": {
+ "category": "symbols",
+ "moji": "⚫",
+ "unicodeVersion": "4.1",
"digest": "c2ba672994ad0f99d7fdc449f3fee45a2dca68a58f9fe95825b38465a30ef44e"
},
- {
- "name": "black_heart",
- "unicode": "1F5A4",
+ "black_heart": {
+ "category": "symbols",
+ "moji": "🖤",
+ "unicodeVersion": "9.0",
"digest": "f334679168d6dd7328c28e9ae3cb2b1fca0e9c2777938d586bfe623db2a688b9"
},
- {
- "name": "black_joker",
- "unicode": "1F0CF",
+ "black_joker": {
+ "category": "symbols",
+ "moji": "🃏",
+ "unicodeVersion": "6.0",
"digest": "d004b25f186494d5b2c65204caa9daecd749c840a0bea5718735e18109e5394d"
},
- {
- "name": "black_large_square",
- "unicode": "2B1B",
+ "black_large_square": {
+ "category": "symbols",
+ "moji": "⬛",
+ "unicodeVersion": "5.1",
"digest": "cbd90dcbc2f674eafa53820548b5263c18c9845ab39937f085e85aca0aebb479"
},
- {
- "name": "black_medium_small_square",
- "unicode": "25FE",
+ "black_medium_small_square": {
+ "category": "symbols",
+ "moji": "◾",
+ "unicodeVersion": "3.2",
"digest": "ab38363c2e862b8f67c719397a09a18e1ef996eec190691fdf769f5cfb209660"
},
- {
- "name": "black_medium_square",
- "unicode": "25FC",
+ "black_medium_square": {
+ "category": "symbols",
+ "moji": "◼",
+ "unicodeVersion": "3.2",
"digest": "c9ffa87c37e8ee65fadcf755176949901aec7367e02abb85e63cad60cd922116"
},
- {
- "name": "black_nib",
- "unicode": "2712",
+ "black_nib": {
+ "category": "objects",
+ "moji": "✒",
+ "unicodeVersion": "1.1",
"digest": "58fb23b1155102970eaa23765e7d529a21e8e545e076ec1158bf11b4de5f51a8"
},
- {
- "name": "black_small_square",
- "unicode": "25AA",
+ "black_small_square": {
+ "category": "symbols",
+ "moji": "▪",
+ "unicodeVersion": "1.1",
"digest": "f69be6de578fffce5a3e60eda690104b2ef6a855c630040104fb760a02ff1aef"
},
- {
- "name": "black_square_button",
- "unicode": "1F532",
+ "black_square_button": {
+ "category": "symbols",
+ "moji": "🔲",
+ "unicodeVersion": "6.0",
"digest": "9d818fcd08ed38cd0bbbcfd83e665aa29b3761c0d8b9806d8954d36785e267a8"
},
- {
- "name": "blossom",
- "unicode": "1F33C",
+ "blossom": {
+ "category": "nature",
+ "moji": "🌼",
+ "unicodeVersion": "6.0",
"digest": "e8cf369d4e4cdb4eccc2ebcbb35439b0344221115701daae642e58dff8544922"
},
- {
- "name": "blowfish",
- "unicode": "1F421",
+ "blowfish": {
+ "category": "nature",
+ "moji": "🐡",
+ "unicodeVersion": "6.0",
"digest": "e706849ed00f08a82312381c76f6f9ba6cc261fbf87a839c85e7dd54138f9dc3"
},
- {
- "name": "blue_book",
- "unicode": "1F4D8",
+ "blue_book": {
+ "category": "objects",
+ "moji": "📘",
+ "unicodeVersion": "6.0",
"digest": "4c845748fe890516b32981b0b62bf3e8e9d906840c2060179f4f844100780615"
},
- {
- "name": "blue_car",
- "unicode": "1F699",
+ "blue_car": {
+ "category": "travel",
+ "moji": "🚙",
+ "unicodeVersion": "6.0",
"digest": "eca91934eb5481726cfd897b1ed5eac306e14d02499fbe49316aaec6c72b6707"
},
- {
- "name": "blue_heart",
- "unicode": "1F499",
+ "blue_heart": {
+ "category": "symbols",
+ "moji": "💙",
+ "unicodeVersion": "6.0",
"digest": "2caa0c8d18538cc871c6fe328a52f71e1df8aabf4d1cc2f5324b261d1b8cb99a"
},
- {
- "name": "blush",
- "unicode": "1F60A",
+ "blush": {
+ "category": "people",
+ "moji": "😊",
+ "unicodeVersion": "6.0",
"digest": "3bfe8d603cfa39999c164779f666d39bbc507f124ba80233ee72da7b3b0c0457"
},
- {
- "name": "boar",
- "unicode": "1F417",
+ "boar": {
+ "category": "nature",
+ "moji": "🐗",
+ "unicodeVersion": "6.0",
"digest": "c9d67479cace427ac3c30460fcffa1bf9a8e5262c0390962405dbbe6bf830fa6"
},
- {
- "name": "bomb",
- "unicode": "1F4A3",
+ "bomb": {
+ "category": "objects",
+ "moji": "💣",
+ "unicodeVersion": "6.0",
"digest": "0155559abc4084f80e9b0b2a2091b8710ddd6369993b7fdd0685f4f8c2fd7e6c"
},
- {
- "name": "book",
- "unicode": "1F4D6",
+ "book": {
+ "category": "objects",
+ "moji": "📖",
+ "unicodeVersion": "6.0",
"digest": "9d912a9d1bb10dc7f2645b345ed09e90461e83df0de275acb806f1f75cef1fcf"
},
- {
- "name": "bookmark",
- "unicode": "1F516",
+ "bookmark": {
+ "category": "objects",
+ "moji": "🔖",
+ "unicodeVersion": "6.0",
"digest": "5705e3108259d6900649157843c50e22d0086c3630b291d3f942da1a736e3e3d"
},
- {
- "name": "bookmark_tabs",
- "unicode": "1F4D1",
+ "bookmark_tabs": {
+ "category": "objects",
+ "moji": "📑",
+ "unicodeVersion": "6.0",
"digest": "c8fc7c9f3f82e1ccc97fc591345fdd88b09eec0fca428d8d4632a121cf1bc39a"
},
- {
- "name": "books",
- "unicode": "1F4DA",
+ "books": {
+ "category": "objects",
+ "moji": "📚",
+ "unicodeVersion": "6.0",
"digest": "cbcf55d39dd05d26ef7350bc51e0e2f064f78bb8f59d407b516d63f68558f8e4"
},
- {
- "name": "boom",
- "unicode": "1F4A5",
+ "boom": {
+ "category": "nature",
+ "moji": "💥",
+ "unicodeVersion": "6.0",
"digest": "f5400e9583f7f997cd2385f21379f6229424a9b221445bc8f36c0bb64bdb3168"
},
- {
- "name": "boot",
- "unicode": "1F462",
+ "boot": {
+ "category": "people",
+ "moji": "👢",
+ "unicodeVersion": "6.0",
"digest": "b4706ff35909a6fb759a3b8a797e90cb67ffc60e4853386a7d89ace9693a9364"
},
- {
- "name": "bouquet",
- "unicode": "1F490",
+ "bouquet": {
+ "category": "nature",
+ "moji": "💐",
+ "unicodeVersion": "6.0",
"digest": "b93751a27b40f6185a22b3e8b413f0fe09b6010d1057c672e1a23088e0b8286f"
},
- {
- "name": "bow",
- "unicode": "1F647",
+ "bow": {
+ "category": "people",
+ "moji": "🙇",
+ "unicodeVersion": "6.0",
"digest": "33cd6da4d408f18d98bebc6a277dea8b914150e32ee472586ce3f1eb814462bd"
},
- {
- "name": "bow_and_arrow",
- "unicode": "1F3F9",
- "digest": "051b4d50ab21a68b8583a6313ec183e3e1e96f493b0f4541fbb888f0b95fdd4d"
- },
- {
- "name": "archery",
- "unicode": "1F3F9",
+ "bow_and_arrow": {
+ "category": "activity",
+ "moji": "🏹",
+ "unicodeVersion": "8.0",
"digest": "051b4d50ab21a68b8583a6313ec183e3e1e96f493b0f4541fbb888f0b95fdd4d"
},
- {
- "name": "bow_tone1",
- "unicode": "1F647-1F3FB",
+ "bow_tone1": {
+ "category": "people",
+ "moji": "🙇🏻",
+ "unicodeVersion": "8.0",
"digest": "995c8400ad60d5adc66c9ae5e3c0ecf56c48b478ad79418d45b6289933d25bdd"
},
- {
- "name": "bow_tone2",
- "unicode": "1F647-1F3FC",
+ "bow_tone2": {
+ "category": "people",
+ "moji": "🙇🏼",
+ "unicodeVersion": "8.0",
"digest": "af89eec2fccda99d9bdd373b2345595882fee1c0a15d29af9028089e20255325"
},
- {
- "name": "bow_tone3",
- "unicode": "1F647-1F3FD",
+ "bow_tone3": {
+ "category": "people",
+ "moji": "🙇🏽",
+ "unicodeVersion": "8.0",
"digest": "015d8122abdf2d0caa03815545f50fb7a71e05dacd46aaa133cc9ace5192f266"
},
- {
- "name": "bow_tone4",
- "unicode": "1F647-1F3FE",
+ "bow_tone4": {
+ "category": "people",
+ "moji": "🙇🏾",
+ "unicodeVersion": "8.0",
"digest": "e8409096a795b775def654d36aeccb8eb91e83d7d1b32145cd73fd0b7b9e885c"
},
- {
- "name": "bow_tone5",
- "unicode": "1F647-1F3FF",
+ "bow_tone5": {
+ "category": "people",
+ "moji": "🙇🏿",
+ "unicodeVersion": "8.0",
"digest": "d87042cde8dbad9fb1a91a2ec60116e27b4a76388b5779d771a0bbae12a2814d"
},
- {
- "name": "bowling",
- "unicode": "1F3B3",
+ "bowling": {
+ "category": "activity",
+ "moji": "🎳",
+ "unicodeVersion": "6.0",
"digest": "737f2cdfa4ac964baade585a39771b18080bd5e9b55c8661d3518f468f344662"
},
- {
- "name": "boxing_glove",
- "unicode": "1F94A",
+ "boxing_glove": {
+ "category": "activity",
+ "moji": "🥊",
+ "unicodeVersion": "9.0",
"digest": "c914b2ce45f20afad66ad6f0d1b0750c4469e4f48b686dfc4aad1ec8d289c563"
},
- {
- "name": "boxing_gloves",
- "unicode": "1F94A",
- "digest": "c914b2ce45f20afad66ad6f0d1b0750c4469e4f48b686dfc4aad1ec8d289c563"
- },
- {
- "name": "boy",
- "unicode": "1F466",
+ "boy": {
+ "category": "people",
+ "moji": "👦",
+ "unicodeVersion": "6.0",
"digest": "7bc0173d8c88f3f12d41f213f7a3a9f5ebf65efad610fd5a2a31935128a6a6c1"
},
- {
- "name": "boy_tone1",
- "unicode": "1F466-1F3FB",
+ "boy_tone1": {
+ "category": "people",
+ "moji": "👦🏻",
+ "unicodeVersion": "8.0",
"digest": "c0e2f0483715b239fe145b0056566f7a3a722319d9a87c1e66733dff1916a19f"
},
- {
- "name": "boy_tone2",
- "unicode": "1F466-1F3FC",
+ "boy_tone2": {
+ "category": "people",
+ "moji": "👦🏼",
+ "unicodeVersion": "8.0",
"digest": "0001d0bd1ff4dbd898604ba965b4039d09667d955bc0349301b992f9ab6dd7fd"
},
- {
- "name": "boy_tone3",
- "unicode": "1F466-1F3FD",
+ "boy_tone3": {
+ "category": "people",
+ "moji": "👦🏽",
+ "unicodeVersion": "8.0",
"digest": "e0f08755955fd2e0bd1c5d5e84429b2a234b24a744bb50bb9f1148495b2b29f9"
},
- {
- "name": "boy_tone4",
- "unicode": "1F466-1F3FE",
+ "boy_tone4": {
+ "category": "people",
+ "moji": "👦🏾",
+ "unicodeVersion": "8.0",
"digest": "04b6bfee58a26b1ce2e5b403504a7033aaf395f03f5cd23e824f32c90c395fe6"
},
- {
- "name": "boy_tone5",
- "unicode": "1F466-1F3FF",
+ "boy_tone5": {
+ "category": "people",
+ "moji": "👦🏿",
+ "unicodeVersion": "8.0",
"digest": "0f76e97237203950da36c737dcc6f56dcd6c123401a8c817a0636376c7f38ef5"
},
- {
- "name": "bread",
- "unicode": "1F35E",
+ "bread": {
+ "category": "food",
+ "moji": "🍞",
+ "unicodeVersion": "6.0",
"digest": "81739830f16f33e6a1dd7cc17c25df207846062bb5167bb8abed7fdd49268b86"
},
- {
- "name": "bride_with_veil",
- "unicode": "1F470",
+ "bride_with_veil": {
+ "category": "people",
+ "moji": "👰",
+ "unicodeVersion": "6.0",
"digest": "8e24bd91c3f564cf6148f2b3b4a7d692c11dd059e76a13331fdfb04ae060ea70"
},
- {
- "name": "bride_with_veil_tone1",
- "unicode": "1F470-1F3FB",
+ "bride_with_veil_tone1": {
+ "category": "people",
+ "moji": "👰🏻",
+ "unicodeVersion": "8.0",
"digest": "0bd2f16f72586f50e768b14b9b353f2e98ccbb2581a568c33b06be56e70ca063"
},
- {
- "name": "bride_with_veil_tone2",
- "unicode": "1F470-1F3FC",
+ "bride_with_veil_tone2": {
+ "category": "people",
+ "moji": "👰🏼",
+ "unicodeVersion": "8.0",
"digest": "e5463f811b2075754f0718b891757cd2e81071edf7af2215581227e1aad1d068"
},
- {
- "name": "bride_with_veil_tone3",
- "unicode": "1F470-1F3FD",
+ "bride_with_veil_tone3": {
+ "category": "people",
+ "moji": "👰🏽",
+ "unicodeVersion": "8.0",
"digest": "e5a053a26f7ccebae7eb12f638be5ed80f77b744708d783eab2eb8aa091cf516"
},
- {
- "name": "bride_with_veil_tone4",
- "unicode": "1F470-1F3FE",
+ "bride_with_veil_tone4": {
+ "category": "people",
+ "moji": "👰🏾",
+ "unicodeVersion": "8.0",
"digest": "410e23825e4401460946dc67a618bd3ace6e1a7c07dd88580a2349423685261f"
},
- {
- "name": "bride_with_veil_tone5",
- "unicode": "1F470-1F3FF",
+ "bride_with_veil_tone5": {
+ "category": "people",
+ "moji": "👰🏿",
+ "unicodeVersion": "8.0",
"digest": "454e87e5a74e13e5b4993541231516fbbe6dbe9f990e1a6f3f4a744d7d4c1615"
},
- {
- "name": "bridge_at_night",
- "unicode": "1F309",
+ "bridge_at_night": {
+ "category": "travel",
+ "moji": "🌉",
+ "unicodeVersion": "6.0",
"digest": "9d3cda5a59e27e3c90939f1ddbe7e998b3ea4fcacfa1467dea0edf39613c2d7f"
},
- {
- "name": "briefcase",
- "unicode": "1F4BC",
+ "briefcase": {
+ "category": "people",
+ "moji": "💼",
+ "unicodeVersion": "6.0",
"digest": "9d00d6a92632aaadc71b017f448c883b27eb31a7554ebb51f7e3a9841f0f7f2b"
},
- {
- "name": "broken_heart",
- "unicode": "1F494",
+ "broken_heart": {
+ "category": "symbols",
+ "moji": "💔",
+ "unicodeVersion": "6.0",
"digest": "c7ca53f444d72e596af46b61ffbc9e7c18a645020c22691e44f967db98dbf853"
},
- {
- "name": "bug",
- "unicode": "1F41B",
+ "bug": {
+ "category": "nature",
+ "moji": "🐛",
+ "unicodeVersion": "6.0",
"digest": "0dccb1d5eb91769377b4c5b310f007b60f54a5c48ba9e467b3a06898a4831b90"
},
- {
- "name": "bulb",
- "unicode": "1F4A1",
+ "bulb": {
+ "category": "objects",
+ "moji": "💡",
+ "unicodeVersion": "6.0",
"digest": "ccdaa2dfde5a88a347035a94b9d4d86cfc335ce0a73292423f5788a4bd21a5a8"
},
- {
- "name": "bullettrain_front",
- "unicode": "1F685",
+ "bullettrain_front": {
+ "category": "travel",
+ "moji": "🚅",
+ "unicodeVersion": "6.0",
"digest": "5195a6a6d23f28e1aa5ebac6ede0f6c6a8b7ff33a9edf034814f227fe976177a"
},
- {
- "name": "bullettrain_side",
- "unicode": "1F684",
+ "bullettrain_side": {
+ "category": "travel",
+ "moji": "🚄",
+ "unicodeVersion": "6.0",
"digest": "96e74842e919716b7bbbab57339bfd70f099a9bcb4710dffd7c80cf38a7bbff7"
},
- {
- "name": "burrito",
- "unicode": "1F32F",
+ "burrito": {
+ "category": "food",
+ "moji": "🌯",
+ "unicodeVersion": "8.0",
"digest": "b2cf81f1efdf87e674461f73f67cd4b58a5f695e65598d0dd3899f2597da43cf"
},
- {
- "name": "bus",
- "unicode": "1F68C",
+ "bus": {
+ "category": "travel",
+ "moji": "🚌",
+ "unicodeVersion": "6.0",
"digest": "192850b762edad21ac8770df38b9cae6d2bc1697a838462f3e36066bfb4eee50"
},
- {
- "name": "busstop",
- "unicode": "1F68F",
+ "busstop": {
+ "category": "travel",
+ "moji": "🚏",
+ "unicodeVersion": "6.0",
"digest": "adabb1ec36402b33feb636eae3656e5a8b51ff1071bcb14125d8ab80d6d12d2a"
},
- {
- "name": "bust_in_silhouette",
- "unicode": "1F464",
+ "bust_in_silhouette": {
+ "category": "people",
+ "moji": "👤",
+ "unicodeVersion": "6.0",
"digest": "277ae43301f1e49e0be03c8e52f0dc7b70c67f9d146bca0a14172e0098f115e6"
},
- {
- "name": "busts_in_silhouette",
- "unicode": "1F465",
+ "busts_in_silhouette": {
+ "category": "people",
+ "moji": "👥",
+ "unicodeVersion": "6.0",
"digest": "7fee96f1b68bb2c6002e47f2ed13c06baa6a3168441b9aca572db7ec45612f7b"
},
- {
- "name": "butterfly",
- "unicode": "1F98B",
+ "butterfly": {
+ "category": "nature",
+ "moji": "🦋",
+ "unicodeVersion": "9.0",
"digest": "a91b6598c17b44a8dc8935a1d99e25f4483ea41470cdd2da343039a9eec29ef1"
},
- {
- "name": "cactus",
- "unicode": "1F335",
+ "cactus": {
+ "category": "nature",
+ "moji": "🌵",
+ "unicodeVersion": "6.0",
"digest": "2c5c4c35f26c7046fdc002b337e0d939729b33a26980e675950f9934c91e40fd"
},
- {
- "name": "cake",
- "unicode": "1F370",
+ "cake": {
+ "category": "food",
+ "moji": "🍰",
+ "unicodeVersion": "6.0",
"digest": "b928902df8084210d51c1da36f9119164a325393c391b28cd8ea914e0b95c17b"
},
- {
- "name": "calendar",
- "unicode": "1F4C6",
+ "calendar": {
+ "category": "objects",
+ "moji": "📆",
+ "unicodeVersion": "6.0",
"digest": "9d990be27778daab041a3583edbd8f83fc8957e42a3aec729c0e2e224a8d05e3"
},
- {
- "name": "calendar_spiral",
- "unicode": "1F5D3",
- "digest": "441a0750eade7ce33e28e58bec76958990c412b68409fcdde59ebad1f25361bb"
- },
- {
- "name": "spiral_calendar_pad",
- "unicode": "1F5D3",
+ "calendar_spiral": {
+ "category": "objects",
+ "moji": "🗓",
+ "unicodeVersion": "7.0",
"digest": "441a0750eade7ce33e28e58bec76958990c412b68409fcdde59ebad1f25361bb"
},
- {
- "name": "call_me",
- "unicode": "1F919",
- "digest": "83d2ed96dcb8b4adf4f4d030ffd07e25ca16351e1a4fbefdf9f46f5ca496a55f"
- },
- {
- "name": "call_me_hand",
- "unicode": "1F919",
+ "call_me": {
+ "category": "people",
+ "moji": "🤙",
+ "unicodeVersion": "9.0",
"digest": "83d2ed96dcb8b4adf4f4d030ffd07e25ca16351e1a4fbefdf9f46f5ca496a55f"
},
- {
- "name": "call_me_tone1",
- "unicode": "1F919-1F3FB",
+ "call_me_tone1": {
+ "category": "people",
+ "moji": "🤙🏻",
+ "unicodeVersion": "9.0",
"digest": "4a5748efa83e7294e8338b8795d4d315ff1cd31ead6759004d0eb330e50de8cd"
},
- {
- "name": "call_me_hand_tone1",
- "unicode": "1F919-1F3FB",
- "digest": "4a5748efa83e7294e8338b8795d4d315ff1cd31ead6759004d0eb330e50de8cd"
- },
- {
- "name": "call_me_tone2",
- "unicode": "1F919-1F3FC",
- "digest": "54feaa6e3c5789ae6e15622127f0e0213234b4b886e1588ce95814348b1f1519"
- },
- {
- "name": "call_me_hand_tone2",
- "unicode": "1F919-1F3FC",
+ "call_me_tone2": {
+ "category": "people",
+ "moji": "🤙🏼",
+ "unicodeVersion": "9.0",
"digest": "54feaa6e3c5789ae6e15622127f0e0213234b4b886e1588ce95814348b1f1519"
},
- {
- "name": "call_me_tone3",
- "unicode": "1F919-1F3FD",
+ "call_me_tone3": {
+ "category": "people",
+ "moji": "🤙🏽",
+ "unicodeVersion": "9.0",
"digest": "57e949b951e14843b712dab5a828f915ee255f5bb973db33946aab4057427419"
},
- {
- "name": "call_me_hand_tone3",
- "unicode": "1F919-1F3FD",
- "digest": "57e949b951e14843b712dab5a828f915ee255f5bb973db33946aab4057427419"
- },
- {
- "name": "call_me_tone4",
- "unicode": "1F919-1F3FE",
- "digest": "f7787e933978a09c7b8ab8d3b1e1ab395aaae998c455e93bb3db24a4c8a60fe0"
- },
- {
- "name": "call_me_hand_tone4",
- "unicode": "1F919-1F3FE",
+ "call_me_tone4": {
+ "category": "people",
+ "moji": "🤙🏾",
+ "unicodeVersion": "9.0",
"digest": "f7787e933978a09c7b8ab8d3b1e1ab395aaae998c455e93bb3db24a4c8a60fe0"
},
- {
- "name": "call_me_tone5",
- "unicode": "1F919-1F3FF",
+ "call_me_tone5": {
+ "category": "people",
+ "moji": "🤙🏿",
+ "unicodeVersion": "9.0",
"digest": "1fdb7d833d000b117d20d48142d3026a61cc9c8b712ebb498fa66bf75c74d7a5"
},
- {
- "name": "call_me_hand_tone5",
- "unicode": "1F919-1F3FF",
- "digest": "1fdb7d833d000b117d20d48142d3026a61cc9c8b712ebb498fa66bf75c74d7a5"
- },
- {
- "name": "calling",
- "unicode": "1F4F2",
+ "calling": {
+ "category": "objects",
+ "moji": "📲",
+ "unicodeVersion": "6.0",
"digest": "acf668c75c11c36686005788266524a972fa1c5bcf666ff3403d909edc5cee91"
},
- {
- "name": "camel",
- "unicode": "1F42B",
+ "camel": {
+ "category": "nature",
+ "moji": "🐫",
+ "unicodeVersion": "6.0",
"digest": "5f927927a7ab1277d0dc8b8211436957968b1e11365a8bf535e9bb94f92c5631"
},
- {
- "name": "camera",
- "unicode": "1F4F7",
+ "camera": {
+ "category": "objects",
+ "moji": "📷",
+ "unicodeVersion": "6.0",
"digest": "fde03e396822a36cd6ae756ede885b945a074395264162731ca5db47a3b39d80"
},
- {
- "name": "camera_with_flash",
- "unicode": "1F4F8",
+ "camera_with_flash": {
+ "category": "objects",
+ "moji": "📸",
+ "unicodeVersion": "7.0",
"digest": "9afd380208187780f00244c45d4db6c5ea1ea088d4a1bd8fc92a8f3877149750"
},
- {
- "name": "camping",
- "unicode": "1F3D5",
+ "camping": {
+ "category": "travel",
+ "moji": "🏕",
+ "unicodeVersion": "7.0",
"digest": "a42a4ff9521affa72db7b0f01da169b4cb6afb9db1c5dfad47dd4c507bfc30d9"
},
- {
- "name": "cancer",
- "unicode": "264B",
+ "cancer": {
+ "category": "symbols",
+ "moji": "♋",
+ "unicodeVersion": "1.1",
"digest": "528c6f21df99a756b553d93a7f395b0f662b30a323affd05f0cedee8ff7b41d6"
},
- {
- "name": "candle",
- "unicode": "1F56F",
+ "candle": {
+ "category": "objects",
+ "moji": "🕯",
+ "unicodeVersion": "7.0",
"digest": "211c04dc3a91b071c284d4180ed09f9d3320e3fd6ba8a9fddd0677bc97fd12cb"
},
- {
- "name": "candy",
- "unicode": "1F36C",
+ "candy": {
+ "category": "food",
+ "moji": "🍬",
+ "unicodeVersion": "6.0",
"digest": "9cff4538918f60f770fceb96e964f5dc3ce31fd08ddd2ab3bfdf2981bfa74100"
},
- {
- "name": "canoe",
- "unicode": "1F6F6",
- "digest": "56ca308cc2ad4827468cf58c4ccf6ef6b3382835a91e935540a2b973e01d2572"
- },
- {
- "name": "kayak",
- "unicode": "1F6F6",
+ "canoe": {
+ "category": "travel",
+ "moji": "🛶",
+ "unicodeVersion": "9.0",
"digest": "56ca308cc2ad4827468cf58c4ccf6ef6b3382835a91e935540a2b973e01d2572"
},
- {
- "name": "capital_abcd",
- "unicode": "1F520",
+ "capital_abcd": {
+ "category": "symbols",
+ "moji": "🔠",
+ "unicodeVersion": "6.0",
"digest": "a416d0b3f564037b680f801fb773b6eaf67225e2cbbfd2cb8a5db0de044321fa"
},
- {
- "name": "capricorn",
- "unicode": "2651",
+ "capricorn": {
+ "category": "symbols",
+ "moji": "♑",
+ "unicodeVersion": "1.1",
"digest": "f11abad102603737b55486fe2ea4d01f28b203394bcd84f19a7948156e6c4b96"
},
- {
- "name": "card_box",
- "unicode": "1F5C3",
+ "card_box": {
+ "category": "objects",
+ "moji": "🗃",
+ "unicodeVersion": "7.0",
"digest": "7a6199d562f30e02ed31094de6aebeb99eae8ac156f6910463dfed73256f4c9a"
},
- {
- "name": "card_file_box",
- "unicode": "1F5C3",
- "digest": "7a6199d562f30e02ed31094de6aebeb99eae8ac156f6910463dfed73256f4c9a"
- },
- {
- "name": "card_index",
- "unicode": "1F4C7",
+ "card_index": {
+ "category": "objects",
+ "moji": "📇",
+ "unicodeVersion": "6.0",
"digest": "86e187e0a72ca5d00207d6ef34d66ce15046848a831c2b5184fb840c5332a2a8"
},
- {
- "name": "carousel_horse",
- "unicode": "1F3A0",
+ "carousel_horse": {
+ "category": "travel",
+ "moji": "🎠",
+ "unicodeVersion": "6.0",
"digest": "c0e7059efc39a64233f774c02ddb1ab51888fff180f906ce13a6e4f9509672fe"
},
- {
- "name": "carrot",
- "unicode": "1F955",
+ "carrot": {
+ "category": "food",
+ "moji": "🥕",
+ "unicodeVersion": "9.0",
"digest": "3a6fd98b63ee73d982a9cdacb08cf7b4014368cde8ffce6056b7df25a5a472b1"
},
- {
- "name": "cartwheel",
- "unicode": "1F938",
- "digest": "d78de3435e0b04a9b1a1048ae12e63e3248f9ace3a0db4d3bda584f22af18863"
- },
- {
- "name": "person_doing_cartwheel",
- "unicode": "1F938",
+ "cartwheel": {
+ "category": "activity",
+ "moji": "🤸",
+ "unicodeVersion": "9.0",
"digest": "d78de3435e0b04a9b1a1048ae12e63e3248f9ace3a0db4d3bda584f22af18863"
},
- {
- "name": "cartwheel_tone1",
- "unicode": "1F938-1F3FB",
+ "cartwheel_tone1": {
+ "category": "activity",
+ "moji": "🤸🏻",
+ "unicodeVersion": "9.0",
"digest": "39a49781a269bb40d8efc8fd73c973b00fb2e192850ea6073062b5dea0cd5b74"
},
- {
- "name": "person_doing_cartwheel_tone1",
- "unicode": "1F938-1F3FB",
- "digest": "39a49781a269bb40d8efc8fd73c973b00fb2e192850ea6073062b5dea0cd5b74"
- },
- {
- "name": "cartwheel_tone2",
- "unicode": "1F938-1F3FC",
- "digest": "6231eb35be45457fd648f8f4b79983f03705c9d983a18067f7e6d9ae47bc1958"
- },
- {
- "name": "person_doing_cartwheel_tone2",
- "unicode": "1F938-1F3FC",
+ "cartwheel_tone2": {
+ "category": "activity",
+ "moji": "🤸🏼",
+ "unicodeVersion": "9.0",
"digest": "6231eb35be45457fd648f8f4b79983f03705c9d983a18067f7e6d9ae47bc1958"
},
- {
- "name": "cartwheel_tone3",
- "unicode": "1F938-1F3FD",
+ "cartwheel_tone3": {
+ "category": "activity",
+ "moji": "🤸🏽",
+ "unicodeVersion": "9.0",
"digest": "ca483c78cc823811a8c279c501d9b283e4c990dafc5995ad40e68ecb0af554df"
},
- {
- "name": "person_doing_cartwheel_tone3",
- "unicode": "1F938-1F3FD",
- "digest": "ca483c78cc823811a8c279c501d9b283e4c990dafc5995ad40e68ecb0af554df"
- },
- {
- "name": "cartwheel_tone4",
- "unicode": "1F938-1F3FE",
- "digest": "8253afb672431c84e498014c30babb00b9284bec773009e79f7f06aa7108643e"
- },
- {
- "name": "person_doing_cartwheel_tone4",
- "unicode": "1F938-1F3FE",
+ "cartwheel_tone4": {
+ "category": "activity",
+ "moji": "🤸🏾,",
+ "unicodeVersion": "9.0",
"digest": "8253afb672431c84e498014c30babb00b9284bec773009e79f7f06aa7108643e"
},
- {
- "name": "cartwheel_tone5",
- "unicode": "1F938-1F3FF",
+ "cartwheel_tone5": {
+ "category": "activity",
+ "moji": "🤸🏿",
+ "unicodeVersion": "9.0",
"digest": "6fd92baff57c38b3adb6753d9e7e547e762971a8872fd3f1e71c6aaf0b1d3ab9"
},
- {
- "name": "person_doing_cartwheel_tone5",
- "unicode": "1F938-1F3FF",
- "digest": "6fd92baff57c38b3adb6753d9e7e547e762971a8872fd3f1e71c6aaf0b1d3ab9"
- },
- {
- "name": "cat",
- "unicode": "1F431",
+ "cat": {
+ "category": "nature",
+ "moji": "🐱",
+ "unicodeVersion": "6.0",
"digest": "e52d0d3a205a0ba99094717e171a7f572b713a0e21b276ffa4a826596fe5cafc"
},
- {
- "name": "cat2",
- "unicode": "1F408",
+ "cat2": {
+ "category": "nature",
+ "moji": "🐈",
+ "unicodeVersion": "6.0",
"digest": "46aa67a99f782935932c77b8de93287142297abe52928c173191cf55bb8f4339"
},
- {
- "name": "cd",
- "unicode": "1F4BF",
+ "cd": {
+ "category": "objects",
+ "moji": "💿",
+ "unicodeVersion": "6.0",
"digest": "16363d8a34b873c12df6354b99f575cae3d80e0d27100ed7eea70f0310953c7b"
},
- {
- "name": "chains",
- "unicode": "26D3",
+ "chains": {
+ "category": "objects",
+ "moji": "⛓",
+ "unicodeVersion": "5.2",
"digest": "3884cdbc6f2b433062af06f942552e563231c24727a2f10fa280b3bb7aa614e2"
},
- {
- "name": "champagne",
- "unicode": "1F37E",
- "digest": "9e6e8987f30a37ae0f3d7dab2f5eeb50aa32b4f31402b29315eb2994afc72457"
- },
- {
- "name": "bottle_with_popping_cork",
- "unicode": "1F37E",
+ "champagne": {
+ "category": "food",
+ "moji": "🍾",
+ "unicodeVersion": "8.0",
"digest": "9e6e8987f30a37ae0f3d7dab2f5eeb50aa32b4f31402b29315eb2994afc72457"
},
- {
- "name": "champagne_glass",
- "unicode": "1F942",
+ "champagne_glass": {
+ "category": "food",
+ "moji": "🥂",
+ "unicodeVersion": "9.0",
"digest": "5a2e4773f7eb126a00122cbfa4dc535da51ce00e0bf0d8d6ff8bab8b3365f8d2"
},
- {
- "name": "clinking_glass",
- "unicode": "1F942",
- "digest": "5a2e4773f7eb126a00122cbfa4dc535da51ce00e0bf0d8d6ff8bab8b3365f8d2"
- },
- {
- "name": "chart",
- "unicode": "1F4B9",
+ "chart": {
+ "category": "symbols",
+ "moji": "💹",
+ "unicodeVersion": "6.0",
"digest": "a092dbc08f925b028286b2b495a5f59033b8537a586a694f46f4c1e7c3a1e27f"
},
- {
- "name": "chart_with_downwards_trend",
- "unicode": "1F4C9",
+ "chart_with_downwards_trend": {
+ "category": "objects",
+ "moji": "📉",
+ "unicodeVersion": "6.0",
"digest": "5db7ccbc37665736a9c0b2f50247dcc09e404ec37f39db45b7b8b9464172a18c"
},
- {
- "name": "chart_with_upwards_trend",
- "unicode": "1F4C8",
+ "chart_with_upwards_trend": {
+ "category": "objects",
+ "moji": "📈",
+ "unicodeVersion": "6.0",
"digest": "bc4ea250b102fe5c09847e471478aff065ad3df755d9717896d38d887d9c6733"
},
- {
- "name": "checkered_flag",
- "unicode": "1F3C1",
+ "checkered_flag": {
+ "category": "travel",
+ "moji": "🏁",
+ "unicodeVersion": "6.0",
"digest": "0e77180e0cf9fc87e755a5a42cf23aec6bf30931db41331311e97ba0be178b78"
},
- {
- "name": "cheese",
- "unicode": "1F9C0",
+ "cheese": {
+ "category": "food",
+ "moji": "🧀",
+ "unicodeVersion": "8.0",
"digest": "50a6cb906c2120e2bbc0e22105924262007cfe1554d7b02b8cc84b6adedc6a0b"
},
- {
- "name": "cheese_wedge",
- "unicode": "1F9C0",
- "digest": "50a6cb906c2120e2bbc0e22105924262007cfe1554d7b02b8cc84b6adedc6a0b"
- },
- {
- "name": "cherries",
- "unicode": "1F352",
+ "cherries": {
+ "category": "food",
+ "moji": "🍒",
+ "unicodeVersion": "6.0",
"digest": "13b8db9e7e6eec8509aa80c762966e1bf3538fcb1ac3d6eab18ee4da1528cf84"
},
- {
- "name": "cherry_blossom",
- "unicode": "1F338",
+ "cherry_blossom": {
+ "category": "nature",
+ "moji": "🌸",
+ "unicodeVersion": "6.0",
"digest": "af3083f5f8dd94936113f2e16caba5aec7a774d5589aa08bf5de82a2d278cc66"
},
- {
- "name": "chestnut",
- "unicode": "1F330",
+ "chestnut": {
+ "category": "nature",
+ "moji": "🌰",
+ "unicodeVersion": "6.0",
"digest": "9f85b79b207a69ab81ab88dcef04954000965b039b4cf57de5f1b381745ab98b"
},
- {
- "name": "chicken",
- "unicode": "1F414",
+ "chicken": {
+ "category": "nature",
+ "moji": "🐔",
+ "unicodeVersion": "6.0",
"digest": "57ceb4459d183740009caac6ebed089d2f1e12f67c138e1be1d0f992313c0ac4"
},
- {
- "name": "children_crossing",
- "unicode": "1F6B8",
+ "children_crossing": {
+ "category": "symbols",
+ "moji": "🚸",
+ "unicodeVersion": "6.0",
"digest": "0ded7d9aca0161e8ef8e2858c3c198e70e4badc7105ac3a6886e06975de19106"
},
- {
- "name": "chipmunk",
- "unicode": "1F43F",
+ "chipmunk": {
+ "category": "nature",
+ "moji": "🐿",
+ "unicodeVersion": "7.0",
"digest": "5b0dc1a859163097727ba2ba5ffca38b0a54d925eebb089977d28d0b4d917a3f"
},
- {
- "name": "chocolate_bar",
- "unicode": "1F36B",
+ "chocolate_bar": {
+ "category": "food",
+ "moji": "🍫",
+ "unicodeVersion": "6.0",
"digest": "dd273e5050488acaf885f8a18b6e2b3901f69c5b39fa6465fb60621783d4109a"
},
- {
- "name": "christmas_tree",
- "unicode": "1F384",
+ "christmas_tree": {
+ "category": "nature",
+ "moji": "🎄",
+ "unicodeVersion": "6.0",
"digest": "ce60cbe2ebbe8057be8edea2392455fedd2bcda64a0a831f6a1942028af7e747"
},
- {
- "name": "church",
- "unicode": "26EA",
+ "church": {
+ "category": "travel",
+ "moji": "⛪",
+ "unicodeVersion": "5.2",
"digest": "2c328456528f7336e59443e20ec3ab22fe71f1fccb1dd50d0ad68eb206937557"
},
- {
- "name": "cinema",
- "unicode": "1F3A6",
+ "cinema": {
+ "category": "symbols",
+ "moji": "🎦",
+ "unicodeVersion": "6.0",
"digest": "4c26dcdc76f93dbc2a1dc49ed4e132b8e8f2b7cdc1acf5e09b3dfd99430d97cd"
},
- {
- "name": "circus_tent",
- "unicode": "1F3AA",
+ "circus_tent": {
+ "category": "activity",
+ "moji": "🎪",
+ "unicodeVersion": "6.0",
"digest": "fec5f2a06222be8be549178b29720343cc00145177ec387ca4e6f3432481fe77"
},
- {
- "name": "city_dusk",
- "unicode": "1F306",
+ "city_dusk": {
+ "category": "travel",
+ "moji": "🌆",
+ "unicodeVersion": "6.0",
"digest": "bba345e949dcc51f5f018220f000223797970c82ead2ab9c822f9dc0847aa155"
},
- {
- "name": "city_sunset",
- "unicode": "1F307",
+ "city_sunset": {
+ "category": "travel",
+ "moji": "🌇",
+ "unicodeVersion": "6.0",
"digest": "a846df1a4c7c778f8e1729804aece86eb29d2fcb95dc39eaaf2aae1897f3dcc7"
},
- {
- "name": "city_sunrise",
- "unicode": "1F307",
- "digest": "a846df1a4c7c778f8e1729804aece86eb29d2fcb95dc39eaaf2aae1897f3dcc7"
- },
- {
- "name": "cityscape",
- "unicode": "1F3D9",
+ "cityscape": {
+ "category": "travel",
+ "moji": "🏙",
+ "unicodeVersion": "7.0",
"digest": "ee360be7514c4bfb0d539dd28f3b2031ebcef04e850723ec0685fb54bd8e6d5f"
},
- {
- "name": "cl",
- "unicode": "1F191",
+ "cl": {
+ "category": "symbols",
+ "moji": "🆑",
+ "unicodeVersion": "6.0",
"digest": "fcec2855dbad9fda11d6e2802bc0dcaabab0b5be233508f5e439f156f07602c1"
},
- {
- "name": "clap",
- "unicode": "1F44F",
+ "clap": {
+ "category": "people",
+ "moji": "👏",
+ "unicodeVersion": "6.0",
"digest": "a1860ce7812a9f6fb55e45761e1b79a2f8f0620eb04f80748a38420889d58a2a"
},
- {
- "name": "clap_tone1",
- "unicode": "1F44F-1F3FB",
+ "clap_tone1": {
+ "category": "people",
+ "moji": "👏🏻",
+ "unicodeVersion": "8.0",
"digest": "18a7022e08223fb2109af5a9b9a5b4f47dc870ce4453f4987d2d0b729ef54586"
},
- {
- "name": "clap_tone2",
- "unicode": "1F44F-1F3FC",
+ "clap_tone2": {
+ "category": "people",
+ "moji": "👏🏼",
+ "unicodeVersion": "8.0",
"digest": "5954c8658b15e755d2018d8674df84d38e22ffededc4d726c6a33b709f71426a"
},
- {
- "name": "clap_tone3",
- "unicode": "1F44F-1F3FD",
+ "clap_tone3": {
+ "category": "people",
+ "moji": "👏🏽",
+ "unicodeVersion": "8.0",
"digest": "22639b6bd3c53784a2f855d6db7bdf31621519f19dfc29a6bc310eee6421f742"
},
- {
- "name": "clap_tone4",
- "unicode": "1F44F-1F3FE",
+ "clap_tone4": {
+ "category": "people",
+ "moji": "👏🏾",
+ "unicodeVersion": "8.0",
"digest": "e55248dc163d1bbd118b50cd8767750ead86d082151febbc0a75b32d63abceec"
},
- {
- "name": "clap_tone5",
- "unicode": "1F44F-1F3FF",
+ "clap_tone5": {
+ "category": "people",
+ "moji": "👏🏿",
+ "unicodeVersion": "8.0",
"digest": "76046b8157dabbe048a07fc318122456020c9c980fc1b8ab76802330e07b3b53"
},
- {
- "name": "clapper",
- "unicode": "1F3AC",
+ "clapper": {
+ "category": "activity",
+ "moji": "🎬",
+ "unicodeVersion": "6.0",
"digest": "8149752a0e3e8abede2d433d1afab6d217877d0c76adb1e2845a0142c0cdcbaa"
},
- {
- "name": "classical_building",
- "unicode": "1F3DB",
+ "classical_building": {
+ "category": "travel",
+ "moji": "🏛",
+ "unicodeVersion": "7.0",
"digest": "9ee0d00c43d6e22b6a3ddea67619737270cc7e9294797a19c7c60d5f92aa44fa"
},
- {
- "name": "clipboard",
- "unicode": "1F4CB",
+ "clipboard": {
+ "category": "objects",
+ "moji": "📋",
+ "unicodeVersion": "6.0",
"digest": "bdd7f7d973c714e59d2903d401a876e6018794c7987c9ca57108c137c5edc25f"
},
- {
- "name": "clock",
- "unicode": "1F570",
- "digest": "302835eab2637db799acf69b3d795571ef3432251267050db0704f2954e8b190"
- },
- {
- "name": "mantlepiece_clock",
- "unicode": "1F570",
+ "clock": {
+ "category": "objects",
+ "moji": "🕰",
+ "unicodeVersion": "7.0",
"digest": "302835eab2637db799acf69b3d795571ef3432251267050db0704f2954e8b190"
},
- {
- "name": "clock1",
- "unicode": "1F550",
+ "clock1": {
+ "category": "symbols",
+ "moji": "🕐",
+ "unicodeVersion": "6.0",
"digest": "1778eec07ce061c9393e5abee5ca83b24e1ce61d8a75fa2e39efcb31aa160395"
},
- {
- "name": "clock10",
- "unicode": "1F559",
+ "clock10": {
+ "category": "symbols",
+ "moji": "🕙",
+ "unicodeVersion": "6.0",
"digest": "601fc12ea5280a54c2e69dbb685f454e4165fe771756ed6f89016e29e683a24f"
},
- {
- "name": "clock1030",
- "unicode": "1F565",
+ "clock1030": {
+ "category": "symbols",
+ "moji": "🕥",
+ "unicodeVersion": "6.0",
"digest": "4fd155f08f797542d52cff4b0aa3ca9f080f37a41c301b82f90ff6d4693c890e"
},
- {
- "name": "clock11",
- "unicode": "1F55A",
+ "clock11": {
+ "category": "symbols",
+ "moji": "🕚",
+ "unicodeVersion": "6.0",
"digest": "5c79dc812e812e8a01993ea633b323d654ce3a7ea258692781a4896e4ad2017e"
},
- {
- "name": "clock1130",
- "unicode": "1F566",
+ "clock1130": {
+ "category": "symbols",
+ "moji": "🕦",
+ "unicodeVersion": "6.0",
"digest": "41497ee2020ee5ac9aa5f9b07560f7afca7c422b04214449cfc5cea9f020f52e"
},
- {
- "name": "clock12",
- "unicode": "1F55B",
+ "clock12": {
+ "category": "symbols",
+ "moji": "🕛",
+ "unicodeVersion": "6.0",
"digest": "046bb7ffa5f5d27c2e3411ba543484d9dabb8ebf6d6e7a7e9bfb088c1813500c"
},
- {
- "name": "clock1230",
- "unicode": "1F567",
+ "clock1230": {
+ "category": "symbols",
+ "moji": "🕧",
+ "unicodeVersion": "6.0",
"digest": "bbfe9db5a2043aaba19a7a2a0185c7efcebf1e8c9263b8233f75b53c4825f0f4"
},
- {
- "name": "clock130",
- "unicode": "1F55C",
+ "clock130": {
+ "category": "symbols",
+ "moji": "🕜",
+ "unicodeVersion": "6.0",
"digest": "8662cb395ee680c2781123305c4c8ce8c0df9565c2c942668940be540cc0c094"
},
- {
- "name": "clock2",
- "unicode": "1F551",
+ "clock2": {
+ "category": "symbols",
+ "moji": "🕑",
+ "unicodeVersion": "6.0",
"digest": "42f7429748b612dce7de77221cbbc710655811f7bb23e2a986c36e6d662f0ec4"
},
- {
- "name": "clock230",
- "unicode": "1F55D",
+ "clock230": {
+ "category": "symbols",
+ "moji": "🕝",
+ "unicodeVersion": "6.0",
"digest": "e710b6ef14227cd240ea3e2a867c8ef45b5c060adf3cb30ba9077c2351fe6677"
},
- {
- "name": "clock3",
- "unicode": "1F552",
+ "clock3": {
+ "category": "symbols",
+ "moji": "🕒",
+ "unicodeVersion": "6.0",
"digest": "7340d465b398a378211dff9ec806db579d061206fd6fc238623d070cfe0a55ce"
},
- {
- "name": "clock330",
- "unicode": "1F55E",
+ "clock330": {
+ "category": "symbols",
+ "moji": "🕞",
+ "unicodeVersion": "6.0",
"digest": "7aa4a15cc8de04ed3bdeb0f8a54a7915065f2809a07054e002d89926c9766831"
},
- {
- "name": "clock4",
- "unicode": "1F553",
+ "clock4": {
+ "category": "symbols",
+ "moji": "🕓",
+ "unicodeVersion": "6.0",
"digest": "36fd88e81ad488b0ec49a911a838693281573fa14736ae4a6dd1c40a4ff69bb1"
},
- {
- "name": "clock430",
- "unicode": "1F55F",
+ "clock430": {
+ "category": "symbols",
+ "moji": "🕟",
+ "unicodeVersion": "6.0",
"digest": "7bd5dd71e89d95dcf18b9e8c1fe2a353a7da3b69aadb8dda80ee9bafb05da58d"
},
- {
- "name": "clock5",
- "unicode": "1F554",
+ "clock5": {
+ "category": "symbols",
+ "moji": "🕔",
+ "unicodeVersion": "6.0",
"digest": "aa406409e56a0bfd8c850e44efe45fd190ffd7bf7061e934ed7928dfbdfc9eba"
},
- {
- "name": "clock530",
- "unicode": "1F560",
+ "clock530": {
+ "category": "symbols",
+ "moji": "🕠",
+ "unicodeVersion": "6.0",
"digest": "25dd3bcc53ddd98eeea498d7dbd4c306ef39dd033f15909063388a0800febf41"
},
- {
- "name": "clock6",
- "unicode": "1F555",
+ "clock6": {
+ "category": "symbols",
+ "moji": "🕕",
+ "unicodeVersion": "6.0",
"digest": "0a321eaf1bc5db8436bbadac66c45ba257fc98ad4c7569ce3fc6602c824b6d7c"
},
- {
- "name": "clock630",
- "unicode": "1F561",
+ "clock630": {
+ "category": "symbols",
+ "moji": "🕡",
+ "unicodeVersion": "6.0",
"digest": "55a4c5a665fdd38a724e9357a93c55401fcd5f1b13078c25754bd70c3fc4ccec"
},
- {
- "name": "clock7",
- "unicode": "1F556",
+ "clock7": {
+ "category": "symbols",
+ "moji": "🕖",
+ "unicodeVersion": "6.0",
"digest": "6154306545716e865da0ec537ee4f22bfe6c7294502a64a2dcf425c587d0e2a2"
},
- {
- "name": "clock730",
- "unicode": "1F562",
+ "clock730": {
+ "category": "symbols",
+ "moji": "🕢",
+ "unicodeVersion": "6.0",
"digest": "6925654de642e50f84661f94364a96c87757d73fffe766aacbf4bbd70130547b"
},
- {
- "name": "clock8",
- "unicode": "1F557",
+ "clock8": {
+ "category": "symbols",
+ "moji": "🕗",
+ "unicodeVersion": "6.0",
"digest": "9be2d189c7ea56d39fd259f84853d753c1cf33e64f8ed57f86f822d9ae23a1ee"
},
- {
- "name": "clock830",
- "unicode": "1F563",
+ "clock830": {
+ "category": "symbols",
+ "moji": "🕣",
+ "unicodeVersion": "6.0",
"digest": "16878613c0000d2f558c88d080551f424a8bd9df1358e0f931dd25c3da68f2d9"
},
- {
- "name": "clock9",
- "unicode": "1F558",
+ "clock9": {
+ "category": "symbols",
+ "moji": "🕘",
+ "unicodeVersion": "6.0",
"digest": "1d1e7e3c9d085ffa5b7c0f3d9fd394b734f16ae3b60df09af50fe6c8d4f3c8bb"
},
- {
- "name": "clock930",
- "unicode": "1F564",
+ "clock930": {
+ "category": "symbols",
+ "moji": "🕤",
+ "unicodeVersion": "6.0",
"digest": "9fdef6a4939315c017b165e1dbac7710fb335df8c309be3fe2a011ef7fc28d74"
},
- {
- "name": "closed_book",
- "unicode": "1F4D5",
+ "closed_book": {
+ "category": "objects",
+ "moji": "📕",
+ "unicodeVersion": "6.0",
"digest": "b18288629d201bfdfc5d66ec47df89809d00642b15732757e6a04789f36a7d9f"
},
- {
- "name": "closed_lock_with_key",
- "unicode": "1F510",
+ "closed_lock_with_key": {
+ "category": "objects",
+ "moji": "🔐",
+ "unicodeVersion": "6.0",
"digest": "e39adfe9b30973bca16472c2b7e6462b064a93b9d452aa48edd74c727641a83d"
},
- {
- "name": "closed_umbrella",
- "unicode": "1F302",
+ "closed_umbrella": {
+ "category": "people",
+ "moji": "🌂",
+ "unicodeVersion": "6.0",
"digest": "2cc0592c74601f7439e88c3c1ec4f05e3459608ef1ea6558c5824ed7c3889727"
},
- {
- "name": "cloud",
- "unicode": "2601",
+ "cloud": {
+ "category": "nature",
+ "moji": "☁",
+ "unicodeVersion": "1.1",
"digest": "5b3a19718dfa8a381929665afdc2284464d24020c8dd0caff4dad465a1f536ba"
},
- {
- "name": "cloud_lightning",
- "unicode": "1F329",
+ "cloud_lightning": {
+ "category": "nature",
+ "moji": "🌩",
+ "unicodeVersion": "7.0",
"digest": "2b32f6d87726df2935ad81870879ccec30ce9b4fd5861d1a6317f9eca2f013d9"
},
- {
- "name": "cloud_with_lightning",
- "unicode": "1F329",
- "digest": "2b32f6d87726df2935ad81870879ccec30ce9b4fd5861d1a6317f9eca2f013d9"
- },
- {
- "name": "cloud_rain",
- "unicode": "1F327",
- "digest": "1e1e8bc59e168e1d2e72bf11f2d43cb578cbf0a5f1daf383bba5c56fb750ee71"
- },
- {
- "name": "cloud_with_rain",
- "unicode": "1F327",
+ "cloud_rain": {
+ "category": "nature",
+ "moji": "🌧",
+ "unicodeVersion": "7.0",
"digest": "1e1e8bc59e168e1d2e72bf11f2d43cb578cbf0a5f1daf383bba5c56fb750ee71"
},
- {
- "name": "cloud_snow",
- "unicode": "1F328",
- "digest": "2d364f859b83e684213e8eece1640208d80a8de0a49d0fc8e0e24c5a8493a3b1"
- },
- {
- "name": "cloud_with_snow",
- "unicode": "1F328",
+ "cloud_snow": {
+ "category": "nature",
+ "moji": "🌨",
+ "unicodeVersion": "7.0",
"digest": "2d364f859b83e684213e8eece1640208d80a8de0a49d0fc8e0e24c5a8493a3b1"
},
- {
- "name": "cloud_tornado",
- "unicode": "1F32A",
- "digest": "7cbed2343c280ba3996082b3d0fb9d8cd57d6e62fe6c9ecb159f46b4a2e49151"
- },
- {
- "name": "cloud_with_tornado",
- "unicode": "1F32A",
+ "cloud_tornado": {
+ "category": "nature",
+ "moji": "🌪",
+ "unicodeVersion": "7.0",
"digest": "7cbed2343c280ba3996082b3d0fb9d8cd57d6e62fe6c9ecb159f46b4a2e49151"
},
- {
- "name": "clown",
- "unicode": "1F921",
- "digest": "eea95687caabc9e808514c2450ba599e5e24ef47923dbec86f5297a64438e2e5"
- },
- {
- "name": "clown_face",
- "unicode": "1F921",
+ "clown": {
+ "category": "people",
+ "moji": "🤡",
+ "unicodeVersion": "9.0",
"digest": "eea95687caabc9e808514c2450ba599e5e24ef47923dbec86f5297a64438e2e5"
},
- {
- "name": "clubs",
- "unicode": "2663",
+ "clubs": {
+ "category": "symbols",
+ "moji": "♣",
+ "unicodeVersion": "1.1",
"digest": "b8cf72ecd8568ced077b475d94788fb282bdb06d25031b5d54dd63e25effb138"
},
- {
- "name": "cocktail",
- "unicode": "1F378",
+ "cocktail": {
+ "category": "food",
+ "moji": "🍸",
+ "unicodeVersion": "6.0",
"digest": "3792def2cde885cf32167f04904d3b0b788388e8af410c63e4cd31550feba775"
},
- {
- "name": "coffee",
- "unicode": "2615",
+ "coffee": {
+ "category": "food",
+ "moji": "☕",
+ "unicodeVersion": "4.0",
"digest": "0d29615a7a67d3aafa257b909bb915dc74fa8f854acb0d9a2c29e94eedf80326"
},
- {
- "name": "coffin",
- "unicode": "26B0",
+ "coffin": {
+ "category": "objects",
+ "moji": "⚰",
+ "unicodeVersion": "4.1",
"digest": "78eccc1aad2a822649fba8503d4d30354bef367c4271193c40ddb692308f9db8"
},
- {
- "name": "cold_sweat",
- "unicode": "1F630",
+ "cold_sweat": {
+ "category": "people",
+ "moji": "😰",
+ "unicodeVersion": "6.0",
"digest": "f53aab523ed3fa2224a16881d263fb5e039f163380f92feb2c63c20f9b14dcd2"
},
- {
- "name": "comet",
- "unicode": "2604",
+ "comet": {
+ "category": "nature",
+ "moji": "☄",
+ "unicodeVersion": "1.1",
"digest": "40ce93e55c6e57a88d80670b37171190bd5ffc87b7078891d8de5b15795385c5"
},
- {
- "name": "compression",
- "unicode": "1F5DC",
+ "compression": {
+ "category": "objects",
+ "moji": "🗜",
+ "unicodeVersion": "7.0",
"digest": "c8841f7afb5345f1c31da116a7fb41d07232ea58d3f7f1a75c5890aa1a80bfd6"
},
- {
- "name": "computer",
- "unicode": "1F4BB",
+ "computer": {
+ "category": "objects",
+ "moji": "💻",
+ "unicodeVersion": "6.0",
"digest": "c970ce76b5607434895b0407bdaa93140f887930781a17dd7dcf16f711451d93"
},
- {
- "name": "confetti_ball",
- "unicode": "1F38A",
+ "confetti_ball": {
+ "category": "objects",
+ "moji": "🎊",
+ "unicodeVersion": "6.0",
"digest": "a638b16f1acdbcf69edf760161b1bd7ff1fd5426c5b1203ad9d294dcc0701f10"
},
- {
- "name": "confounded",
- "unicode": "1F616",
+ "confounded": {
+ "category": "people",
+ "moji": "😖",
+ "unicodeVersion": "6.0",
"digest": "e2ff3b4df65d00c1ca9ae0cb379f959ea2cecefb3d676d4f8c2c5f2c103da4f6"
},
- {
- "name": "confused",
- "unicode": "1F615",
+ "confused": {
+ "category": "people",
+ "moji": "😕",
+ "unicodeVersion": "6.1",
"digest": "118d7f830ec08a3ac4b798eebb77a989b8c142f2588727181be4a2548e3c4f06"
},
- {
- "name": "congratulations",
- "unicode": "3297",
+ "congratulations": {
+ "category": "symbols",
+ "moji": "㊗",
+ "unicodeVersion": "1.1",
"digest": "02fd1338c54fe5f9a0fd861f23c56edc1d39bcd3140b68f0f626f9e2494d2d1c"
},
- {
- "name": "construction",
- "unicode": "1F6A7",
+ "construction": {
+ "category": "travel",
+ "moji": "🚧",
+ "unicodeVersion": "6.0",
"digest": "c3a0401331111b9eda1206bee5f322db80b0870547d307b10dcac1314e4078c8"
},
- {
- "name": "construction_site",
- "unicode": "1F3D7",
- "digest": "c611f0a5de10f000a0756935f226845c7292f19ff5581d1f7a7554316338bbcb"
- },
- {
- "name": "building_construction",
- "unicode": "1F3D7",
+ "construction_site": {
+ "category": "travel",
+ "moji": "🏗",
+ "unicodeVersion": "7.0",
"digest": "c611f0a5de10f000a0756935f226845c7292f19ff5581d1f7a7554316338bbcb"
},
- {
- "name": "construction_worker",
- "unicode": "1F477",
+ "construction_worker": {
+ "category": "people",
+ "moji": "👷",
+ "unicodeVersion": "6.0",
"digest": "8c094733987e7c4da8d3aa4588b530ae07042bd70cf337b1fd412a70ee8f0ed6"
},
- {
- "name": "construction_worker_tone1",
- "unicode": "1F477-1F3FB",
+ "construction_worker_tone1": {
+ "category": "people",
+ "moji": "👷🏻",
+ "unicodeVersion": "8.0",
"digest": "fcd927405fef4486105cd3aff62155467d21cebbc013924d4b52b717b566602b"
},
- {
- "name": "construction_worker_tone2",
- "unicode": "1F477-1F3FC",
+ "construction_worker_tone2": {
+ "category": "people",
+ "moji": "👷🏼",
+ "unicodeVersion": "8.0",
"digest": "d1ec773828936c703dd6e334e696dc3cf7c34c0a8ec691564a384b735cdeaaba"
},
- {
- "name": "construction_worker_tone3",
- "unicode": "1F477-1F3FD",
+ "construction_worker_tone3": {
+ "category": "people",
+ "moji": "👷🏽",
+ "unicodeVersion": "8.0",
"digest": "37c114d6879b9b32b800b0d4cf770dcbe04d1455698130ecd709a0cb9dea880b"
},
- {
- "name": "construction_worker_tone4",
- "unicode": "1F477-1F3FE",
+ "construction_worker_tone4": {
+ "category": "people",
+ "moji": "👷🏾",
+ "unicodeVersion": "8.0",
"digest": "5264996c1bedb6061a0dfdddce233d863bf308d27127ad152b63bfd983162cf7"
},
- {
- "name": "construction_worker_tone5",
- "unicode": "1F477-1F3FF",
+ "construction_worker_tone5": {
+ "category": "people",
+ "moji": "👷🏿",
+ "unicodeVersion": "8.0",
"digest": "87051aec81fd5dfd4dc44ff0411a528ee08253e9494d37efa550694e28dde6d3"
},
- {
- "name": "control_knobs",
- "unicode": "1F39B",
+ "control_knobs": {
+ "category": "objects",
+ "moji": "🎛",
+ "unicodeVersion": "7.0",
"digest": "0d7f33ff7acc1cc3a81e6a786ff007df20da145e3070f338505dfed5100e9fcb"
},
- {
- "name": "convenience_store",
- "unicode": "1F3EA",
+ "convenience_store": {
+ "category": "travel",
+ "moji": "🏪",
+ "unicodeVersion": "6.0",
"digest": "975dcf9b8e9e3fb1e29574b41300b9d96fd64703b3c18ff52f9f1875d1cf1b52"
},
- {
- "name": "cookie",
- "unicode": "1F36A",
+ "cookie": {
+ "category": "food",
+ "moji": "🍪",
+ "unicodeVersion": "6.0",
"digest": "4bed3522bd50091ac5b68ca760661eb484d7f1b9c9d564d2097bd812b7f28ae4"
},
- {
- "name": "cooking",
- "unicode": "1F373",
+ "cooking": {
+ "category": "food",
+ "moji": "🍳",
+ "unicodeVersion": "6.0",
"digest": "563ffd6cae381ce1e318cdacc54e70040d6a01a50d0db8aeb50edbbe413eac58"
},
- {
- "name": "cool",
- "unicode": "1F192",
+ "cool": {
+ "category": "symbols",
+ "moji": "🆒",
+ "unicodeVersion": "6.0",
"digest": "5739a37341c782a4736adfce804e12776ae33081098a3d052d8ae9a64b4d22d1"
},
- {
- "name": "cop",
- "unicode": "1F46E",
+ "cop": {
+ "category": "people",
+ "moji": "👮",
+ "unicodeVersion": "6.0",
"digest": "78996521bbe231d03ebea355226d8a1515f47cde7b2fbeca1037e7b7e5133466"
},
- {
- "name": "cop_tone1",
- "unicode": "1F46E-1F3FB",
+ "cop_tone1": {
+ "category": "people",
+ "moji": "👮🏻",
+ "unicodeVersion": "8.0",
"digest": "8a38cd107f5f4c0b821ac43f32df5dc57facaf39fbafb98483ec00fd7df41baf"
},
- {
- "name": "cop_tone2",
- "unicode": "1F46E-1F3FC",
+ "cop_tone2": {
+ "category": "people",
+ "moji": "👮🏼",
+ "unicodeVersion": "8.0",
"digest": "8ab8ab086f3ff82aa4bf4760c3c822846ec2696c41d21dffdac12d5afbe398b7"
},
- {
- "name": "cop_tone3",
- "unicode": "1F46E-1F3FD",
+ "cop_tone3": {
+ "category": "people",
+ "moji": "👮🏽",
+ "unicodeVersion": "8.0",
"digest": "fce710a99fd44a7c8af3ea01b2007e46d3ff38d7a0dff1ef26d6f893ede7e6d2"
},
- {
- "name": "cop_tone4",
- "unicode": "1F46E-1F3FE",
+ "cop_tone4": {
+ "category": "people",
+ "moji": "👮🏾",
+ "unicodeVersion": "8.0",
"digest": "3017dd73ef475379911c5e6c79bd0f9f533dbbc5057bce6a11244faa12996ba0"
},
- {
- "name": "cop_tone5",
- "unicode": "1F46E-1F3FF",
+ "cop_tone5": {
+ "category": "people",
+ "moji": "👮🏿",
+ "unicodeVersion": "8.0",
"digest": "a3b8807b3f2a8d6ee9bcec0339355bda486e8c930f727139f5447a4b046a6307"
},
- {
- "name": "copyright",
- "unicode": "00A9",
+ "copyright": {
+ "category": "symbols",
+ "moji": "©",
+ "unicodeVersion": "1.1",
"digest": "cc28663cdd3f8333d9bb57b511348cde4e51bda19cf0629dccb05c8fc425e079"
},
- {
- "name": "corn",
- "unicode": "1F33D",
+ "corn": {
+ "category": "food",
+ "moji": "🌽",
+ "unicodeVersion": "6.0",
"digest": "a099a0b291fa758690e6ee6c762b9ade9a0e3350a707c52d968dfffbcc467de5"
},
- {
- "name": "couch",
- "unicode": "1F6CB",
- "digest": "84cd734dbaa7f9f519438036d687e7a53217130779bc3de30258f163521b9474"
- },
- {
- "name": "couch_and_lamp",
- "unicode": "1F6CB",
+ "couch": {
+ "category": "objects",
+ "moji": "🛋",
+ "unicodeVersion": "7.0",
"digest": "84cd734dbaa7f9f519438036d687e7a53217130779bc3de30258f163521b9474"
},
- {
- "name": "couple",
- "unicode": "1F46B",
+ "couple": {
+ "category": "people",
+ "moji": "👫",
+ "unicodeVersion": "6.0",
"digest": "c897ba76e24e2f43a4aa261c2754800a8473f43c7ce53f9909a6af2c4897732a"
},
- {
- "name": "couple_mm",
- "unicode": "1F468-2764-1F468",
+ "couple_mm": {
+ "category": "people",
+ "moji": "👨‍❤️‍👨",
+ "unicodeVersion": "6.0",
"digest": "c812471d35d46e12270653039a907d1dfa2dea0defd65596283e5b8e03cea803"
},
- {
- "name": "couple_with_heart_mm",
- "unicode": "1F468-2764-1F468",
- "digest": "c812471d35d46e12270653039a907d1dfa2dea0defd65596283e5b8e03cea803"
- },
- {
- "name": "couple_with_heart",
- "unicode": "1F491",
+ "couple_with_heart": {
+ "category": "people",
+ "moji": "💑",
+ "unicodeVersion": "6.0",
"digest": "420bfa81bad10365550c77a98e1c07eb00d03663fe7b610fab1aca8a0a9d201b"
},
- {
- "name": "couple_ww",
- "unicode": "1F469-2764-1F469",
- "digest": "7ac49153a612d63302299eee996308b7dcafa0a152473dab679215036fe6567e"
- },
- {
- "name": "couple_with_heart_ww",
- "unicode": "1F469-2764-1F469",
+ "couple_ww": {
+ "category": "people",
+ "moji": "👩‍❤️‍👩",
+ "unicodeVersion": "6.0",
"digest": "7ac49153a612d63302299eee996308b7dcafa0a152473dab679215036fe6567e"
},
- {
- "name": "couplekiss",
- "unicode": "1F48F",
+ "couplekiss": {
+ "category": "people",
+ "moji": "💏",
+ "unicodeVersion": "6.0",
"digest": "1acfef9d375c4c1deb235babd856b0f90ad4f3194751694cb6abb44f00f29e42"
},
- {
- "name": "cow",
- "unicode": "1F42E",
+ "cow": {
+ "category": "nature",
+ "moji": "🐮",
+ "unicodeVersion": "6.0",
"digest": "d71c854ff8b343ee24b8c2b9d56c7cb3fc6fa1a6dc0d7a137841b9f646e6d71b"
},
- {
- "name": "cow2",
- "unicode": "1F404",
+ "cow2": {
+ "category": "nature",
+ "moji": "🐄",
+ "unicodeVersion": "6.0",
"digest": "e7a5131d7dee0f3356814b0ac1ea8ff280b12a7b580181e20ddb0b7eeb7e7339"
},
- {
- "name": "cowboy",
- "unicode": "1F920",
+ "cowboy": {
+ "category": "people",
+ "moji": "🤠",
+ "unicodeVersion": "9.0",
"digest": "1aabf23f6b95a9b772fdb8eb45b8ec93584a5357f9131c6eabc9d1b83fe67e89"
},
- {
- "name": "face_with_cowboy_hat",
- "unicode": "1F920",
- "digest": "1aabf23f6b95a9b772fdb8eb45b8ec93584a5357f9131c6eabc9d1b83fe67e89"
- },
- {
- "name": "crab",
- "unicode": "1F980",
+ "crab": {
+ "category": "nature",
+ "moji": "🦀",
+ "unicodeVersion": "8.0",
"digest": "e6be16699fdb5d87f42f28f6cc141a44b7ffd834ecdd536813c4b5b86d3fc4a5"
},
- {
- "name": "crayon",
- "unicode": "1F58D",
- "digest": "b180d6afa4777861222a4228164ce284230fe90c589f52ffa9351bac777e901a"
- },
- {
- "name": "lower_left_crayon",
- "unicode": "1F58D",
+ "crayon": {
+ "category": "objects",
+ "moji": "🖍",
+ "unicodeVersion": "7.0",
"digest": "b180d6afa4777861222a4228164ce284230fe90c589f52ffa9351bac777e901a"
},
- {
- "name": "credit_card",
- "unicode": "1F4B3",
+ "credit_card": {
+ "category": "objects",
+ "moji": "💳",
+ "unicodeVersion": "6.0",
"digest": "808cd120fd3738eb2be1f6c6c029d98387b0e03fca7d1451e8fbf9c5ab3f643f"
},
- {
- "name": "crescent_moon",
- "unicode": "1F319",
+ "crescent_moon": {
+ "category": "nature",
+ "moji": "🌙",
+ "unicodeVersion": "6.0",
"digest": "042e7e01e6e88b97a763b7cc41e2a2b3fe68a649bacf4a090cd28fc653baf640"
},
- {
- "name": "cricket",
- "unicode": "1F3CF",
+ "cricket": {
+ "category": "activity",
+ "moji": "🏏",
+ "unicodeVersion": "8.0",
"digest": "4c4559d0b4efe24cc248fa57f413541307992e519d0cb9fb8828637ac2f4cc16"
},
- {
- "name": "cricket_bat_ball",
- "unicode": "1F3CF",
- "digest": "4c4559d0b4efe24cc248fa57f413541307992e519d0cb9fb8828637ac2f4cc16"
- },
- {
- "name": "crocodile",
- "unicode": "1F40A",
+ "crocodile": {
+ "category": "nature",
+ "moji": "🐊",
+ "unicodeVersion": "6.0",
"digest": "59cb4164c50b6bc9ae311ce6f7610467c1aaafa848b5fff7614f064715f91992"
},
- {
- "name": "croissant",
- "unicode": "1F950",
+ "croissant": {
+ "category": "food",
+ "moji": "🥐",
+ "unicodeVersion": "9.0",
"digest": "b751e287157a1e276617a841a5b5f7f1208ca226cfd8fa947f144390b65a5e16"
},
- {
- "name": "cross",
- "unicode": "271D",
- "digest": "a6b07c838fb75ef2ebefa2df6005e8d784753239ec03c37695a13e3b1954d653"
- },
- {
- "name": "latin_cross",
- "unicode": "271D",
+ "cross": {
+ "category": "symbols",
+ "moji": "✝",
+ "unicodeVersion": "1.1",
"digest": "a6b07c838fb75ef2ebefa2df6005e8d784753239ec03c37695a13e3b1954d653"
},
- {
- "name": "crossed_flags",
- "unicode": "1F38C",
+ "crossed_flags": {
+ "category": "objects",
+ "moji": "🎌",
+ "unicodeVersion": "6.0",
"digest": "2841c671075e6f1a79c61c2d716423159fb0bc0786e3fb0049697766533bf262"
},
- {
- "name": "crossed_swords",
- "unicode": "2694",
+ "crossed_swords": {
+ "category": "objects",
+ "moji": "⚔",
+ "unicodeVersion": "4.1",
"digest": "3771a5b26b514236521ce44e15f7730fa9148c6a782b9b600ab870a1f7de6f9f"
},
- {
- "name": "crown",
- "unicode": "1F451",
+ "crown": {
+ "category": "people",
+ "moji": "👑",
+ "unicodeVersion": "6.0",
"digest": "6741e58d8f823194e0a3484ac1563e20d9e0b44c1bc46d82444dfffa092cdfc7"
},
- {
- "name": "cruise_ship",
- "unicode": "1F6F3",
+ "cruise_ship": {
+ "category": "travel",
+ "moji": "🛳",
+ "unicodeVersion": "7.0",
"digest": "2b7b62db5d118a632673564099e3405ea6d61ea9b8e123b5a2aaf011bb2a54a4"
},
- {
- "name": "passenger_ship",
- "unicode": "1F6F3",
- "digest": "2b7b62db5d118a632673564099e3405ea6d61ea9b8e123b5a2aaf011bb2a54a4"
- },
- {
- "name": "cry",
- "unicode": "1F622",
+ "cry": {
+ "category": "people",
+ "moji": "😢",
+ "unicodeVersion": "6.0",
"digest": "fc3307ec4fe75539770c1123a0e8e721d9e021009a502655132f68d7cc453816"
},
- {
- "name": "crying_cat_face",
- "unicode": "1F63F",
+ "crying_cat_face": {
+ "category": "people",
+ "moji": "😿",
+ "unicodeVersion": "6.0",
"digest": "4942c24935c22babdcb8af41d2c0a7588356b6b674bc238902e2f10ad03e2c5b"
},
- {
- "name": "crystal_ball",
- "unicode": "1F52E",
+ "crystal_ball": {
+ "category": "objects",
+ "moji": "🔮",
+ "unicodeVersion": "6.0",
"digest": "05f73b30b1e5b0fc66fb5dc6caddd2d547ee7b9d2f97513dc908ba1a2e352e30"
},
- {
- "name": "cucumber",
- "unicode": "1F952",
+ "cucumber": {
+ "category": "food",
+ "moji": "🥒",
+ "unicodeVersion": "9.0",
"digest": "d1196e23f2f155ef5c1330f8497f40957a7357cb177127f457c5c471f0a23727"
},
- {
- "name": "cupid",
- "unicode": "1F498",
+ "cupid": {
+ "category": "symbols",
+ "moji": "💘",
+ "unicodeVersion": "6.0",
"digest": "246e71f44c6ebc2e4f887e25438e4f894e8cc92e06069e711b893ff391abb658"
},
- {
- "name": "curly_loop",
- "unicode": "27B0",
+ "curly_loop": {
+ "category": "symbols",
+ "moji": "➰",
+ "unicodeVersion": "6.0",
"digest": "9e4eb98d6597888f91208080c6a79824adb432ea34f46c85da26cb630bd1cc73"
},
- {
- "name": "currency_exchange",
- "unicode": "1F4B1",
+ "currency_exchange": {
+ "category": "symbols",
+ "moji": "💱",
+ "unicodeVersion": "6.0",
"digest": "b85377265b9876888969aa42b65bba0be523a370175baf226f20131e535af554"
},
- {
- "name": "curry",
- "unicode": "1F35B",
+ "curry": {
+ "category": "food",
+ "moji": "🍛",
+ "unicodeVersion": "6.0",
"digest": "a01c0a713662817720b485f7739f57e61afc025f5c43792f4de961c94f92f31e"
},
- {
- "name": "custard",
- "unicode": "1F36E",
+ "custard": {
+ "category": "food",
+ "moji": "🍮",
+ "unicodeVersion": "6.0",
"digest": "85c2b9ac904134a6c3587eb0a0806f2ab4282c5ed5c79d41734f3203998f757e"
},
- {
- "name": "customs",
- "unicode": "1F6C3",
+ "customs": {
+ "category": "symbols",
+ "moji": "🛃",
+ "unicodeVersion": "6.0",
"digest": "eb2546e1e617d4c1a1f614318af5e5dacf3e8d9479ffa08108977defa83ded32"
},
- {
- "name": "cyclone",
- "unicode": "1F300",
+ "cyclone": {
+ "category": "symbols",
+ "moji": "🌀",
+ "unicodeVersion": "6.0",
"digest": "7a0f8564d76adf2d0ed272f56dc0d01fb7b557852e0ca797e73f5472b8630bf3"
},
- {
- "name": "dagger",
- "unicode": "1F5E1",
+ "dagger": {
+ "category": "objects",
+ "moji": "🗡",
+ "unicodeVersion": "7.0",
"digest": "35a179168198d03295e626cc27d3b92d30a732c55a2ca75d7a11a0fbed414772"
},
- {
- "name": "dagger_knife",
- "unicode": "1F5E1",
- "digest": "35a179168198d03295e626cc27d3b92d30a732c55a2ca75d7a11a0fbed414772"
- },
- {
- "name": "dancer",
- "unicode": "1F483",
+ "dancer": {
+ "category": "people",
+ "moji": "💃",
+ "unicodeVersion": "6.0",
"digest": "66ffa86827e85acae4aa870c0859fe3a9dad03d21ff4bc800b61c95c902a8a90"
},
- {
- "name": "dancer_tone1",
- "unicode": "1F483-1F3FB",
+ "dancer_tone1": {
+ "category": "people",
+ "moji": "💃🏻",
+ "unicodeVersion": "8.0",
"digest": "bdbee740addc890e369d3469a3585eb0d1e4fbc7e04dd6f6aca762d8aeee6a8c"
},
- {
- "name": "dancer_tone2",
- "unicode": "1F483-1F3FC",
+ "dancer_tone2": {
+ "category": "people",
+ "moji": "💃🏼",
+ "unicodeVersion": "8.0",
"digest": "9f7b4c627241eaa2def9717a5286a423f0b9c1b044dd9ea4442a76f1858d14a4"
},
- {
- "name": "dancer_tone3",
- "unicode": "1F483-1F3FD",
+ "dancer_tone3": {
+ "category": "people",
+ "moji": "💃🏽",
+ "unicodeVersion": "8.0",
"digest": "a6bd49a377ce6c2004bf126b6f66d0b21d8c14103c2add7b10f12ed9e1c2d302"
},
- {
- "name": "dancer_tone4",
- "unicode": "1F483-1F3FE",
+ "dancer_tone4": {
+ "category": "people",
+ "moji": "💃🏾",
+ "unicodeVersion": "8.0",
"digest": "4ec2a7629c01b0e9006b5cda4deae3bf297ce3b71d18063f93eeb5c14be19a1a"
},
- {
- "name": "dancer_tone5",
- "unicode": "1F483-1F3FF",
+ "dancer_tone5": {
+ "category": "people",
+ "moji": "💃🏿",
+ "unicodeVersion": "8.0",
"digest": "2b48e3a6b366c6f55f73b816e6fb03c39e9890f586f7e9c9043cf0c013d9cdd5"
},
- {
- "name": "dancers",
- "unicode": "1F46F",
+ "dancers": {
+ "category": "people",
+ "moji": "👯",
+ "unicodeVersion": "6.0",
"digest": "12be66ed19d232bb387270f40bece68bd0cb2342b318f6c9bb8b49c64ff7d0ad"
},
- {
- "name": "dango",
- "unicode": "1F361",
+ "dango": {
+ "category": "food",
+ "moji": "🍡",
+ "unicodeVersion": "6.0",
"digest": "34e8cd153c50f2d725abe8934c35c96a3ab533f0cc5fbb1e1474eafad1dc1fc2"
},
- {
- "name": "dark_sunglasses",
- "unicode": "1F576",
+ "dark_sunglasses": {
+ "category": "people",
+ "moji": "🕶",
+ "unicodeVersion": "7.0",
"digest": "d0a735ad5bf0ece00af2a21abf950b89292ebd8ca6e28b1dbb1368252fb44afe"
},
- {
- "name": "dart",
- "unicode": "1F3AF",
+ "dart": {
+ "category": "activity",
+ "moji": "🎯",
+ "unicodeVersion": "6.0",
"digest": "998642f06a875905e0a6bf30963c025baff1cf55b8e76884b9119f2d71188b0c"
},
- {
- "name": "dash",
- "unicode": "1F4A8",
+ "dash": {
+ "category": "nature",
+ "moji": "💨",
+ "unicodeVersion": "6.0",
"digest": "f7aae7d3887c67d76f3329c2dc9e6807dc580a4b07ab35599c7805e41823a345"
},
- {
- "name": "date",
- "unicode": "1F4C5",
+ "date": {
+ "category": "objects",
+ "moji": "📅",
+ "unicodeVersion": "6.0",
"digest": "d0b695e4a7cfbbe71b4fbebf345b66ca98f0cf1c751362928e54c23ca78d4c7b"
},
- {
- "name": "deciduous_tree",
- "unicode": "1F333",
+ "deciduous_tree": {
+ "category": "nature",
+ "moji": "🌳",
+ "unicodeVersion": "6.0",
"digest": "3c70f1a77f2754f41c830e88d43b7d53c14311d64626ded164aa9ac7d2695790"
},
- {
- "name": "deer",
- "unicode": "1F98C",
+ "deer": {
+ "category": "nature",
+ "moji": "🦌",
+ "unicodeVersion": "9.0",
"digest": "7f4302ca68fd121ee73be48d0a0a0fb9e7e2741071a491ad2b7b0eab9f11ad25"
},
- {
- "name": "department_store",
- "unicode": "1F3EC",
+ "department_store": {
+ "category": "travel",
+ "moji": "🏬",
+ "unicodeVersion": "6.0",
"digest": "4be910d2efe74d8ce2c1f41d7753c8873579faca83fcf779a4887d8ab9e5923b"
},
- {
- "name": "desert",
- "unicode": "1F3DC",
+ "desert": {
+ "category": "travel",
+ "moji": "🏜",
+ "unicodeVersion": "7.0",
"digest": "d4b1a11c5130debe042df6cc2b3389f15c68a5cb32dc1b3a82b78f733d0c9e4e"
},
- {
- "name": "desktop",
- "unicode": "1F5A5",
+ "desktop": {
+ "category": "objects",
+ "moji": "🖥",
+ "unicodeVersion": "7.0",
"digest": "cde5bfb6c71bb7d663808a3561b24cb5b5560f95f510b40f81250cac1b21933e"
},
- {
- "name": "desktop_computer",
- "unicode": "1F5A5",
- "digest": "cde5bfb6c71bb7d663808a3561b24cb5b5560f95f510b40f81250cac1b21933e"
- },
- {
- "name": "diamond_shape_with_a_dot_inside",
- "unicode": "1F4A0",
+ "diamond_shape_with_a_dot_inside": {
+ "category": "symbols",
+ "moji": "💠",
+ "unicodeVersion": "6.0",
"digest": "e91323577ab89e95b0fa0b9272ea0c797b76908f24d36992630e9325273a4ce3"
},
- {
- "name": "diamonds",
- "unicode": "2666",
+ "diamonds": {
+ "category": "symbols",
+ "moji": "♦",
+ "unicodeVersion": "1.1",
"digest": "bf3d9a020afe8aa226db73590bc193a9c2c3e6e642edd2445c5960c3e67cf153"
},
- {
- "name": "disappointed",
- "unicode": "1F61E",
+ "disappointed": {
+ "category": "people",
+ "moji": "😞",
+ "unicodeVersion": "6.0",
"digest": "c0f406c6beea0fd1328adefc097d04aa16b72f7a5afa0867967d8ea25d72db17"
},
- {
- "name": "disappointed_relieved",
- "unicode": "1F625",
+ "disappointed_relieved": {
+ "category": "people",
+ "moji": "😥",
+ "unicodeVersion": "6.0",
"digest": "c826f5dd4f2f7e5289d720851d4826ab8284d915606c1b152ab229b7fadbba14"
},
- {
- "name": "dividers",
- "unicode": "1F5C2",
- "digest": "4b2c653b18cf0fa31f1f0ac94a6fbd214ea0d1b0a90a450ab6e169906fc5764f"
- },
- {
- "name": "card_index_dividers",
- "unicode": "1F5C2",
+ "dividers": {
+ "category": "objects",
+ "moji": "🗂",
+ "unicodeVersion": "7.0",
"digest": "4b2c653b18cf0fa31f1f0ac94a6fbd214ea0d1b0a90a450ab6e169906fc5764f"
},
- {
- "name": "dizzy",
- "unicode": "1F4AB",
+ "dizzy": {
+ "category": "nature",
+ "moji": "💫",
+ "unicodeVersion": "6.0",
"digest": "d577545c2de42389695447c6ebbfef895f30f0fda84eef45684f9bf4a9c27ff1"
},
- {
- "name": "dizzy_face",
- "unicode": "1F635",
+ "dizzy_face": {
+ "category": "people",
+ "moji": "😵",
+ "unicodeVersion": "6.0",
"digest": "7b3aeaffb4e15ccf633b91dda4a44847a1eb28d78ce58b4d171b20a771bde414"
},
- {
- "name": "do_not_litter",
- "unicode": "1F6AF",
+ "do_not_litter": {
+ "category": "symbols",
+ "moji": "🚯",
+ "unicodeVersion": "6.0",
"digest": "98b07fbbcdb438d1b8a755869fa2de8e180a77fce359ec830eb46d38ec3e67cb"
},
- {
- "name": "dog",
- "unicode": "1F436",
+ "dog": {
+ "category": "nature",
+ "moji": "🐶",
+ "unicodeVersion": "6.0",
"digest": "3b31ce067b13e463284ce85536512cb1f8cd8b52fe73659f69971d0d6c1dfc11"
},
- {
- "name": "dog2",
- "unicode": "1F415",
+ "dog2": {
+ "category": "nature",
+ "moji": "🐕",
+ "unicodeVersion": "6.0",
"digest": "0a8901bce5ed994533ff84299b2a1364de28d872c9f9510d3426a83e8a9d2e34"
},
- {
- "name": "dollar",
- "unicode": "1F4B5",
+ "dollar": {
+ "category": "objects",
+ "moji": "💵",
+ "unicodeVersion": "6.0",
"digest": "52438e38867aedc021740bb41f9ba336e75a50faa148419412a01d75d8c93155"
},
- {
- "name": "dolls",
- "unicode": "1F38E",
+ "dolls": {
+ "category": "objects",
+ "moji": "🎎",
+ "unicodeVersion": "6.0",
"digest": "a687184e9a0915deef44bb3cacfb19d3f3f19cf2c110f1da90191dd567333c57"
},
- {
- "name": "dolphin",
- "unicode": "1F42C",
+ "dolphin": {
+ "category": "nature",
+ "moji": "🐬",
+ "unicodeVersion": "6.0",
"digest": "0b7ee08f4236232ca533ed3a3023d28020d36f178efaec5ce8b0e13a84778512"
},
- {
- "name": "door",
- "unicode": "1F6AA",
+ "door": {
+ "category": "objects",
+ "moji": "🚪",
+ "unicodeVersion": "6.0",
"digest": "984a9ca88852ebdb539e0c385d9c6ffe5010e9189bc372a3d00f5c8d44c8e6f5"
},
- {
- "name": "doughnut",
- "unicode": "1F369",
+ "doughnut": {
+ "category": "food",
+ "moji": "🍩",
+ "unicodeVersion": "6.0",
"digest": "27634587e6a53807baa32157bb06b0e115c8ad8aefebba7ebb0b65a084170e3a"
},
- {
- "name": "dove",
- "unicode": "1F54A",
+ "dove": {
+ "category": "nature",
+ "moji": "🕊",
+ "unicodeVersion": "7.0",
"digest": "7c665f8594ffa53e72b01647e9d27360fb87d52d02fe9f20fc5fda08f9797dc3"
},
- {
- "name": "dove_of_peace",
- "unicode": "1F54A",
- "digest": "7c665f8594ffa53e72b01647e9d27360fb87d52d02fe9f20fc5fda08f9797dc3"
- },
- {
- "name": "dragon",
- "unicode": "1F409",
+ "dragon": {
+ "category": "nature",
+ "moji": "🐉",
+ "unicodeVersion": "6.0",
"digest": "2abcb3d945d848e34ffc76203b29ef26df7458856166fffd155611f7bbe72652"
},
- {
- "name": "dragon_face",
- "unicode": "1F432",
+ "dragon_face": {
+ "category": "nature",
+ "moji": "🐲",
+ "unicodeVersion": "6.0",
"digest": "0030548931b931e3b51f26cf660394aee36499e688ba83ce9cfccb635dcd4d54"
},
- {
- "name": "dress",
- "unicode": "1F457",
+ "dress": {
+ "category": "people",
+ "moji": "👗",
+ "unicodeVersion": "6.0",
"digest": "96ceba928fb356f7c0ae99bf22552321f08a65d5f1c0340ab89641219ad366ad"
},
- {
- "name": "dromedary_camel",
- "unicode": "1F42A",
+ "dromedary_camel": {
+ "category": "nature",
+ "moji": "🐪",
+ "unicodeVersion": "6.0",
"digest": "e06ef69c29f0fb12481727c0b4124e700572d3d7955e173279320f43f286518d"
},
- {
- "name": "drooling_face",
- "unicode": "1F924",
- "digest": "5203cb05cd266d7a7c929ab40364ad68571d380d9c7ff93a8d6d55261abaa1ba"
- },
- {
- "name": "drool",
- "unicode": "1F924",
+ "drooling_face": {
+ "category": "people",
+ "moji": "🤤",
+ "unicodeVersion": "9.0",
"digest": "5203cb05cd266d7a7c929ab40364ad68571d380d9c7ff93a8d6d55261abaa1ba"
},
- {
- "name": "droplet",
- "unicode": "1F4A7",
+ "droplet": {
+ "category": "nature",
+ "moji": "💧",
+ "unicodeVersion": "6.0",
"digest": "6475b4a4460a672c436a68f282ac97fb31e2934db4b80620063ee816159aa7c3"
},
- {
- "name": "drum",
- "unicode": "1F941",
- "digest": "0d0639980b1a5dcbf1c3e7ef47263fb6543b871242c58452a8c2f642525d9dd8"
- },
- {
- "name": "drum_with_drumsticks",
- "unicode": "1F941",
+ "drum": {
+ "category": "activity",
+ "moji": "🥁",
+ "unicodeVersion": "9.0",
"digest": "0d0639980b1a5dcbf1c3e7ef47263fb6543b871242c58452a8c2f642525d9dd8"
},
- {
- "name": "duck",
- "unicode": "1F986",
+ "duck": {
+ "category": "nature",
+ "moji": "🦆",
+ "unicodeVersion": "9.0",
"digest": "8f8373798a7727368b32328e7a9a349727a949e7391ddd243b6456141a4f7e94"
},
- {
- "name": "dvd",
- "unicode": "1F4C0",
+ "dvd": {
+ "category": "objects",
+ "moji": "📀",
+ "unicodeVersion": "6.0",
"digest": "3b7903285d91277181c26fdc9df857761bbac509d352e320c2519ea3b132704f"
},
- {
- "name": "e-mail",
- "unicode": "1F4E7",
- "digest": "39b5a57a2376e4a1137e381be02a1775bd580e0371438f5297a401ea634f1830"
- },
- {
- "name": "email",
- "unicode": "1F4E7",
+ "e-mail": {
+ "category": "objects",
+ "moji": "📧",
+ "unicodeVersion": "6.0",
"digest": "39b5a57a2376e4a1137e381be02a1775bd580e0371438f5297a401ea634f1830"
},
- {
- "name": "eagle",
- "unicode": "1F985",
+ "eagle": {
+ "category": "nature",
+ "moji": "🦅",
+ "unicodeVersion": "9.0",
"digest": "b44fd4f61b83c5114358a272343ac9b0eabbc70847f739bbdbf8aae3ade5bc1d"
},
- {
- "name": "ear",
- "unicode": "1F442",
+ "ear": {
+ "category": "people",
+ "moji": "👂",
+ "unicodeVersion": "6.0",
"digest": "4fdeb5a46e69311ecfd09c5b45c9018c24b625e28475cca8fa516b086ef952f8"
},
- {
- "name": "ear_of_rice",
- "unicode": "1F33E",
+ "ear_of_rice": {
+ "category": "nature",
+ "moji": "🌾",
+ "unicodeVersion": "6.0",
"digest": "2997c340c2b333d6ba9b73f94ff1a1881735fe0cc4f0c72d7719b305499fc425"
},
- {
- "name": "ear_tone1",
- "unicode": "1F442-1F3FB",
+ "ear_tone1": {
+ "category": "people",
+ "moji": "👂🏻",
+ "unicodeVersion": "8.0",
"digest": "5ca759b8569a377a4e63e30d94b585b9f76d15348a8a0c1ba19fdc522790615e"
},
- {
- "name": "ear_tone2",
- "unicode": "1F442-1F3FC",
+ "ear_tone2": {
+ "category": "people",
+ "moji": "👂🏼",
+ "unicodeVersion": "8.0",
"digest": "12aafb3ef2cfcdc892b2877c2e24920620f0f77f850e12afbfe55eadce9e37df"
},
- {
- "name": "ear_tone3",
- "unicode": "1F442-1F3FD",
+ "ear_tone3": {
+ "category": "people",
+ "moji": "👂🏽",
+ "unicodeVersion": "8.0",
"digest": "f4d28d9f72cf116ac92d80061eb84c918d6523bf53b2ad526f5457aba487d527"
},
- {
- "name": "ear_tone4",
- "unicode": "1F442-1F3FE",
+ "ear_tone4": {
+ "category": "people",
+ "moji": "👂🏾",
+ "unicodeVersion": "8.0",
"digest": "eaa9453670f7e3adc6ec6934ee70efc9bf60fe6c99c5804b7ba9e3804aec65de"
},
- {
- "name": "ear_tone5",
- "unicode": "1F442-1F3FF",
+ "ear_tone5": {
+ "category": "people",
+ "moji": "👂🏿",
+ "unicodeVersion": "8.0",
"digest": "54bd0782419489556b80e9e0d15b05df74757aa4e04ba565f45c20d3dd60e3f1"
},
- {
- "name": "earth_africa",
- "unicode": "1F30D",
+ "earth_africa": {
+ "category": "nature",
+ "moji": "🌍",
+ "unicodeVersion": "6.0",
"digest": "c691a6f591f5a07b268fd64efe113e81cec8d5963ad83ced2537422343ff7ecf"
},
- {
- "name": "earth_americas",
- "unicode": "1F30E",
+ "earth_americas": {
+ "category": "nature",
+ "moji": "🌎",
+ "unicodeVersion": "6.0",
"digest": "a9c60cf8341ff59a9cc1a715b7144af734fcd28915a8e003a31ebf2abf9aedb1"
},
- {
- "name": "earth_asia",
- "unicode": "1F30F",
+ "earth_asia": {
+ "category": "nature",
+ "moji": "🌏",
+ "unicodeVersion": "6.0",
"digest": "ee2beb61fb8c87279161c5a8c4ad17bb71ce790123f8fa33522941d027e060a5"
},
- {
- "name": "egg",
- "unicode": "1F95A",
+ "egg": {
+ "category": "food",
+ "moji": "🥚",
+ "unicodeVersion": "9.0",
"digest": "72b9c841af784e7cbccbbe48ba833df5cecdd284397c199cab079872e879d92f"
},
- {
- "name": "eggplant",
- "unicode": "1F346",
+ "eggplant": {
+ "category": "food",
+ "moji": "🍆",
+ "unicodeVersion": "6.0",
"digest": "ec0a460e0cf0e615f51279677594a899672e1b4ecd9396e17a8cfa2a3efe5238"
},
- {
- "name": "eight",
- "unicode": "0038-20E3",
+ "eight": {
+ "category": "symbols",
+ "moji": "8️⃣",
+ "unicodeVersion": "3.0",
"digest": "57ff905033a32747690adba6486d12b09eb4d45de556f4e1ab6fb04e1fb861a8"
},
- {
- "name": "eight_pointed_black_star",
- "unicode": "2734",
+ "eight_pointed_black_star": {
+ "category": "symbols",
+ "moji": "✴",
+ "unicodeVersion": "1.1",
"digest": "7bf11f6e28591e3d0625296aaabf4ecb75c982e425abf3049339e93494acc17e"
},
- {
- "name": "eight_spoked_asterisk",
- "unicode": "2733",
+ "eight_spoked_asterisk": {
+ "category": "symbols",
+ "moji": "✳",
+ "unicodeVersion": "1.1",
"digest": "bb0758e7cc0e357285937671a91489bd32ce9d248eecdcc9c275a53a66325b26"
},
- {
- "name": "eject",
- "unicode": "23CF",
+ "eject": {
+ "category": "symbols",
+ "moji": "⏏",
+ "unicodeVersion": "4.0",
"digest": "eeb0cd23ead0c965e307de517a6805265f0c780c3e454e64bc4c1425dfe7548e"
},
- {
- "name": "eject_symbol",
- "unicode": "23CF",
- "digest": "eeb0cd23ead0c965e307de517a6805265f0c780c3e454e64bc4c1425dfe7548e"
- },
- {
- "name": "electric_plug",
- "unicode": "1F50C",
+ "electric_plug": {
+ "category": "objects",
+ "moji": "🔌",
+ "unicodeVersion": "6.0",
"digest": "b10ce87af86fa4f4022572ceb5ecd73bea867347a86832a7ea248364b0aad8d0"
},
- {
- "name": "elephant",
- "unicode": "1F418",
+ "elephant": {
+ "category": "nature",
+ "moji": "🐘",
+ "unicodeVersion": "6.0",
"digest": "b7750f4b013fbd28ac5330e1694ef4d3b4a9c6fc7b807879db0c24b035a16c29"
},
- {
- "name": "end",
- "unicode": "1F51A",
+ "end": {
+ "category": "symbols",
+ "moji": "🔚",
+ "unicodeVersion": "6.0",
"digest": "dd93aee6986eb637a8b58f234da47568b88525599f73246e322af030351997a2"
},
- {
- "name": "envelope",
- "unicode": "2709",
+ "envelope": {
+ "category": "objects",
+ "moji": "✉",
+ "unicodeVersion": "1.1",
"digest": "f5a512022a2f5280f372ff39c22cbda815f698710ca66f8f8c4d08418f98ca78"
},
- {
- "name": "envelope_with_arrow",
- "unicode": "1F4E9",
+ "envelope_with_arrow": {
+ "category": "objects",
+ "moji": "📩",
+ "unicodeVersion": "6.0",
"digest": "f8643212e6a94f58ccf2bcedc54c5fda8ebeab274f4a8803f253de5f50ddb1d6"
},
- {
- "name": "euro",
- "unicode": "1F4B6",
+ "euro": {
+ "category": "objects",
+ "moji": "💶",
+ "unicodeVersion": "6.0",
"digest": "3af3e223e8f26468a94f6f5c17198432656e8d20b3bab31566c2b5a86e717df4"
},
- {
- "name": "european_castle",
- "unicode": "1F3F0",
+ "european_castle": {
+ "category": "travel",
+ "moji": "🏰",
+ "unicodeVersion": "6.0",
"digest": "21082d0be7e3b2794e59ff0170da0cfe42a9b734cf02704603e3b52ff48202ba"
},
- {
- "name": "european_post_office",
- "unicode": "1F3E4",
+ "european_post_office": {
+ "category": "travel",
+ "moji": "🏤",
+ "unicodeVersion": "6.0",
"digest": "02b4c7602939f0cb9cb2b4e05996bcdb6bd93cf8025c2ea02db8cbe13ca397d0"
},
- {
- "name": "evergreen_tree",
- "unicode": "1F332",
+ "evergreen_tree": {
+ "category": "nature",
+ "moji": "🌲",
+ "unicodeVersion": "6.0",
"digest": "74b226098e66c0a94a92e0f22b9d631736e12dca72c34182c9d0ba56aa593172"
},
- {
- "name": "exclamation",
- "unicode": "2757",
+ "exclamation": {
+ "category": "symbols",
+ "moji": "❗",
+ "unicodeVersion": "5.2",
"digest": "45b87ae4593656d7da49ff5645fb6a2a18d582553295358da9f09f1ae8272445"
},
- {
- "name": "expressionless",
- "unicode": "1F611",
+ "expressionless": {
+ "category": "people",
+ "moji": "😑",
+ "unicodeVersion": "6.1",
"digest": "34e2a1c8121f4f0bc4ce33d226d8cc1a4ebf5260746df2b23e29eef24ee9372e"
},
- {
- "name": "eye",
- "unicode": "1F441",
+ "eye": {
+ "category": "people",
+ "moji": "👁",
+ "unicodeVersion": "7.0",
"digest": "79ecff79c2edee630e72725b54e67ee2e96d24ca03fef2954a56a09c0a2227f8"
},
- {
- "name": "eye_in_speech_bubble",
- "unicode": "1F441-1F5E8",
+ "eye_in_speech_bubble": {
+ "category": "symbols",
+ "moji": "👁‍🗨",
+ "unicodeVersion": "7.0",
"digest": "c0050c026c2a3060723cab2df2603c1c7da7ed81faedb9ebe16cd89721928a55"
},
- {
- "name": "eyeglasses",
- "unicode": "1F453",
+ "eyeglasses": {
+ "category": "people",
+ "moji": "👓",
+ "unicodeVersion": "6.0",
"digest": "d4a9585d6c43ef514a97c45c64607162e775a45544821f1470c6f8f25b93ab81"
},
- {
- "name": "eyes",
- "unicode": "1F440",
+ "eyes": {
+ "category": "people",
+ "moji": "👀",
+ "unicodeVersion": "6.0",
"digest": "1d5cae0b9b2e51e1de54295685d7f0c72ee794e2e6335a95b1d056c7e77260e8"
},
- {
- "name": "face_palm",
- "unicode": "1F926",
+ "face_palm": {
+ "category": "people",
+ "moji": "🤦",
+ "unicodeVersion": "9.0",
"digest": "4ec873048b34b1bb34430724cf28e4bee6c0a9eee88ce39b9d1565047dc92420"
},
- {
- "name": "face_palm_tone1",
- "unicode": "1F926-1F3FB",
+ "face_palm_tone1": {
+ "category": "people",
+ "moji": "🤦🏻",
+ "unicodeVersion": "9.0",
"digest": "e93ef92b4c01dbea6c400e708e23dd36da92ccfbf5eb4f177b3b20c3a46bdc19"
},
- {
- "name": "face_palm_tone2",
- "unicode": "1F926-1F3FC",
+ "face_palm_tone2": {
+ "category": "people",
+ "moji": "🤦🏼",
+ "unicodeVersion": "9.0",
"digest": "22c8bf9fd9fa2ed9dca7a6397ed00ba6cfe9aeef2b0fb7b516ee4dda0df050ea"
},
- {
- "name": "face_palm_tone3",
- "unicode": "1F926-1F3FD",
+ "face_palm_tone3": {
+ "category": "people",
+ "moji": "🤦🏽",
+ "unicodeVersion": "9.0",
"digest": "c0b8bb9d2423e6787b6bdf1ca5a13f52853e4f48a9a1af0f2d4af1364fff022e"
},
- {
- "name": "face_palm_tone4",
- "unicode": "1F926-1F3FE",
+ "face_palm_tone4": {
+ "category": "people",
+ "moji": "🤦🏾",
+ "unicodeVersion": "9.0",
"digest": "f522ab186adcbb4549ea2c03500cdd7a86add548e43ebf7a54d58cc24deea072"
},
- {
- "name": "face_palm_tone5",
- "unicode": "1F926-1F3FF",
+ "face_palm_tone5": {
+ "category": "people",
+ "moji": "🤦🏿",
+ "unicodeVersion": "9.0",
"digest": "363507ae7178b5ec583635f47bcab10c897346f48b85d8759b1004c32cd8ad65"
},
- {
- "name": "factory",
- "unicode": "1F3ED",
+ "factory": {
+ "category": "travel",
+ "moji": "🏭",
+ "unicodeVersion": "6.0",
"digest": "c7aeb61ed8b0ac5c91d5197c73f1e2bb801921c22a76bb82c7659d990680dcb0"
},
- {
- "name": "fallen_leaf",
- "unicode": "1F342",
+ "fallen_leaf": {
+ "category": "nature",
+ "moji": "🍂",
+ "unicodeVersion": "6.0",
"digest": "81fce04231d48db0e55f3697f930e9a7e3306bed5e35f1234e98c40a24ac5626"
},
- {
- "name": "family",
- "unicode": "1F46A",
+ "family": {
+ "category": "people",
+ "moji": "👪",
+ "unicodeVersion": "6.0",
"digest": "06f2ce63768ffe43b3d9b2a9660b34d043f37b3c91610dd62343ba21df8ecbe5"
},
- {
- "name": "family_mmb",
- "unicode": "1F468-1F468-1F466",
+ "family_mmb": {
+ "category": "people",
+ "moji": "👨‍👨‍👦",
+ "unicodeVersion": "6.0",
"digest": "41a18405be796699a7eb7c36ab6f7d898e322749997f45387377acf5bb16a50f"
},
- {
- "name": "family_mmbb",
- "unicode": "1F468-1F468-1F466-1F466",
+ "family_mmbb": {
+ "category": "people",
+ "moji": "👨‍👨‍👦‍👦",
+ "unicodeVersion": "6.0",
"digest": "87255d1d18c6971c8c083c818e598424c1bd717eed892478b7e9516639dbfb45"
},
- {
- "name": "family_mmg",
- "unicode": "1F468-1F468-1F467",
+ "family_mmg": {
+ "category": "people",
+ "moji": "👨‍👨‍👧",
+ "unicodeVersion": "6.0",
"digest": "a132b1b8f10b318d8e23aee15dab4caa14528aeb3c89966d4bcc25fb54af72ad"
},
- {
- "name": "family_mmgb",
- "unicode": "1F468-1F468-1F467-1F466",
+ "family_mmgb": {
+ "category": "people",
+ "moji": "👨‍👨‍👧‍👦",
+ "unicodeVersion": "6.0",
"digest": "eb2bc1966df406aaf38ce5a58db9324162799cdacf31f74f40e6384807a8efc2"
},
- {
- "name": "family_mmgg",
- "unicode": "1F468-1F468-1F467-1F467",
+ "family_mmgg": {
+ "category": "people",
+ "moji": "👨‍👨‍👧‍👧",
+ "unicodeVersion": "6.0",
"digest": "24f3d60f98fbd6b687f7cacfb629390b90509a754036e5439ae5294759c0606b"
},
- {
- "name": "family_mwbb",
- "unicode": "1F468-1F469-1F466-1F466",
+ "family_mwbb": {
+ "category": "people",
+ "moji": "👨‍👩‍👦‍👦",
+ "unicodeVersion": "6.0",
"digest": "2f77692bcb9275c4df501b64a18401dcaf8c68b21f26fbdad59b1feab0c98fd1"
},
- {
- "name": "family_mwg",
- "unicode": "1F468-1F469-1F467",
+ "family_mwg": {
+ "category": "people",
+ "moji": "👨‍👩‍👧",
+ "unicodeVersion": "6.0",
"digest": "1a976d13127665d9386cebfdb24e5572dc499bda484c0ee05585886edc616130"
},
- {
- "name": "family_mwgb",
- "unicode": "1F468-1F469-1F467-1F466",
+ "family_mwgb": {
+ "category": "people",
+ "moji": "👨‍👩‍👧‍👦",
+ "unicodeVersion": "6.0",
"digest": "960ec2cbac13ef208e73644cd36711b83e6c070c36950f834f3669812839b7f8"
},
- {
- "name": "family_mwgg",
- "unicode": "1F468-1F469-1F467-1F467",
+ "family_mwgg": {
+ "category": "people",
+ "moji": "👨‍👩‍👧‍👧",
+ "unicodeVersion": "6.0",
"digest": "8353b03dfa5c24aba75a0abdfdac01603f593819d54b4c7f2f88aafb31da0c6a"
},
- {
- "name": "family_wwb",
- "unicode": "1F469-1F469-1F466",
+ "family_wwb": {
+ "category": "people",
+ "moji": "👩‍👩‍👦",
+ "unicodeVersion": "6.0",
"digest": "07a5dd397718c553573689f6512f386729c13a12d5dc78be47c06405769cd98a"
},
- {
- "name": "family_wwbb",
- "unicode": "1F469-1F469-1F466-1F466",
+ "family_wwbb": {
+ "category": "people",
+ "moji": "👩‍👩‍👦‍👦",
+ "unicodeVersion": "6.0",
"digest": "b627f460f1da0d47b0b662402940b2b77c9538d380d05436dfca4b456c50c939"
},
- {
- "name": "family_wwg",
- "unicode": "1F469-1F469-1F467",
+ "family_wwg": {
+ "category": "people",
+ "moji": "👩‍👩‍👧",
+ "unicodeVersion": "6.0",
"digest": "2d6f373bed53f1028f0fbe9caf036465a351f37b9e00fca7d722cc5a1984f251"
},
- {
- "name": "family_wwgb",
- "unicode": "1F469-1F469-1F467-1F466",
+ "family_wwgb": {
+ "category": "people",
+ "moji": "👩‍👩‍👧‍👦",
+ "unicodeVersion": "6.0",
"digest": "72be5c85e1621f73d6794edd6e428febdb366b9e4c816f7829897fd1ab34642b"
},
- {
- "name": "family_wwgg",
- "unicode": "1F469-1F469-1F467-1F467",
+ "family_wwgg": {
+ "category": "people",
+ "moji": "👩‍👩‍👧‍👧",
+ "unicodeVersion": "6.0",
"digest": "c39e0916069460d2d9741bddf58e76f5d6a09254cba0eeb262345adf8630bc32"
},
- {
- "name": "fast_forward",
- "unicode": "23E9",
+ "fast_forward": {
+ "category": "symbols",
+ "moji": "⏩",
+ "unicodeVersion": "6.0",
"digest": "e7d2d8085cfd406c2b096e8dd147dd3722290a5727b1f7df185989526a2335ec"
},
- {
- "name": "fax",
- "unicode": "1F4E0",
+ "fax": {
+ "category": "objects",
+ "moji": "📠",
+ "unicodeVersion": "6.0",
"digest": "ff85ffa440c5379c9b138ebe2d7912d6098da3b37a051b80442d5557b7f993b0"
},
- {
- "name": "fearful",
- "unicode": "1F628",
+ "fearful": {
+ "category": "people",
+ "moji": "😨",
+ "unicodeVersion": "6.0",
"digest": "b72bdf7d075d5c4e38bbd8512fb45fda2e85c9c8732a47e67575ae9f2ed4c5df"
},
- {
- "name": "feet",
- "unicode": "1F43E",
+ "feet": {
+ "category": "nature",
+ "moji": "🐾",
+ "unicodeVersion": "6.0",
"digest": "45aca538d3a9831a0c7de491e5656c17705c07b8f4ac8e85254656b608976016"
},
- {
- "name": "fencer",
- "unicode": "1F93A",
- "digest": "5db00fa456af9f6c7cb88d300579dd63e426bcb97ad25486b664aff25c688e21"
- },
- {
- "name": "fencing",
- "unicode": "1F93A",
+ "fencer": {
+ "category": "activity",
+ "moji": "🤺",
+ "unicodeVersion": "9.0",
"digest": "5db00fa456af9f6c7cb88d300579dd63e426bcb97ad25486b664aff25c688e21"
},
- {
- "name": "ferris_wheel",
- "unicode": "1F3A1",
+ "ferris_wheel": {
+ "category": "travel",
+ "moji": "🎡",
+ "unicodeVersion": "6.0",
"digest": "24b4551b7b79a2a5fd73de61542f2b444f896a52030c5f29791c8fcfcc28b95c"
},
- {
- "name": "ferry",
- "unicode": "26F4",
+ "ferry": {
+ "category": "travel",
+ "moji": "⛴",
+ "unicodeVersion": "5.2",
"digest": "5002a72af2e3c4cef9a36ad5987aeed7d99f96bfd13e56f78957315ec7e749a3"
},
- {
- "name": "field_hockey",
- "unicode": "1F3D1",
+ "field_hockey": {
+ "category": "activity",
+ "moji": "🏑",
+ "unicodeVersion": "8.0",
"digest": "4ee091d96161ba719ab8fd6f2b03f96d902a6f22cffe0563b930618bb8ac2b67"
},
- {
- "name": "file_cabinet",
- "unicode": "1F5C4",
+ "file_cabinet": {
+ "category": "objects",
+ "moji": "🗄",
+ "unicodeVersion": "7.0",
"digest": "92914147bf93e6d64271ff99d217a18a9850a367d08a5f9f458ecf9311a5bbe9"
},
- {
- "name": "file_folder",
- "unicode": "1F4C1",
+ "file_folder": {
+ "category": "objects",
+ "moji": "📁",
+ "unicodeVersion": "6.0",
"digest": "62a42a929267cfbfdb795ead381c9657c343458bc5fca95ea8a0ab892c61d4f6"
},
- {
- "name": "film_frames",
- "unicode": "1F39E",
+ "film_frames": {
+ "category": "objects",
+ "moji": "🎞",
+ "unicodeVersion": "7.0",
"digest": "4da212148cadb9c4ea91e60d2d8316e38cea99ef4f14afc023711dd7c54ade5a"
},
- {
- "name": "fingers_crossed",
- "unicode": "1F91E",
- "digest": "a5c797ead191b9712e185083266b455cdf09f6a34c10f8c51aa145e6073427e1"
- },
- {
- "name": "hand_with_index_and_middle_finger_crossed",
- "unicode": "1F91E",
+ "fingers_crossed": {
+ "category": "people",
+ "moji": "🤞",
+ "unicodeVersion": "9.0",
"digest": "a5c797ead191b9712e185083266b455cdf09f6a34c10f8c51aa145e6073427e1"
},
- {
- "name": "fingers_crossed_tone1",
- "unicode": "1F91E-1F3FB",
- "digest": "db56d47bf887f2d8459a3aaba23f15c0087234ae5a54125052e7046e034a4988"
- },
- {
- "name": "hand_with_index_and_middle_fingers_crossed_tone1",
- "unicode": "1F91E-1F3FB",
+ "fingers_crossed_tone1": {
+ "category": "people",
+ "moji": "🤞🏻",
+ "unicodeVersion": "9.0",
"digest": "db56d47bf887f2d8459a3aaba23f15c0087234ae5a54125052e7046e034a4988"
},
- {
- "name": "fingers_crossed_tone2",
- "unicode": "1F91E-1F3FC",
- "digest": "19f1bcca3991db7ed2037278c0baab6cd7f12aeaf2e0074de402c4d9e45c1899"
- },
- {
- "name": "hand_with_index_and_middle_fingers_crossed_tone2",
- "unicode": "1F91E-1F3FC",
+ "fingers_crossed_tone2": {
+ "category": "people",
+ "moji": "🤞🏼",
+ "unicodeVersion": "9.0",
"digest": "19f1bcca3991db7ed2037278c0baab6cd7f12aeaf2e0074de402c4d9e45c1899"
},
- {
- "name": "fingers_crossed_tone3",
- "unicode": "1F91E-1F3FD",
- "digest": "895a3314f6a310f31f7e728bcca20ff834fbfac62ce00e27e3ea5ad0dfc1ba35"
- },
- {
- "name": "hand_with_index_and_middle_fingers_crossed_tone3",
- "unicode": "1F91E-1F3FD",
+ "fingers_crossed_tone3": {
+ "category": "people",
+ "moji": "🤞🏽",
+ "unicodeVersion": "9.0",
"digest": "895a3314f6a310f31f7e728bcca20ff834fbfac62ce00e27e3ea5ad0dfc1ba35"
},
- {
- "name": "fingers_crossed_tone4",
- "unicode": "1F91E-1F3FE",
- "digest": "fcb5c4de2001d23a5df1b8702624d134b7f94e93e2dcc8adf6c1033c77722b0e"
- },
- {
- "name": "hand_with_index_and_middle_fingers_crossed_tone4",
- "unicode": "1F91E-1F3FE",
+ "fingers_crossed_tone4": {
+ "category": "people",
+ "moji": "🤞🏾",
+ "unicodeVersion": "9.0",
"digest": "fcb5c4de2001d23a5df1b8702624d134b7f94e93e2dcc8adf6c1033c77722b0e"
},
- {
- "name": "fingers_crossed_tone5",
- "unicode": "1F91E-1F3FF",
- "digest": "50132c78d530b048c21be4e788b446872a79b3b3a91009db12f4021c44c8469d"
- },
- {
- "name": "hand_with_index_and_middle_fingers_crossed_tone5",
- "unicode": "1F91E-1F3FF",
+ "fingers_crossed_tone5": {
+ "category": "people",
+ "moji": "🤞🏿",
+ "unicodeVersion": "9.0",
"digest": "50132c78d530b048c21be4e788b446872a79b3b3a91009db12f4021c44c8469d"
},
- {
- "name": "fire",
- "unicode": "1F525",
+ "fire": {
+ "category": "nature",
+ "moji": "🔥",
+ "unicodeVersion": "6.0",
"digest": "b3e67c913903d900f5e50e7e7e4d7e9370bb6ceedfbee548be39e4c9e4b69416"
},
- {
- "name": "flame",
- "unicode": "1F525",
- "digest": "b3e67c913903d900f5e50e7e7e4d7e9370bb6ceedfbee548be39e4c9e4b69416"
- },
- {
- "name": "fire_engine",
- "unicode": "1F692",
+ "fire_engine": {
+ "category": "travel",
+ "moji": "🚒",
+ "unicodeVersion": "6.0",
"digest": "c3a518f27d625e3b62dffa227eb82764bf0a147f10ec0e7f4f43f3f96751af20"
},
- {
- "name": "fireworks",
- "unicode": "1F386",
+ "fireworks": {
+ "category": "travel",
+ "moji": "🎆",
+ "unicodeVersion": "6.0",
"digest": "b62ae08a00c0cc6eba8f9666c8fd9946ce57c3cfc01fe99542a8690a4a566a65"
},
- {
- "name": "first_place",
- "unicode": "1F947",
- "digest": "e3de5d9f14f05544dbee5965cc2baa20e7b417a488c8a18598979038860fd901"
- },
- {
- "name": "first_place_medal",
- "unicode": "1F947",
+ "first_place": {
+ "category": "activity",
+ "moji": "🥇",
+ "unicodeVersion": "9.0",
"digest": "e3de5d9f14f05544dbee5965cc2baa20e7b417a488c8a18598979038860fd901"
},
- {
- "name": "first_quarter_moon",
- "unicode": "1F313",
+ "first_quarter_moon": {
+ "category": "nature",
+ "moji": "🌓",
+ "unicodeVersion": "6.0",
"digest": "a207ce93084448622a4a5c49c85c566a9fda6be7337c86a013eeb713fe47fd29"
},
- {
- "name": "first_quarter_moon_with_face",
- "unicode": "1F31B",
+ "first_quarter_moon_with_face": {
+ "category": "nature",
+ "moji": "🌛",
+ "unicodeVersion": "6.0",
"digest": "1d1f54a5075f2311bcc017c44898b9d8c58edc13b298d58c238fff9ab8ee2ef3"
},
- {
- "name": "fish",
- "unicode": "1F41F",
+ "fish": {
+ "category": "nature",
+ "moji": "🐟",
+ "unicodeVersion": "6.0",
"digest": "8f62f08fbeaf39694c19816b5c7d4f292017fe5bf9f8dd7e40f1630f5f83b28b"
},
- {
- "name": "fish_cake",
- "unicode": "1F365",
+ "fish_cake": {
+ "category": "food",
+ "moji": "🍥",
+ "unicodeVersion": "6.0",
"digest": "5a6ca2100c8830927b22afa6f1d2fc821f5692cd23507fe5a776f6e085cbbfb2"
},
- {
- "name": "fishing_pole_and_fish",
- "unicode": "1F3A3",
+ "fishing_pole_and_fish": {
+ "category": "activity",
+ "moji": "🎣",
+ "unicodeVersion": "6.0",
"digest": "f8fb84eccceec88321b0a2a46f732ecfc378f787c19c27ac1327735f1ca9a48b"
},
- {
- "name": "fist",
- "unicode": "270A",
+ "fist": {
+ "category": "people",
+ "moji": "✊",
+ "unicodeVersion": "6.0",
"digest": "557f96d85615b8d78436bc67266115bfc8556c97c14f7909dfda1cf134e8344f"
},
- {
- "name": "fist_tone1",
- "unicode": "270A-1F3FB",
+ "fist_tone1": {
+ "category": "people",
+ "moji": "✊🏻",
+ "unicodeVersion": "8.0",
"digest": "6c1b946f9e01abc39b5085e24e8b6077fc0e34188e8daa30c6a3adddd387413e"
},
- {
- "name": "fist_tone2",
- "unicode": "270A-1F3FC",
+ "fist_tone2": {
+ "category": "people",
+ "moji": "✊🏼",
+ "unicodeVersion": "8.0",
"digest": "e9b9e1ec638dca4d5e1519bca7338f58cce2f2a282ee4c3581e8643166fc415f"
},
- {
- "name": "fist_tone3",
- "unicode": "270A-1F3FD",
+ "fist_tone3": {
+ "category": "people",
+ "moji": "✊🏽",
+ "unicodeVersion": "8.0",
"digest": "8c14d24055c143960b3d2a27fe23c55d2d3ac5f84f87e4e876616235e8698c7f"
},
- {
- "name": "fist_tone4",
- "unicode": "270A-1F3FE",
+ "fist_tone4": {
+ "category": "people",
+ "moji": "✊🏾",
+ "unicodeVersion": "8.0",
"digest": "923f034f481e952e6e5d1664588f99f79bd5416d4197b0ade6621f2669ce5765"
},
- {
- "name": "fist_tone5",
- "unicode": "270A-1F3FF",
+ "fist_tone5": {
+ "category": "people",
+ "moji": "✊🏿",
+ "unicodeVersion": "8.0",
"digest": "d691d2902216080916a29047e07d7a5bf2aed07e062067ca9d01cbf6fdf48c8d"
},
- {
- "name": "five",
- "unicode": "0035-20E3",
+ "five": {
+ "category": "symbols",
+ "moji": "5️⃣",
+ "unicodeVersion": "3.0",
"digest": "8f03f62fdbf744ae49c8a60fbf715ebfccbd6b62d91148e0923907006f3c2726"
},
- {
- "name": "flag_ac",
- "unicode": "1F1E6-1F1E8",
- "digest": "2e5c08535dc8ea96422d56a36b4fffc0b3bd2a13f2ab0d8dbd0e3a29bf3fc40c"
- },
- {
- "name": "ac",
- "unicode": "1F1E6-1F1E8",
+ "flag_ac": {
+ "category": "flags",
+ "moji": "🇦🇨",
+ "unicodeVersion": "6.0",
"digest": "2e5c08535dc8ea96422d56a36b4fffc0b3bd2a13f2ab0d8dbd0e3a29bf3fc40c"
},
- {
- "name": "flag_ad",
- "unicode": "1F1E6-1F1E9",
+ "flag_ad": {
+ "category": "flags",
+ "moji": "🇦🇩",
+ "unicodeVersion": "6.0",
"digest": "184fdcf790b8e2fd851b2b2b32f8636c595dd289734d12dc01ae4aa177e2043a"
},
- {
- "name": "ad",
- "unicode": "1F1E6-1F1E9",
- "digest": "184fdcf790b8e2fd851b2b2b32f8636c595dd289734d12dc01ae4aa177e2043a"
- },
- {
- "name": "flag_ae",
- "unicode": "1F1E6-1F1EA",
- "digest": "4a3257a9ce118e97567e76280f24d60fb555f1bada2eb26a2442a47f9398d21e"
- },
- {
- "name": "ae",
- "unicode": "1F1E6-1F1EA",
+ "flag_ae": {
+ "category": "flags",
+ "moji": "🇦🇪",
+ "unicodeVersion": "6.0",
"digest": "4a3257a9ce118e97567e76280f24d60fb555f1bada2eb26a2442a47f9398d21e"
},
- {
- "name": "flag_af",
- "unicode": "1F1E6-1F1EB",
+ "flag_af": {
+ "category": "flags",
+ "moji": "🇦🇫",
+ "unicodeVersion": "6.0",
"digest": "0f6c719cac7ab3140694f6b580787ecdbf503e38f16de7ec5803f7d06a088ec3"
},
- {
- "name": "af",
- "unicode": "1F1E6-1F1EB",
- "digest": "0f6c719cac7ab3140694f6b580787ecdbf503e38f16de7ec5803f7d06a088ec3"
- },
- {
- "name": "flag_ag",
- "unicode": "1F1E6-1F1EC",
- "digest": "92bf5a0e74564739862e9ba79331ffa656b7bae2ace0fc8dfd288984e4d510d4"
- },
- {
- "name": "ag",
- "unicode": "1F1E6-1F1EC",
+ "flag_ag": {
+ "category": "flags",
+ "moji": "🇦🇬",
+ "unicodeVersion": "6.0",
"digest": "92bf5a0e74564739862e9ba79331ffa656b7bae2ace0fc8dfd288984e4d510d4"
},
- {
- "name": "flag_ai",
- "unicode": "1F1E6-1F1EE",
+ "flag_ai": {
+ "category": "flags",
+ "moji": "🇦🇮",
+ "unicodeVersion": "6.0",
"digest": "aeaadc7ffafd8a1e01fdabc69d35f725d5f737b4c284a36191d96729f4e66e8f"
},
- {
- "name": "ai",
- "unicode": "1F1E6-1F1EE",
- "digest": "aeaadc7ffafd8a1e01fdabc69d35f725d5f737b4c284a36191d96729f4e66e8f"
- },
- {
- "name": "flag_al",
- "unicode": "1F1E6-1F1F1",
- "digest": "5ce7866d214d18c5f3438d480d14e77d104c4de679f0fdfca8cf0a44ce48eeea"
- },
- {
- "name": "al",
- "unicode": "1F1E6-1F1F1",
+ "flag_al": {
+ "category": "flags",
+ "moji": "🇦🇱",
+ "unicodeVersion": "6.0",
"digest": "5ce7866d214d18c5f3438d480d14e77d104c4de679f0fdfca8cf0a44ce48eeea"
},
- {
- "name": "flag_am",
- "unicode": "1F1E6-1F1F2",
+ "flag_am": {
+ "category": "flags",
+ "moji": "🇦🇲",
+ "unicodeVersion": "6.0",
"digest": "b40f5705f0cf9ef0fa7ffff0b371c4099319001ce79f894c317912f4dc5de4c8"
},
- {
- "name": "am",
- "unicode": "1F1E6-1F1F2",
- "digest": "b40f5705f0cf9ef0fa7ffff0b371c4099319001ce79f894c317912f4dc5de4c8"
- },
- {
- "name": "flag_ao",
- "unicode": "1F1E6-1F1F4",
+ "flag_ao": {
+ "category": "flags",
+ "moji": "🇦🇴",
+ "unicodeVersion": "6.0",
"digest": "eab6fbc1824d6e3cd152e8ec1d82e1beaebe02b53b35c6f7a883b8548af02f3a"
},
- {
- "name": "ao",
- "unicode": "1F1E6-1F1F4",
- "digest": "eab6fbc1824d6e3cd152e8ec1d82e1beaebe02b53b35c6f7a883b8548af02f3a"
- },
- {
- "name": "flag_aq",
- "unicode": "1F1E6-1F1F6",
+ "flag_aq": {
+ "category": "flags",
+ "moji": "🇦🇶",
+ "unicodeVersion": "6.0",
"digest": "367f6677a683a5f0e7248ab3a8f46d06ba146a0fd75004c70bac0e913147cdaa"
},
- {
- "name": "aq",
- "unicode": "1F1E6-1F1F6",
- "digest": "367f6677a683a5f0e7248ab3a8f46d06ba146a0fd75004c70bac0e913147cdaa"
- },
- {
- "name": "flag_ar",
- "unicode": "1F1E6-1F1F7",
- "digest": "f0dc466b3216957f2679d7208c2d7cf288448b0739b9270a7c5fa717577bdf25"
- },
- {
- "name": "ar",
- "unicode": "1F1E6-1F1F7",
+ "flag_ar": {
+ "category": "flags",
+ "moji": "🇦🇷",
+ "unicodeVersion": "6.0",
"digest": "f0dc466b3216957f2679d7208c2d7cf288448b0739b9270a7c5fa717577bdf25"
},
- {
- "name": "flag_as",
- "unicode": "1F1E6-1F1F8",
+ "flag_as": {
+ "category": "flags",
+ "moji": "🇦🇸",
+ "unicodeVersion": "6.0",
"digest": "fcb7a865c7763c63b23485cc27207b99a3a8492e83d5b5ee2df259a9f68f77d6"
},
- {
- "name": "as",
- "unicode": "1F1E6-1F1F8",
- "digest": "fcb7a865c7763c63b23485cc27207b99a3a8492e83d5b5ee2df259a9f68f77d6"
- },
- {
- "name": "flag_at",
- "unicode": "1F1E6-1F1F9",
- "digest": "1d3d58e9abc034f9a093a94716eddf9811d54dfaf27969fd322b3809fac70217"
- },
- {
- "name": "at",
- "unicode": "1F1E6-1F1F9",
+ "flag_at": {
+ "category": "flags",
+ "moji": "🇦🇹",
+ "unicodeVersion": "6.0",
"digest": "1d3d58e9abc034f9a093a94716eddf9811d54dfaf27969fd322b3809fac70217"
},
- {
- "name": "flag_au",
- "unicode": "1F1E6-1F1FA",
- "digest": "789563b64c71a5ad49078d335dc166ef614edb56d1e401885d32fb191c198fbd"
- },
- {
- "name": "au",
- "unicode": "1F1E6-1F1FA",
+ "flag_au": {
+ "category": "flags",
+ "moji": "🇦🇺",
+ "unicodeVersion": "6.0",
"digest": "789563b64c71a5ad49078d335dc166ef614edb56d1e401885d32fb191c198fbd"
},
- {
- "name": "flag_aw",
- "unicode": "1F1E6-1F1FC",
- "digest": "1504dc3fd8457b44fdf75c15e136dc46a13e8342d1f98949728cdc1238843e0c"
- },
- {
- "name": "aw",
- "unicode": "1F1E6-1F1FC",
+ "flag_aw": {
+ "category": "flags",
+ "moji": "🇦🇼",
+ "unicodeVersion": "6.0",
"digest": "1504dc3fd8457b44fdf75c15e136dc46a13e8342d1f98949728cdc1238843e0c"
},
- {
- "name": "flag_ax",
- "unicode": "1F1E6-1F1FD",
+ "flag_ax": {
+ "category": "flags",
+ "moji": "🇦🇽",
+ "unicodeVersion": "6.0",
"digest": "e96fa3525f3be25016a4cf8428261735f3ed5fc9fe5b827b461746a3f08877bf"
},
- {
- "name": "ax",
- "unicode": "1F1E6-1F1FD",
- "digest": "e96fa3525f3be25016a4cf8428261735f3ed5fc9fe5b827b461746a3f08877bf"
- },
- {
- "name": "flag_az",
- "unicode": "1F1E6-1F1FF",
- "digest": "12c366ac2c38b91314fb29056e09fa6e7417766cebde3045859cdb127549f4a2"
- },
- {
- "name": "az",
- "unicode": "1F1E6-1F1FF",
+ "flag_az": {
+ "category": "flags",
+ "moji": "🇦🇿",
+ "unicodeVersion": "6.0",
"digest": "12c366ac2c38b91314fb29056e09fa6e7417766cebde3045859cdb127549f4a2"
},
- {
- "name": "flag_ba",
- "unicode": "1F1E7-1F1E6",
- "digest": "0819ea3901510ac20c7f10e67e5f6c818210f17a362c1d12e299c41feb07f828"
- },
- {
- "name": "ba",
- "unicode": "1F1E7-1F1E6",
+ "flag_ba": {
+ "category": "flags",
+ "moji": "🇧🇦",
+ "unicodeVersion": "6.0",
"digest": "0819ea3901510ac20c7f10e67e5f6c818210f17a362c1d12e299c41feb07f828"
},
- {
- "name": "flag_bb",
- "unicode": "1F1E7-1F1E7",
+ "flag_bb": {
+ "category": "flags",
+ "moji": "🇧🇧",
+ "unicodeVersion": "6.0",
"digest": "cf32778a272ed6cbc8e783b59befd9b204009c69c61a425e148d867808b7fab9"
},
- {
- "name": "bb",
- "unicode": "1F1E7-1F1E7",
- "digest": "cf32778a272ed6cbc8e783b59befd9b204009c69c61a425e148d867808b7fab9"
- },
- {
- "name": "flag_bd",
- "unicode": "1F1E7-1F1E9",
- "digest": "e6ed186644a874588e879513aec92f8107220dcdd14c766dee61f266ce045665"
- },
- {
- "name": "bd",
- "unicode": "1F1E7-1F1E9",
+ "flag_bd": {
+ "category": "flags",
+ "moji": "🇧🇩",
+ "unicodeVersion": "6.0",
"digest": "e6ed186644a874588e879513aec92f8107220dcdd14c766dee61f266ce045665"
},
- {
- "name": "flag_be",
- "unicode": "1F1E7-1F1EA",
+ "flag_be": {
+ "category": "flags",
+ "moji": "🇧🇪",
+ "unicodeVersion": "6.0",
"digest": "4d941011d15d9f6e755d6f7694884758baf17ac0691bf5d63700f8d6dbcdb948"
},
- {
- "name": "be",
- "unicode": "1F1E7-1F1EA",
- "digest": "4d941011d15d9f6e755d6f7694884758baf17ac0691bf5d63700f8d6dbcdb948"
- },
- {
- "name": "flag_bf",
- "unicode": "1F1E7-1F1EB",
- "digest": "fcc57dbda9a86f725f558b6c6309484c97e65f1644aae4f9fb5e642681f6c2e0"
- },
- {
- "name": "bf",
- "unicode": "1F1E7-1F1EB",
+ "flag_bf": {
+ "category": "flags",
+ "moji": "🇧🇫",
+ "unicodeVersion": "6.0",
"digest": "fcc57dbda9a86f725f558b6c6309484c97e65f1644aae4f9fb5e642681f6c2e0"
},
- {
- "name": "flag_bg",
- "unicode": "1F1E7-1F1EC",
+ "flag_bg": {
+ "category": "flags",
+ "moji": "🇧🇬",
+ "unicodeVersion": "6.0",
"digest": "816c47ed96c36c90723da150645902ea8ba18b44757fdd776c7b3542cfecfb18"
},
- {
- "name": "bg",
- "unicode": "1F1E7-1F1EC",
- "digest": "816c47ed96c36c90723da150645902ea8ba18b44757fdd776c7b3542cfecfb18"
- },
- {
- "name": "flag_bh",
- "unicode": "1F1E7-1F1ED",
- "digest": "2cd5c21775a6e73f59d08c9ee0cedf4e8241e562eab939573501d47681987737"
- },
- {
- "name": "bh",
- "unicode": "1F1E7-1F1ED",
+ "flag_bh": {
+ "category": "flags",
+ "moji": "🇧🇭",
+ "unicodeVersion": "6.0",
"digest": "2cd5c21775a6e73f59d08c9ee0cedf4e8241e562eab939573501d47681987737"
},
- {
- "name": "flag_bi",
- "unicode": "1F1E7-1F1EE",
+ "flag_bi": {
+ "category": "flags",
+ "moji": "🇧🇮",
+ "unicodeVersion": "6.0",
"digest": "2da82acbec5518360633c1b0b56d55a79b67237f67d92af5e5cd75a2f3bd550e"
},
- {
- "name": "bi",
- "unicode": "1F1E7-1F1EE",
- "digest": "2da82acbec5518360633c1b0b56d55a79b67237f67d92af5e5cd75a2f3bd550e"
- },
- {
- "name": "flag_bj",
- "unicode": "1F1E7-1F1EF",
- "digest": "8fe8c34651eb4e28ab395261a5b72b6f37579535ed676d15de131914e19c0436"
- },
- {
- "name": "bj",
- "unicode": "1F1E7-1F1EF",
+ "flag_bj": {
+ "category": "flags",
+ "moji": "🇧🇯",
+ "unicodeVersion": "6.0",
"digest": "8fe8c34651eb4e28ab395261a5b72b6f37579535ed676d15de131914e19c0436"
},
- {
- "name": "flag_bl",
- "unicode": "1F1E7-1F1F1",
+ "flag_bl": {
+ "category": "flags",
+ "moji": "🇧🇱",
+ "unicodeVersion": "6.0",
"digest": "d37f2a215ee7ef5b5ab62d2a0c87e90553b17c6ee310f803a71e9fd72db880e7"
},
- {
- "name": "bl",
- "unicode": "1F1E7-1F1F1",
- "digest": "d37f2a215ee7ef5b5ab62d2a0c87e90553b17c6ee310f803a71e9fd72db880e7"
- },
- {
- "name": "flag_black",
- "unicode": "1F3F4",
- "digest": "3740bfc9bcb3b46b697b8b7c47ab2c3e95eca9dbcba12f2bf98a01302704f203"
- },
- {
- "name": "waving_black_flag",
- "unicode": "1F3F4",
+ "flag_black": {
+ "category": "objects",
+ "moji": "🏴",
+ "unicodeVersion": "6.0",
"digest": "3740bfc9bcb3b46b697b8b7c47ab2c3e95eca9dbcba12f2bf98a01302704f203"
},
- {
- "name": "flag_bm",
- "unicode": "1F1E7-1F1F2",
+ "flag_bm": {
+ "category": "flags",
+ "moji": "🇧🇲",
+ "unicodeVersion": "6.0",
"digest": "ccd21655573f3c955d616c5c7b1eac2be1d4772ff611648d6713ba55d9e4aa9b"
},
- {
- "name": "bm",
- "unicode": "1F1E7-1F1F2",
- "digest": "ccd21655573f3c955d616c5c7b1eac2be1d4772ff611648d6713ba55d9e4aa9b"
- },
- {
- "name": "flag_bn",
- "unicode": "1F1E7-1F1F3",
- "digest": "54330c3d7a37392e69098c213fd8c78f3faab4e7e5909c039188110422514228"
- },
- {
- "name": "bn",
- "unicode": "1F1E7-1F1F3",
+ "flag_bn": {
+ "category": "flags",
+ "moji": "🇧🇳",
+ "unicodeVersion": "6.0",
"digest": "54330c3d7a37392e69098c213fd8c78f3faab4e7e5909c039188110422514228"
},
- {
- "name": "flag_bo",
- "unicode": "1F1E7-1F1F4",
+ "flag_bo": {
+ "category": "flags",
+ "moji": "🇧🇴",
+ "unicodeVersion": "6.0",
"digest": "32aff973b26f4f91ca19dddd7861b564da43cfbee87603d8c004f1111342366c"
},
- {
- "name": "bo",
- "unicode": "1F1E7-1F1F4",
- "digest": "32aff973b26f4f91ca19dddd7861b564da43cfbee87603d8c004f1111342366c"
- },
- {
- "name": "flag_bq",
- "unicode": "1F1E7-1F1F6",
- "digest": "b1ebc959c43f706ca430d8633d9efaa9c60133871506b5f030b730cfb4c19e6f"
- },
- {
- "name": "bq",
- "unicode": "1F1E7-1F1F6",
+ "flag_bq": {
+ "category": "flags",
+ "moji": "🇧🇶",
+ "unicodeVersion": "6.0",
"digest": "b1ebc959c43f706ca430d8633d9efaa9c60133871506b5f030b730cfb4c19e6f"
},
- {
- "name": "flag_br",
- "unicode": "1F1E7-1F1F7",
+ "flag_br": {
+ "category": "flags",
+ "moji": "🇧🇷",
+ "unicodeVersion": "6.0",
"digest": "64fb154d71fa34ff4838bc405f3e58a4102cf0cb49ca4b06fc3c7a6bf39671f0"
},
- {
- "name": "br",
- "unicode": "1F1E7-1F1F7",
- "digest": "64fb154d71fa34ff4838bc405f3e58a4102cf0cb49ca4b06fc3c7a6bf39671f0"
- },
- {
- "name": "flag_bs",
- "unicode": "1F1E7-1F1F8",
+ "flag_bs": {
+ "category": "flags",
+ "moji": "🇧🇸",
+ "unicodeVersion": "6.0",
"digest": "c4b07e5f652ab06ece95d3774ce8b1399a935f8a28d440cb13cc8bd0b9728ed5"
},
- {
- "name": "bs",
- "unicode": "1F1E7-1F1F8",
- "digest": "c4b07e5f652ab06ece95d3774ce8b1399a935f8a28d440cb13cc8bd0b9728ed5"
- },
- {
- "name": "flag_bt",
- "unicode": "1F1E7-1F1F9",
+ "flag_bt": {
+ "category": "flags",
+ "moji": "🇧🇹",
+ "unicodeVersion": "6.0",
"digest": "901ddbd999dd89a87c1e1208b1470cb4e604a9bc023d0cbcdee64e1bc54079ba"
},
- {
- "name": "bt",
- "unicode": "1F1E7-1F1F9",
- "digest": "901ddbd999dd89a87c1e1208b1470cb4e604a9bc023d0cbcdee64e1bc54079ba"
- },
- {
- "name": "flag_bv",
- "unicode": "1F1E7-1F1FB",
- "digest": "bbf6daa6174c6fbbbf541c8274f31b6757c3a16007c2687015ea041fd1e2c6b6"
- },
- {
- "name": "bv",
- "unicode": "1F1E7-1F1FB",
+ "flag_bv": {
+ "category": "flags",
+ "moji": "🇧🇻",
+ "unicodeVersion": "6.0",
"digest": "bbf6daa6174c6fbbbf541c8274f31b6757c3a16007c2687015ea041fd1e2c6b6"
},
- {
- "name": "flag_bw",
- "unicode": "1F1E7-1F1FC",
+ "flag_bw": {
+ "category": "flags",
+ "moji": "🇧🇼",
+ "unicodeVersion": "6.0",
"digest": "05aa351bc04dc0fe2669441ab500e000d48b1f0d7ad9e885c7abfb898aa0eb3f"
},
- {
- "name": "bw",
- "unicode": "1F1E7-1F1FC",
- "digest": "05aa351bc04dc0fe2669441ab500e000d48b1f0d7ad9e885c7abfb898aa0eb3f"
- },
- {
- "name": "flag_by",
- "unicode": "1F1E7-1F1FE",
- "digest": "6eda3b87336ecf0aae4963986d86b916a055d8268c70520303288f235a93b0d9"
- },
- {
- "name": "by",
- "unicode": "1F1E7-1F1FE",
+ "flag_by": {
+ "category": "flags",
+ "moji": "🇧🇾",
+ "unicodeVersion": "6.0",
"digest": "6eda3b87336ecf0aae4963986d86b916a055d8268c70520303288f235a93b0d9"
},
- {
- "name": "flag_bz",
- "unicode": "1F1E7-1F1FF",
- "digest": "d76ed945b1408558a30a99b8eed6712de968fc49fba1721b5660b8f48087e45a"
- },
- {
- "name": "bz",
- "unicode": "1F1E7-1F1FF",
+ "flag_bz": {
+ "category": "flags",
+ "moji": "🇧🇿",
+ "unicodeVersion": "6.0",
"digest": "d76ed945b1408558a30a99b8eed6712de968fc49fba1721b5660b8f48087e45a"
},
- {
- "name": "flag_ca",
- "unicode": "1F1E8-1F1E6",
- "digest": "2fd036047d89751c05de5577909b58347883bc89c3b7d90bec28ad4770a98ecd"
- },
- {
- "name": "ca",
- "unicode": "1F1E8-1F1E6",
+ "flag_ca": {
+ "category": "flags",
+ "moji": "🇨🇦",
+ "unicodeVersion": "6.0",
"digest": "2fd036047d89751c05de5577909b58347883bc89c3b7d90bec28ad4770a98ecd"
},
- {
- "name": "flag_cc",
- "unicode": "1F1E8-1F1E8",
+ "flag_cc": {
+ "category": "flags",
+ "moji": "🇨🇨",
+ "unicodeVersion": "6.0",
"digest": "837ba181a01c71f05d438d205efaaee99f93b2370c97b13e6132f99860323e36"
},
- {
- "name": "cc",
- "unicode": "1F1E8-1F1E8",
- "digest": "837ba181a01c71f05d438d205efaaee99f93b2370c97b13e6132f99860323e36"
- },
- {
- "name": "flag_cd",
- "unicode": "1F1E8-1F1E9",
- "digest": "318689274b4b3b58aed7fc1654127499a9da69bff1b83e592e86e69d167ce16f"
- },
- {
- "name": "congo",
- "unicode": "1F1E8-1F1E9",
+ "flag_cd": {
+ "category": "flags",
+ "moji": "🇨🇩",
+ "unicodeVersion": "6.0",
"digest": "318689274b4b3b58aed7fc1654127499a9da69bff1b83e592e86e69d167ce16f"
},
- {
- "name": "flag_cf",
- "unicode": "1F1E8-1F1EB",
- "digest": "06d6042849d3b7b217c2b18ba787aae449e8c7d2537e2e5974744ec196062228"
- },
- {
- "name": "cf",
- "unicode": "1F1E8-1F1EB",
+ "flag_cf": {
+ "category": "flags",
+ "moji": "🇨🇫",
+ "unicodeVersion": "6.0",
"digest": "06d6042849d3b7b217c2b18ba787aae449e8c7d2537e2e5974744ec196062228"
},
- {
- "name": "flag_cg",
- "unicode": "1F1E8-1F1EC",
- "digest": "09f45d2dcb5a24d8349ef86e7405cc29ef3d65a908c0bff3221c3b4546547813"
- },
- {
- "name": "cg",
- "unicode": "1F1E8-1F1EC",
+ "flag_cg": {
+ "category": "flags",
+ "moji": "🇨🇬",
+ "unicodeVersion": "6.0",
"digest": "09f45d2dcb5a24d8349ef86e7405cc29ef3d65a908c0bff3221c3b4546547813"
},
- {
- "name": "flag_ch",
- "unicode": "1F1E8-1F1ED",
- "digest": "53d6d35aeeebb0b4b1ad858dc3691e649ac73d30b3be76f96d5fe9605fa99386"
- },
- {
- "name": "ch",
- "unicode": "1F1E8-1F1ED",
+ "flag_ch": {
+ "category": "flags",
+ "moji": "🇨🇭",
+ "unicodeVersion": "6.0",
"digest": "53d6d35aeeebb0b4b1ad858dc3691e649ac73d30b3be76f96d5fe9605fa99386"
},
- {
- "name": "flag_ci",
- "unicode": "1F1E8-1F1EE",
- "digest": "7d85a0c314b7397c9397a54ce2f3a4dc5f40d0234e586dbd8a541a8666f0f51e"
- },
- {
- "name": "ci",
- "unicode": "1F1E8-1F1EE",
+ "flag_ci": {
+ "category": "flags",
+ "moji": "🇨🇮",
+ "unicodeVersion": "6.0",
"digest": "7d85a0c314b7397c9397a54ce2f3a4dc5f40d0234e586dbd8a541a8666f0f51e"
},
- {
- "name": "flag_ck",
- "unicode": "1F1E8-1F1F0",
- "digest": "c1aa105fe106ed09ed59a596859a0ce4e65a415c59f63df51961491cb947b136"
- },
- {
- "name": "ck",
- "unicode": "1F1E8-1F1F0",
+ "flag_ck": {
+ "category": "flags",
+ "moji": "🇨🇰",
+ "unicodeVersion": "6.0",
"digest": "c1aa105fe106ed09ed59a596859a0ce4e65a415c59f63df51961491cb947b136"
},
- {
- "name": "flag_cl",
- "unicode": "1F1E8-1F1F1",
- "digest": "0fffdad0d892f5c08aaa332af1ed2c228583d89a43190e979a3c3cb020d5a723"
- },
- {
- "name": "chile",
- "unicode": "1F1E8-1F1F1",
+ "flag_cl": {
+ "category": "flags",
+ "moji": "🇨🇱",
+ "unicodeVersion": "6.0",
"digest": "0fffdad0d892f5c08aaa332af1ed2c228583d89a43190e979a3c3cb020d5a723"
},
- {
- "name": "flag_cm",
- "unicode": "1F1E8-1F1F2",
- "digest": "e9f55e41a1fd2735a82ad7a7ac39326a944cb20423ffba3608ac53a46036caad"
- },
- {
- "name": "cm",
- "unicode": "1F1E8-1F1F2",
+ "flag_cm": {
+ "category": "flags",
+ "moji": "🇨🇲",
+ "unicodeVersion": "6.0",
"digest": "e9f55e41a1fd2735a82ad7a7ac39326a944cb20423ffba3608ac53a46036caad"
},
- {
- "name": "flag_cn",
- "unicode": "1F1E8-1F1F3",
- "digest": "e2c8fee7e3bd51b13d6083d5bf344abe6b9b642e3cbb099d38b4ce341c99d890"
- },
- {
- "name": "cn",
- "unicode": "1F1E8-1F1F3",
+ "flag_cn": {
+ "category": "flags",
+ "moji": "🇨🇳",
+ "unicodeVersion": "6.0",
"digest": "e2c8fee7e3bd51b13d6083d5bf344abe6b9b642e3cbb099d38b4ce341c99d890"
},
- {
- "name": "flag_co",
- "unicode": "1F1E8-1F1F4",
- "digest": "51c60d0979bf8342eaff7cda9faf4b0dfab38efaf5ddf3717eb8f0e2a595b15f"
- },
- {
- "name": "co",
- "unicode": "1F1E8-1F1F4",
+ "flag_co": {
+ "category": "flags",
+ "moji": "🇨🇴",
+ "unicodeVersion": "6.0",
"digest": "51c60d0979bf8342eaff7cda9faf4b0dfab38efaf5ddf3717eb8f0e2a595b15f"
},
- {
- "name": "flag_cp",
- "unicode": "1F1E8-1F1F5",
+ "flag_cp": {
+ "category": "flags",
+ "moji": "🇨🇵",
+ "unicodeVersion": "6.0",
"digest": "a0124683aa88cd7da886da70c65796c5ad84eb3751e356e9b2aa8ac249cf0bf9"
},
- {
- "name": "cp",
- "unicode": "1F1E8-1F1F5",
- "digest": "a0124683aa88cd7da886da70c65796c5ad84eb3751e356e9b2aa8ac249cf0bf9"
- },
- {
- "name": "flag_cr",
- "unicode": "1F1E8-1F1F7",
- "digest": "907905971b219e617a34eef4839b0bd08d98f3480e2631bce523120dcef95196"
- },
- {
- "name": "cr",
- "unicode": "1F1E8-1F1F7",
+ "flag_cr": {
+ "category": "flags",
+ "moji": "🇨🇷",
+ "unicodeVersion": "6.0",
"digest": "907905971b219e617a34eef4839b0bd08d98f3480e2631bce523120dcef95196"
},
- {
- "name": "flag_cu",
- "unicode": "1F1E8-1F1FA",
+ "flag_cu": {
+ "category": "flags",
+ "moji": "🇨🇺",
+ "unicodeVersion": "6.0",
"digest": "d88cea729dc9dbbbcadac0409ec561995f061b2280577c01c6c6b37de347f150"
},
- {
- "name": "cu",
- "unicode": "1F1E8-1F1FA",
- "digest": "d88cea729dc9dbbbcadac0409ec561995f061b2280577c01c6c6b37de347f150"
- },
- {
- "name": "flag_cv",
- "unicode": "1F1E8-1F1FB",
- "digest": "5ce97944adfce09e96387e6f872256482ac99ccbc60017c4d58ddd15b6fb67a7"
- },
- {
- "name": "cv",
- "unicode": "1F1E8-1F1FB",
+ "flag_cv": {
+ "category": "flags",
+ "moji": "🇨🇻",
+ "unicodeVersion": "6.0",
"digest": "5ce97944adfce09e96387e6f872256482ac99ccbc60017c4d58ddd15b6fb67a7"
},
- {
- "name": "flag_cw",
- "unicode": "1F1E8-1F1FC",
+ "flag_cw": {
+ "category": "flags",
+ "moji": "🇨🇼",
+ "unicodeVersion": "6.0",
"digest": "a6fc31bd66ddc2ee8e7bde3aeabfe1c4ad00c9688abae234a541cc1236d68c1b"
},
- {
- "name": "cw",
- "unicode": "1F1E8-1F1FC",
- "digest": "a6fc31bd66ddc2ee8e7bde3aeabfe1c4ad00c9688abae234a541cc1236d68c1b"
- },
- {
- "name": "flag_cx",
- "unicode": "1F1E8-1F1FD",
- "digest": "1261b32bfa22fa1441f5390ff499ac6b921d7ac59cc8acda3deb3a2beb4fb345"
- },
- {
- "name": "cx",
- "unicode": "1F1E8-1F1FD",
+ "flag_cx": {
+ "category": "flags",
+ "moji": "🇨🇽",
+ "unicodeVersion": "6.0",
"digest": "1261b32bfa22fa1441f5390ff499ac6b921d7ac59cc8acda3deb3a2beb4fb345"
},
- {
- "name": "flag_cy",
- "unicode": "1F1E8-1F1FE",
+ "flag_cy": {
+ "category": "flags",
+ "moji": "🇨🇾",
+ "unicodeVersion": "6.0",
"digest": "82b1baa05ecffa0ea1f9a83b518163cbd7910985a21955740520bb16b7bb624f"
},
- {
- "name": "cy",
- "unicode": "1F1E8-1F1FE",
- "digest": "82b1baa05ecffa0ea1f9a83b518163cbd7910985a21955740520bb16b7bb624f"
- },
- {
- "name": "flag_cz",
- "unicode": "1F1E8-1F1FF",
- "digest": "a169b18968992a52299b67c24fba495e84de28dec2ebb947a08e0d615ac54a5a"
- },
- {
- "name": "cz",
- "unicode": "1F1E8-1F1FF",
+ "flag_cz": {
+ "category": "flags",
+ "moji": "🇨🇿",
+ "unicodeVersion": "6.0",
"digest": "a169b18968992a52299b67c24fba495e84de28dec2ebb947a08e0d615ac54a5a"
},
- {
- "name": "flag_de",
- "unicode": "1F1E9-1F1EA",
+ "flag_de": {
+ "category": "flags",
+ "moji": "🇩🇪",
+ "unicodeVersion": "6.0",
"digest": "99d1906944966a188c72ae592362ed907e2a0bfe95263955c34a0941507b30c1"
},
- {
- "name": "de",
- "unicode": "1F1E9-1F1EA",
- "digest": "99d1906944966a188c72ae592362ed907e2a0bfe95263955c34a0941507b30c1"
- },
- {
- "name": "flag_dg",
- "unicode": "1F1E9-1F1EC",
- "digest": "dd45e1afe792fca57d4161434bf611bcb7170072d63e4a27fb9dcd6e8912621e"
- },
- {
- "name": "dg",
- "unicode": "1F1E9-1F1EC",
+ "flag_dg": {
+ "category": "flags",
+ "moji": "🇩🇬",
+ "unicodeVersion": "6.0",
"digest": "dd45e1afe792fca57d4161434bf611bcb7170072d63e4a27fb9dcd6e8912621e"
},
- {
- "name": "flag_dj",
- "unicode": "1F1E9-1F1EF",
+ "flag_dj": {
+ "category": "flags",
+ "moji": "🇩🇯",
+ "unicodeVersion": "6.0",
"digest": "e90ba4e98fca71ff0ca5e65c28b911cc52f043428f375d8f954ecbd3b0c8f4dd"
},
- {
- "name": "dj",
- "unicode": "1F1E9-1F1EF",
- "digest": "e90ba4e98fca71ff0ca5e65c28b911cc52f043428f375d8f954ecbd3b0c8f4dd"
- },
- {
- "name": "flag_dk",
- "unicode": "1F1E9-1F1F0",
- "digest": "65b3b5f31935a4969d81fedbb8279c7ad32da454d15c5eafcceba5d140927c77"
- },
- {
- "name": "dk",
- "unicode": "1F1E9-1F1F0",
+ "flag_dk": {
+ "category": "flags",
+ "moji": "🇩🇰",
+ "unicodeVersion": "6.0",
"digest": "65b3b5f31935a4969d81fedbb8279c7ad32da454d15c5eafcceba5d140927c77"
},
- {
- "name": "flag_dm",
- "unicode": "1F1E9-1F1F2",
+ "flag_dm": {
+ "category": "flags",
+ "moji": "🇩🇲",
+ "unicodeVersion": "6.0",
"digest": "f6225ded6d2cfd6c182ab1a53b8c49dc9df195df11eb7ff27b15f5d3721ba0eb"
},
- {
- "name": "dm",
- "unicode": "1F1E9-1F1F2",
- "digest": "f6225ded6d2cfd6c182ab1a53b8c49dc9df195df11eb7ff27b15f5d3721ba0eb"
- },
- {
- "name": "flag_do",
- "unicode": "1F1E9-1F1F4",
- "digest": "dc2ad6856cebbe47c5bd7f5dcf087e4f680d396b2d49440a9b71f0ad49fb8102"
- },
- {
- "name": "do",
- "unicode": "1F1E9-1F1F4",
+ "flag_do": {
+ "category": "flags",
+ "moji": "🇩🇴",
+ "unicodeVersion": "6.0",
"digest": "dc2ad6856cebbe47c5bd7f5dcf087e4f680d396b2d49440a9b71f0ad49fb8102"
},
- {
- "name": "flag_dz",
- "unicode": "1F1E9-1F1FF",
+ "flag_dz": {
+ "category": "flags",
+ "moji": "🇩🇿",
+ "unicodeVersion": "6.0",
"digest": "ea69fffc4d545f9c0fcef6768257501952955ba4d274c9b81843229a1265c5ed"
},
- {
- "name": "dz",
- "unicode": "1F1E9-1F1FF",
- "digest": "ea69fffc4d545f9c0fcef6768257501952955ba4d274c9b81843229a1265c5ed"
- },
- {
- "name": "flag_ea",
- "unicode": "1F1EA-1F1E6",
+ "flag_ea": {
+ "category": "flags",
+ "moji": "🇪🇦",
+ "unicodeVersion": "6.0",
"digest": "e63bfe15428c481dd23b569e7aaf0a76106e58a946995b4415a81097ecd53b7d"
},
- {
- "name": "ea",
- "unicode": "1F1EA-1F1E6",
- "digest": "e63bfe15428c481dd23b569e7aaf0a76106e58a946995b4415a81097ecd53b7d"
- },
- {
- "name": "flag_ec",
- "unicode": "1F1EA-1F1E8",
+ "flag_ec": {
+ "category": "flags",
+ "moji": "🇪🇨",
+ "unicodeVersion": "6.0",
"digest": "0cdabf85cd567047fda1d9a4508220cab829943a7c542c315078db0aac33edac"
},
- {
- "name": "ec",
- "unicode": "1F1EA-1F1E8",
- "digest": "0cdabf85cd567047fda1d9a4508220cab829943a7c542c315078db0aac33edac"
- },
- {
- "name": "flag_ee",
- "unicode": "1F1EA-1F1EA",
- "digest": "6dc4e3377e8e2af3ff40cf940a914bc7840980b4a14e7da86954343f2b1025fe"
- },
- {
- "name": "ee",
- "unicode": "1F1EA-1F1EA",
+ "flag_ee": {
+ "category": "flags",
+ "moji": "🇪🇪",
+ "unicodeVersion": "6.0",
"digest": "6dc4e3377e8e2af3ff40cf940a914bc7840980b4a14e7da86954343f2b1025fe"
},
- {
- "name": "flag_eg",
- "unicode": "1F1EA-1F1EC",
+ "flag_eg": {
+ "category": "flags",
+ "moji": "🇪🇬",
+ "unicodeVersion": "6.0",
"digest": "2ed6bc056015694d75993eb5ee3c1850921d5630681207b04dfbdb982ab346a2"
},
- {
- "name": "eg",
- "unicode": "1F1EA-1F1EC",
- "digest": "2ed6bc056015694d75993eb5ee3c1850921d5630681207b04dfbdb982ab346a2"
- },
- {
- "name": "flag_eh",
- "unicode": "1F1EA-1F1ED",
- "digest": "72adb55943e4df99c00843c65463718609d937480f73dcf4a4451d46b9967a5e"
- },
- {
- "name": "eh",
- "unicode": "1F1EA-1F1ED",
+ "flag_eh": {
+ "category": "flags",
+ "moji": "🇪🇭",
+ "unicodeVersion": "6.0",
"digest": "72adb55943e4df99c00843c65463718609d937480f73dcf4a4451d46b9967a5e"
},
- {
- "name": "flag_er",
- "unicode": "1F1EA-1F1F7",
- "digest": "3fa59331eb5300c8c1f7b1f1bc15cfcfe688da6fa4a79341854598086a44eebc"
- },
- {
- "name": "er",
- "unicode": "1F1EA-1F1F7",
+ "flag_er": {
+ "category": "flags",
+ "moji": "🇪🇷",
+ "unicodeVersion": "6.0",
"digest": "3fa59331eb5300c8c1f7b1f1bc15cfcfe688da6fa4a79341854598086a44eebc"
},
- {
- "name": "flag_es",
- "unicode": "1F1EA-1F1F8",
- "digest": "1fa1d5cb0a7e8b14aaec758b2e7bf49cdf8f3d09bbcc7dfd589053a432eeae25"
- },
- {
- "name": "es",
- "unicode": "1F1EA-1F1F8",
+ "flag_es": {
+ "category": "flags",
+ "moji": "🇪🇸",
+ "unicodeVersion": "6.0",
"digest": "1fa1d5cb0a7e8b14aaec758b2e7bf49cdf8f3d09bbcc7dfd589053a432eeae25"
},
- {
- "name": "flag_et",
- "unicode": "1F1EA-1F1F9",
- "digest": "72771decfb214394e4beb594e848ea590c3615800adbba24b5df4c5db6ee9617"
- },
- {
- "name": "et",
- "unicode": "1F1EA-1F1F9",
+ "flag_et": {
+ "category": "flags",
+ "moji": "🇪🇹",
+ "unicodeVersion": "6.0",
"digest": "72771decfb214394e4beb594e848ea590c3615800adbba24b5df4c5db6ee9617"
},
- {
- "name": "flag_eu",
- "unicode": "1F1EA-1F1FA",
- "digest": "4bfa1b2ef23764ead5ef7899806f93e13fd29a09c75e61431579a4116c836aa4"
- },
- {
- "name": "eu",
- "unicode": "1F1EA-1F1FA",
+ "flag_eu": {
+ "category": "flags",
+ "moji": "🇪🇺",
+ "unicodeVersion": "6.0",
"digest": "4bfa1b2ef23764ead5ef7899806f93e13fd29a09c75e61431579a4116c836aa4"
},
- {
- "name": "flag_fi",
- "unicode": "1F1EB-1F1EE",
- "digest": "d0208cdd5b153a2865f9f674179c62871d4675abb0fb639fba88fcd62553f54e"
- },
- {
- "name": "fi",
- "unicode": "1F1EB-1F1EE",
+ "flag_fi": {
+ "category": "flags",
+ "moji": "🇫🇮",
+ "unicodeVersion": "6.0",
"digest": "d0208cdd5b153a2865f9f674179c62871d4675abb0fb639fba88fcd62553f54e"
},
- {
- "name": "flag_fj",
- "unicode": "1F1EB-1F1EF",
+ "flag_fj": {
+ "category": "flags",
+ "moji": "🇫🇯",
+ "unicodeVersion": "6.0",
"digest": "6c5ec41114af3846b093a418f6e2b5ff7a83cb72cecde75a7dc62e8cb6dcfe45"
},
- {
- "name": "fj",
- "unicode": "1F1EB-1F1EF",
- "digest": "6c5ec41114af3846b093a418f6e2b5ff7a83cb72cecde75a7dc62e8cb6dcfe45"
- },
- {
- "name": "flag_fk",
- "unicode": "1F1EB-1F1F0",
- "digest": "c69ad641d53785deff5c3934b7dcfcd3dc32ffc31b6d3e799d0555b03c23fc15"
- },
- {
- "name": "fk",
- "unicode": "1F1EB-1F1F0",
+ "flag_fk": {
+ "category": "flags",
+ "moji": "🇫🇰",
+ "unicodeVersion": "6.0",
"digest": "c69ad641d53785deff5c3934b7dcfcd3dc32ffc31b6d3e799d0555b03c23fc15"
},
- {
- "name": "flag_fm",
- "unicode": "1F1EB-1F1F2",
+ "flag_fm": {
+ "category": "flags",
+ "moji": "🇫🇲",
+ "unicodeVersion": "6.0",
"digest": "1e29fb06b273f253c23a9e4aa8ff84bfe22cffb5fa158a0c6f4cdeabe0216990"
},
- {
- "name": "fm",
- "unicode": "1F1EB-1F1F2",
- "digest": "1e29fb06b273f253c23a9e4aa8ff84bfe22cffb5fa158a0c6f4cdeabe0216990"
- },
- {
- "name": "flag_fo",
- "unicode": "1F1EB-1F1F4",
- "digest": "f4907d2f606f4f9d3bef06c6d38e8e88f2a148197b1573668866431a007afc2e"
- },
- {
- "name": "fo",
- "unicode": "1F1EB-1F1F4",
+ "flag_fo": {
+ "category": "flags",
+ "moji": "🇫🇴",
+ "unicodeVersion": "6.0",
"digest": "f4907d2f606f4f9d3bef06c6d38e8e88f2a148197b1573668866431a007afc2e"
},
- {
- "name": "flag_fr",
- "unicode": "1F1EB-1F1F7",
+ "flag_fr": {
+ "category": "flags",
+ "moji": "🇫🇷",
+ "unicodeVersion": "6.0",
"digest": "5a1308ab3cbf6bffcab12588cf3325151a6c72990db7408c2b8605d89f94ed6e"
},
- {
- "name": "fr",
- "unicode": "1F1EB-1F1F7",
- "digest": "5a1308ab3cbf6bffcab12588cf3325151a6c72990db7408c2b8605d89f94ed6e"
- },
- {
- "name": "flag_ga",
- "unicode": "1F1EC-1F1E6",
- "digest": "ddc32dee2976507be878ec3d3d2408632ca21bc434cd9f58db4f6ac9774a2db5"
- },
- {
- "name": "ga",
- "unicode": "1F1EC-1F1E6",
+ "flag_ga": {
+ "category": "flags",
+ "moji": "🇬🇦",
+ "unicodeVersion": "6.0",
"digest": "ddc32dee2976507be878ec3d3d2408632ca21bc434cd9f58db4f6ac9774a2db5"
},
- {
- "name": "flag_gb",
- "unicode": "1F1EC-1F1E7",
+ "flag_gb": {
+ "category": "flags",
+ "moji": "🇬🇧",
+ "unicodeVersion": "6.0",
"digest": "6b3bb254d134870b02cb066b06e206f652638a915c84b8649ceb30ec67fbebde"
},
- {
- "name": "gb",
- "unicode": "1F1EC-1F1E7",
- "digest": "6b3bb254d134870b02cb066b06e206f652638a915c84b8649ceb30ec67fbebde"
- },
- {
- "name": "flag_gd",
- "unicode": "1F1EC-1F1E9",
+ "flag_gd": {
+ "category": "flags",
+ "moji": "🇬🇩",
+ "unicodeVersion": "6.0",
"digest": "b6a210541ca22d816405f2a7d0d5241dc4d5488c8a36e15bd1e3063f9c41327f"
},
- {
- "name": "gd",
- "unicode": "1F1EC-1F1E9",
- "digest": "b6a210541ca22d816405f2a7d0d5241dc4d5488c8a36e15bd1e3063f9c41327f"
- },
- {
- "name": "flag_ge",
- "unicode": "1F1EC-1F1EA",
+ "flag_ge": {
+ "category": "flags",
+ "moji": "🇬🇪",
+ "unicodeVersion": "6.0",
"digest": "e9a5035b7a46b925737e7f7b0ae2419cc4af0e980fbee5bd916edeef13823367"
},
- {
- "name": "ge",
- "unicode": "1F1EC-1F1EA",
- "digest": "e9a5035b7a46b925737e7f7b0ae2419cc4af0e980fbee5bd916edeef13823367"
- },
- {
- "name": "flag_gf",
- "unicode": "1F1EC-1F1EB",
- "digest": "ce1bcd8c303897c1c22c5994182f21240b4aa635f0d7ce9944f76cbdbf0e4956"
- },
- {
- "name": "gf",
- "unicode": "1F1EC-1F1EB",
+ "flag_gf": {
+ "category": "flags",
+ "moji": "🇬🇫",
+ "unicodeVersion": "6.0",
"digest": "ce1bcd8c303897c1c22c5994182f21240b4aa635f0d7ce9944f76cbdbf0e4956"
},
- {
- "name": "flag_gg",
- "unicode": "1F1EC-1F1EC",
+ "flag_gg": {
+ "category": "flags",
+ "moji": "🇬🇬",
+ "unicodeVersion": "6.0",
"digest": "a435aab3609533ab2d68acd97deba844bfb0fc27b2adac68668223011f23ae5d"
},
- {
- "name": "gg",
- "unicode": "1F1EC-1F1EC",
- "digest": "a435aab3609533ab2d68acd97deba844bfb0fc27b2adac68668223011f23ae5d"
- },
- {
- "name": "flag_gh",
- "unicode": "1F1EC-1F1ED",
- "digest": "7cad43b40f69b9b00cc1b38036789ce774fd3d597c89f0bf38433847ea69be26"
- },
- {
- "name": "gh",
- "unicode": "1F1EC-1F1ED",
+ "flag_gh": {
+ "category": "flags",
+ "moji": "🇬🇭",
+ "unicodeVersion": "6.0",
"digest": "7cad43b40f69b9b00cc1b38036789ce774fd3d597c89f0bf38433847ea69be26"
},
- {
- "name": "flag_gi",
- "unicode": "1F1EC-1F1EE",
- "digest": "70e9b17d18bf3e0e4d03f4f824323a57909416e4082ca9d8a0796a6959de4f07"
- },
- {
- "name": "gi",
- "unicode": "1F1EC-1F1EE",
+ "flag_gi": {
+ "category": "flags",
+ "moji": "🇬🇮",
+ "unicodeVersion": "6.0",
"digest": "70e9b17d18bf3e0e4d03f4f824323a57909416e4082ca9d8a0796a6959de4f07"
},
- {
- "name": "flag_gl",
- "unicode": "1F1EC-1F1F1",
- "digest": "1963d8cca1c1f06b7536b7fb8f5a4782ac0bb05afdf6e481101bce45c58cdd4b"
- },
- {
- "name": "gl",
- "unicode": "1F1EC-1F1F1",
+ "flag_gl": {
+ "category": "flags",
+ "moji": "🇬🇱",
+ "unicodeVersion": "6.0",
"digest": "1963d8cca1c1f06b7536b7fb8f5a4782ac0bb05afdf6e481101bce45c58cdd4b"
},
- {
- "name": "flag_gm",
- "unicode": "1F1EC-1F1F2",
+ "flag_gm": {
+ "category": "flags",
+ "moji": "🇬🇲",
+ "unicodeVersion": "6.0",
"digest": "6c776a8daa3f4daa2597b0025aec06fc0a53aed262e845d4da3897cd7a89c6a1"
},
- {
- "name": "gm",
- "unicode": "1F1EC-1F1F2",
- "digest": "6c776a8daa3f4daa2597b0025aec06fc0a53aed262e845d4da3897cd7a89c6a1"
- },
- {
- "name": "flag_gn",
- "unicode": "1F1EC-1F1F3",
- "digest": "134cf7c839370d171ae80a72e5d18d32ea1967df19c191d1a4ea446d649e9558"
- },
- {
- "name": "gn",
- "unicode": "1F1EC-1F1F3",
+ "flag_gn": {
+ "category": "flags",
+ "moji": "🇬🇳",
+ "unicodeVersion": "6.0",
"digest": "134cf7c839370d171ae80a72e5d18d32ea1967df19c191d1a4ea446d649e9558"
},
- {
- "name": "flag_gp",
- "unicode": "1F1EC-1F1F5",
- "digest": "be3e906b039ba4884053c78f4f14de9aa87c5573860ccb69ec766068ae3887c2"
- },
- {
- "name": "gp",
- "unicode": "1F1EC-1F1F5",
+ "flag_gp": {
+ "category": "flags",
+ "moji": "🇬🇵",
+ "unicodeVersion": "6.0",
"digest": "be3e906b039ba4884053c78f4f14de9aa87c5573860ccb69ec766068ae3887c2"
},
- {
- "name": "flag_gq",
- "unicode": "1F1EC-1F1F6",
- "digest": "d476059c4ab41f5a1ef88583087362a5bc57cede930126f37041d1546564ab70"
- },
- {
- "name": "gq",
- "unicode": "1F1EC-1F1F6",
+ "flag_gq": {
+ "category": "flags",
+ "moji": "🇬🇶",
+ "unicodeVersion": "6.0",
"digest": "d476059c4ab41f5a1ef88583087362a5bc57cede930126f37041d1546564ab70"
},
- {
- "name": "flag_gr",
- "unicode": "1F1EC-1F1F7",
- "digest": "b9fa9304647aaa08167a07858bb18d778dcc399375f86f580b8d4244794678bc"
- },
- {
- "name": "gr",
- "unicode": "1F1EC-1F1F7",
+ "flag_gr": {
+ "category": "flags",
+ "moji": "🇬🇷",
+ "unicodeVersion": "6.0",
"digest": "b9fa9304647aaa08167a07858bb18d778dcc399375f86f580b8d4244794678bc"
},
- {
- "name": "flag_gs",
- "unicode": "1F1EC-1F1F8",
- "digest": "de33fbef6e294eb7af36e5b94d8ff573b354a4ff1ebdccf50ca528b86ed601d9"
- },
- {
- "name": "gs",
- "unicode": "1F1EC-1F1F8",
+ "flag_gs": {
+ "category": "flags",
+ "moji": "🇬🇸",
+ "unicodeVersion": "6.0",
"digest": "de33fbef6e294eb7af36e5b94d8ff573b354a4ff1ebdccf50ca528b86ed601d9"
},
- {
- "name": "flag_gt",
- "unicode": "1F1EC-1F1F9",
- "digest": "4160843e5d642df597c8423eb8e3b74deafe304f3d141c8a4d2fc07509e44832"
- },
- {
- "name": "gt",
- "unicode": "1F1EC-1F1F9",
+ "flag_gt": {
+ "category": "flags",
+ "moji": "🇬🇹",
+ "unicodeVersion": "6.0",
"digest": "4160843e5d642df597c8423eb8e3b74deafe304f3d141c8a4d2fc07509e44832"
},
- {
- "name": "flag_gu",
- "unicode": "1F1EC-1F1FA",
- "digest": "3b0cb257ba5b1c3e15d9102410c5f7418da03372e91ce90513de25b9f45283e3"
- },
- {
- "name": "gu",
- "unicode": "1F1EC-1F1FA",
+ "flag_gu": {
+ "category": "flags",
+ "moji": "🇬🇺",
+ "unicodeVersion": "6.0",
"digest": "3b0cb257ba5b1c3e15d9102410c5f7418da03372e91ce90513de25b9f45283e3"
},
- {
- "name": "flag_gw",
- "unicode": "1F1EC-1F1FC",
+ "flag_gw": {
+ "category": "flags",
+ "moji": "🇬🇼",
+ "unicodeVersion": "6.0",
"digest": "bdf07a8f93c0f0a573af5f5361be404a3ba65b729c1a4c05b7632c03d85efc72"
},
- {
- "name": "gw",
- "unicode": "1F1EC-1F1FC",
- "digest": "bdf07a8f93c0f0a573af5f5361be404a3ba65b729c1a4c05b7632c03d85efc72"
- },
- {
- "name": "flag_gy",
- "unicode": "1F1EC-1F1FE",
- "digest": "b47d8c98b747556f827ad0d1169264eb68ecaf9d2fb76595e8c31866361cbfc6"
- },
- {
- "name": "gy",
- "unicode": "1F1EC-1F1FE",
+ "flag_gy": {
+ "category": "flags",
+ "moji": "🇬🇾",
+ "unicodeVersion": "6.0",
"digest": "b47d8c98b747556f827ad0d1169264eb68ecaf9d2fb76595e8c31866361cbfc6"
},
- {
- "name": "flag_hk",
- "unicode": "1F1ED-1F1F0",
- "digest": "8e5a54b2e4bd4f5182085299b9648062463da05d535cf0e46a7d9c58eaeb171f"
- },
- {
- "name": "hk",
- "unicode": "1F1ED-1F1F0",
+ "flag_hk": {
+ "category": "flags",
+ "moji": "🇭🇰",
+ "unicodeVersion": "6.0",
"digest": "8e5a54b2e4bd4f5182085299b9648062463da05d535cf0e46a7d9c58eaeb171f"
},
- {
- "name": "flag_hm",
- "unicode": "1F1ED-1F1F2",
+ "flag_hm": {
+ "category": "flags",
+ "moji": "🇭🇲",
+ "unicodeVersion": "6.0",
"digest": "63c3e080c5e82a72c6d4cf5997ac823dc02184719ec59aadea6dd41b127abf22"
},
- {
- "name": "hm",
- "unicode": "1F1ED-1F1F2",
- "digest": "63c3e080c5e82a72c6d4cf5997ac823dc02184719ec59aadea6dd41b127abf22"
- },
- {
- "name": "flag_hn",
- "unicode": "1F1ED-1F1F3",
- "digest": "87c1d160db810b5ed208fb33add54f96c17b0f08d87b81f6f09429abf6ec93ac"
- },
- {
- "name": "hn",
- "unicode": "1F1ED-1F1F3",
+ "flag_hn": {
+ "category": "flags",
+ "moji": "🇭🇳",
+ "unicodeVersion": "6.0",
"digest": "87c1d160db810b5ed208fb33add54f96c17b0f08d87b81f6f09429abf6ec93ac"
},
- {
- "name": "flag_hr",
- "unicode": "1F1ED-1F1F7",
+ "flag_hr": {
+ "category": "flags",
+ "moji": "🇭🇷",
+ "unicodeVersion": "6.0",
"digest": "8b68112f79baea38565673acf4f1cb90675a5829ff17e4cf9415c928b62aed88"
},
- {
- "name": "hr",
- "unicode": "1F1ED-1F1F7",
- "digest": "8b68112f79baea38565673acf4f1cb90675a5829ff17e4cf9415c928b62aed88"
- },
- {
- "name": "flag_ht",
- "unicode": "1F1ED-1F1F9",
- "digest": "05dbd548c310ef1ebd1724aa85d821f8320106b16ddbf1f6442ea37e4407d5e1"
- },
- {
- "name": "ht",
- "unicode": "1F1ED-1F1F9",
+ "flag_ht": {
+ "category": "flags",
+ "moji": "🇭🇹",
+ "unicodeVersion": "6.0",
"digest": "05dbd548c310ef1ebd1724aa85d821f8320106b16ddbf1f6442ea37e4407d5e1"
},
- {
- "name": "flag_hu",
- "unicode": "1F1ED-1F1FA",
+ "flag_hu": {
+ "category": "flags",
+ "moji": "🇭🇺",
+ "unicodeVersion": "6.0",
"digest": "5079f3d6f1459e6df8dda5c19d2367ead8f5a755b8874ac999bae58e3c9f47a7"
},
- {
- "name": "hu",
- "unicode": "1F1ED-1F1FA",
- "digest": "5079f3d6f1459e6df8dda5c19d2367ead8f5a755b8874ac999bae58e3c9f47a7"
- },
- {
- "name": "flag_ic",
- "unicode": "1F1EE-1F1E8",
- "digest": "8dcb18c4b75a60867a68d2f6edbf81e782aafb4b9a0404c8081f872dfe71e432"
- },
- {
- "name": "ic",
- "unicode": "1F1EE-1F1E8",
+ "flag_ic": {
+ "category": "flags",
+ "moji": "🇮🇨",
+ "unicodeVersion": "6.0",
"digest": "8dcb18c4b75a60867a68d2f6edbf81e782aafb4b9a0404c8081f872dfe71e432"
},
- {
- "name": "flag_id",
- "unicode": "1F1EE-1F1E9",
+ "flag_id": {
+ "category": "flags",
+ "moji": "🇮🇩",
+ "unicodeVersion": "6.0",
"digest": "1b0eb69a158ed3afe24be448d44751f95dcc5cbc7d1393a5753293f16ef0a66c"
},
- {
- "name": "indonesia",
- "unicode": "1F1EE-1F1E9",
- "digest": "1b0eb69a158ed3afe24be448d44751f95dcc5cbc7d1393a5753293f16ef0a66c"
- },
- {
- "name": "flag_ie",
- "unicode": "1F1EE-1F1EA",
- "digest": "5fc8c101ad7296224455f72f73c335aa4f676023b68645bafaf69087f69af390"
- },
- {
- "name": "ie",
- "unicode": "1F1EE-1F1EA",
+ "flag_ie": {
+ "category": "flags",
+ "moji": "🇮🇪",
+ "unicodeVersion": "6.0",
"digest": "5fc8c101ad7296224455f72f73c335aa4f676023b68645bafaf69087f69af390"
},
- {
- "name": "flag_il",
- "unicode": "1F1EE-1F1F1",
+ "flag_il": {
+ "category": "flags",
+ "moji": "🇮🇱",
+ "unicodeVersion": "6.0",
"digest": "5aea4207415b7615dcdd69413705aefda700aefd0d27010cd0a0a338d879d9b8"
},
- {
- "name": "il",
- "unicode": "1F1EE-1F1F1",
- "digest": "5aea4207415b7615dcdd69413705aefda700aefd0d27010cd0a0a338d879d9b8"
- },
- {
- "name": "flag_im",
- "unicode": "1F1EE-1F1F2",
- "digest": "1ee9b3a5f1a52fc6d8369bfd81995fc0567e7a61deacd013701b3ec5fd64502e"
- },
- {
- "name": "im",
- "unicode": "1F1EE-1F1F2",
+ "flag_im": {
+ "category": "flags",
+ "moji": "🇮🇲",
+ "unicodeVersion": "6.0",
"digest": "1ee9b3a5f1a52fc6d8369bfd81995fc0567e7a61deacd013701b3ec5fd64502e"
},
- {
- "name": "flag_in",
- "unicode": "1F1EE-1F1F3",
+ "flag_in": {
+ "category": "flags",
+ "moji": "🇮🇳",
+ "unicodeVersion": "6.0",
"digest": "202ede502f34d55d180726ac2f29141c6875516f1b3e7ee99f266b16c2fe4bfd"
},
- {
- "name": "in",
- "unicode": "1F1EE-1F1F3",
- "digest": "202ede502f34d55d180726ac2f29141c6875516f1b3e7ee99f266b16c2fe4bfd"
- },
- {
- "name": "flag_io",
- "unicode": "1F1EE-1F1F4",
- "digest": "dd45e1afe792fca57d4161434bf611bcb7170072d63e4a27fb9dcd6e8912621e"
- },
- {
- "name": "io",
- "unicode": "1F1EE-1F1F4",
+ "flag_io": {
+ "category": "flags",
+ "moji": "🇮🇴",
+ "unicodeVersion": "6.0",
"digest": "dd45e1afe792fca57d4161434bf611bcb7170072d63e4a27fb9dcd6e8912621e"
},
- {
- "name": "flag_iq",
- "unicode": "1F1EE-1F1F6",
+ "flag_iq": {
+ "category": "flags",
+ "moji": "🇮🇶",
+ "unicodeVersion": "6.0",
"digest": "bef294772b5ffccd6c061c19d60af66f61b248d78705faf347ade9ebfca2b46d"
},
- {
- "name": "iq",
- "unicode": "1F1EE-1F1F6",
- "digest": "bef294772b5ffccd6c061c19d60af66f61b248d78705faf347ade9ebfca2b46d"
- },
- {
- "name": "flag_ir",
- "unicode": "1F1EE-1F1F7",
- "digest": "d4faca93577a5546330ab6a09252307e19fb420d89912c0b48ceb90bf409d48e"
- },
- {
- "name": "ir",
- "unicode": "1F1EE-1F1F7",
+ "flag_ir": {
+ "category": "flags",
+ "moji": "🇮🇷",
+ "unicodeVersion": "6.0",
"digest": "d4faca93577a5546330ab6a09252307e19fb420d89912c0b48ceb90bf409d48e"
},
- {
- "name": "flag_is",
- "unicode": "1F1EE-1F1F8",
+ "flag_is": {
+ "category": "flags",
+ "moji": "🇮🇸",
+ "unicodeVersion": "6.0",
"digest": "b2fc04226b274009b4d99d92bcb72b255b534b6fd4b76d82dce1575ad975a456"
},
- {
- "name": "is",
- "unicode": "1F1EE-1F1F8",
- "digest": "b2fc04226b274009b4d99d92bcb72b255b534b6fd4b76d82dce1575ad975a456"
- },
- {
- "name": "flag_it",
- "unicode": "1F1EE-1F1F9",
+ "flag_it": {
+ "category": "flags",
+ "moji": "🇮🇹",
+ "unicodeVersion": "6.0",
"digest": "735760f193855d55460a0fb93dad55ff67253cab63176eceb90b9bde1faead1e"
},
- {
- "name": "it",
- "unicode": "1F1EE-1F1F9",
- "digest": "735760f193855d55460a0fb93dad55ff67253cab63176eceb90b9bde1faead1e"
- },
- {
- "name": "flag_je",
- "unicode": "1F1EF-1F1EA",
+ "flag_je": {
+ "category": "flags",
+ "moji": "🇯🇪",
+ "unicodeVersion": "6.0",
"digest": "671a487a60571d928d2abaf306d0a9ba50239ec54ada14ea29a9a99df658d3cc"
},
- {
- "name": "je",
- "unicode": "1F1EF-1F1EA",
- "digest": "671a487a60571d928d2abaf306d0a9ba50239ec54ada14ea29a9a99df658d3cc"
- },
- {
- "name": "flag_jm",
- "unicode": "1F1EF-1F1F2",
- "digest": "fb9047199d030b78fc0dcfc58d9b524fdb929238d922809da88147b7cebf4211"
- },
- {
- "name": "jm",
- "unicode": "1F1EF-1F1F2",
+ "flag_jm": {
+ "category": "flags",
+ "moji": "🇯🇲",
+ "unicodeVersion": "6.0",
"digest": "fb9047199d030b78fc0dcfc58d9b524fdb929238d922809da88147b7cebf4211"
},
- {
- "name": "flag_jo",
- "unicode": "1F1EF-1F1F4",
+ "flag_jo": {
+ "category": "flags",
+ "moji": "🇯🇴",
+ "unicodeVersion": "6.0",
"digest": "19f7d536d0293ebf3db49e05a158097cbde467115ef96523a0553808fd0b4178"
},
- {
- "name": "jo",
- "unicode": "1F1EF-1F1F4",
- "digest": "19f7d536d0293ebf3db49e05a158097cbde467115ef96523a0553808fd0b4178"
- },
- {
- "name": "flag_jp",
- "unicode": "1F1EF-1F1F5",
- "digest": "51e971f777fe481ca9f7e077ecb2ce252c3cc0086b76384e7b965cdc337f3f9e"
- },
- {
- "name": "jp",
- "unicode": "1F1EF-1F1F5",
+ "flag_jp": {
+ "category": "flags",
+ "moji": "🇯🇵",
+ "unicodeVersion": "6.0",
"digest": "51e971f777fe481ca9f7e077ecb2ce252c3cc0086b76384e7b965cdc337f3f9e"
},
- {
- "name": "flag_ke",
- "unicode": "1F1F0-1F1EA",
- "digest": "0cec8f068548cfd3e7a20c10af84f97ca415fd6f8ab8b50783bf982e77d7260e"
- },
- {
- "name": "ke",
- "unicode": "1F1F0-1F1EA",
+ "flag_ke": {
+ "category": "flags",
+ "moji": "🇰🇪",
+ "unicodeVersion": "6.0",
"digest": "0cec8f068548cfd3e7a20c10af84f97ca415fd6f8ab8b50783bf982e77d7260e"
},
- {
- "name": "flag_kg",
- "unicode": "1F1F0-1F1EC",
- "digest": "5803ea6ab028261923fd7570c670a50518c6f462a2fb4d463531b12c3e382e6f"
- },
- {
- "name": "kg",
- "unicode": "1F1F0-1F1EC",
+ "flag_kg": {
+ "category": "flags",
+ "moji": "🇰🇬",
+ "unicodeVersion": "6.0",
"digest": "5803ea6ab028261923fd7570c670a50518c6f462a2fb4d463531b12c3e382e6f"
},
- {
- "name": "flag_kh",
- "unicode": "1F1F0-1F1ED",
+ "flag_kh": {
+ "category": "flags",
+ "moji": "🇰🇭",
+ "unicodeVersion": "6.0",
"digest": "287d357afe47179853fd485fb102834ead145598ed892664fc62d245cac16080"
},
- {
- "name": "kh",
- "unicode": "1F1F0-1F1ED",
- "digest": "287d357afe47179853fd485fb102834ead145598ed892664fc62d245cac16080"
- },
- {
- "name": "flag_ki",
- "unicode": "1F1F0-1F1EE",
- "digest": "ae4aee0d9cd7a21d4e250d45a484f5f641acdab3d79b437337b25fe34a0b49b0"
- },
- {
- "name": "ki",
- "unicode": "1F1F0-1F1EE",
+ "flag_ki": {
+ "category": "flags",
+ "moji": "🇰🇮",
+ "unicodeVersion": "6.0",
"digest": "ae4aee0d9cd7a21d4e250d45a484f5f641acdab3d79b437337b25fe34a0b49b0"
},
- {
- "name": "flag_km",
- "unicode": "1F1F0-1F1F2",
- "digest": "2d1730acbf5421fd02bd5483e26a86d82ec2fa99f0ff75bfd728a9df7914ad3b"
- },
- {
- "name": "km",
- "unicode": "1F1F0-1F1F2",
+ "flag_km": {
+ "category": "flags",
+ "moji": "🇰🇲",
+ "unicodeVersion": "6.0",
"digest": "2d1730acbf5421fd02bd5483e26a86d82ec2fa99f0ff75bfd728a9df7914ad3b"
},
- {
- "name": "flag_kn",
- "unicode": "1F1F0-1F1F3",
- "digest": "b9ed979db9c6d243b00f61f19a9ec0f2c2390b2e5cace5ad61d9371dc8c670ac"
- },
- {
- "name": "kn",
- "unicode": "1F1F0-1F1F3",
+ "flag_kn": {
+ "category": "flags",
+ "moji": "🇰🇳",
+ "unicodeVersion": "6.0",
"digest": "b9ed979db9c6d243b00f61f19a9ec0f2c2390b2e5cace5ad61d9371dc8c670ac"
},
- {
- "name": "flag_kp",
- "unicode": "1F1F0-1F1F5",
- "digest": "1bab0b9cab8028a95ce7231ad8d88ebcd31601cfa321284bba017ead47f6c729"
- },
- {
- "name": "kp",
- "unicode": "1F1F0-1F1F5",
+ "flag_kp": {
+ "category": "flags",
+ "moji": "🇰🇵",
+ "unicodeVersion": "6.0",
"digest": "1bab0b9cab8028a95ce7231ad8d88ebcd31601cfa321284bba017ead47f6c729"
},
- {
- "name": "flag_kr",
- "unicode": "1F1F0-1F1F7",
- "digest": "33be8c09ebe273e203aa703cc827d52a6d9bf1699f5445bba13a77af2df45fa6"
- },
- {
- "name": "kr",
- "unicode": "1F1F0-1F1F7",
+ "flag_kr": {
+ "category": "flags",
+ "moji": "🇰🇷",
+ "unicodeVersion": "6.0",
"digest": "33be8c09ebe273e203aa703cc827d52a6d9bf1699f5445bba13a77af2df45fa6"
},
- {
- "name": "flag_kw",
- "unicode": "1F1F0-1F1FC",
- "digest": "04d901a92ea55b13dc4983a9e3adb52dc89c9f3decee86fd06022aa902678b6d"
- },
- {
- "name": "kw",
- "unicode": "1F1F0-1F1FC",
+ "flag_kw": {
+ "category": "flags",
+ "moji": "🇰🇼",
+ "unicodeVersion": "6.0",
"digest": "04d901a92ea55b13dc4983a9e3adb52dc89c9f3decee86fd06022aa902678b6d"
},
- {
- "name": "flag_ky",
- "unicode": "1F1F0-1F1FE",
- "digest": "10f4d02f33cadd34da89de71a3b763809bad480cd9ae9d2ec000db026bd94cd1"
- },
- {
- "name": "ky",
- "unicode": "1F1F0-1F1FE",
+ "flag_ky": {
+ "category": "flags",
+ "moji": "🇰🇾",
+ "unicodeVersion": "6.0",
"digest": "10f4d02f33cadd34da89de71a3b763809bad480cd9ae9d2ec000db026bd94cd1"
},
- {
- "name": "flag_kz",
- "unicode": "1F1F0-1F1FF",
- "digest": "dfaff69a78cf635f7fad41bd5bdcc8003298454708a6178ba7348b1b40c360c1"
- },
- {
- "name": "kz",
- "unicode": "1F1F0-1F1FF",
+ "flag_kz": {
+ "category": "flags",
+ "moji": "🇰🇿",
+ "unicodeVersion": "6.0",
"digest": "dfaff69a78cf635f7fad41bd5bdcc8003298454708a6178ba7348b1b40c360c1"
},
- {
- "name": "flag_la",
- "unicode": "1F1F1-1F1E6",
- "digest": "4fcfbdc694cf99ae3f832500cdcdedb88c444b6df88bc9b7141f4f26ba3d5bfd"
- },
- {
- "name": "la",
- "unicode": "1F1F1-1F1E6",
+ "flag_la": {
+ "category": "flags",
+ "moji": "🇱🇦",
+ "unicodeVersion": "6.0",
"digest": "4fcfbdc694cf99ae3f832500cdcdedb88c444b6df88bc9b7141f4f26ba3d5bfd"
},
- {
- "name": "flag_lb",
- "unicode": "1F1F1-1F1E7",
- "digest": "af4b1f784bea0ec7a712495491dffbd1152cc857a99fd433f76bfeb313819a62"
- },
- {
- "name": "lb",
- "unicode": "1F1F1-1F1E7",
+ "flag_lb": {
+ "category": "flags",
+ "moji": "🇱🇧",
+ "unicodeVersion": "6.0",
"digest": "af4b1f784bea0ec7a712495491dffbd1152cc857a99fd433f76bfeb313819a62"
},
- {
- "name": "flag_lc",
- "unicode": "1F1F1-1F1E8",
+ "flag_lc": {
+ "category": "flags",
+ "moji": "🇱🇨",
+ "unicodeVersion": "6.0",
"digest": "40784aa558b75d07ae499c004e2cc5d0b2efdfc3e5be705b5a9f6b70d681c396"
},
- {
- "name": "lc",
- "unicode": "1F1F1-1F1E8",
- "digest": "40784aa558b75d07ae499c004e2cc5d0b2efdfc3e5be705b5a9f6b70d681c396"
- },
- {
- "name": "flag_li",
- "unicode": "1F1F1-1F1EE",
- "digest": "c4eb4c43f457ce60ff9d046adb512c1d3462203403eeb595bff3ebc010ed6633"
- },
- {
- "name": "li",
- "unicode": "1F1F1-1F1EE",
+ "flag_li": {
+ "category": "flags",
+ "moji": "🇱🇮",
+ "unicodeVersion": "6.0",
"digest": "c4eb4c43f457ce60ff9d046adb512c1d3462203403eeb595bff3ebc010ed6633"
},
- {
- "name": "flag_lk",
- "unicode": "1F1F1-1F1F0",
+ "flag_lk": {
+ "category": "flags",
+ "moji": "🇱🇰",
+ "unicodeVersion": "6.0",
"digest": "a5285cdfdc3715fa3941f5f0eb03dc425969eaaf22c719c27ab4418628d09bc5"
},
- {
- "name": "lk",
- "unicode": "1F1F1-1F1F0",
- "digest": "a5285cdfdc3715fa3941f5f0eb03dc425969eaaf22c719c27ab4418628d09bc5"
- },
- {
- "name": "flag_lr",
- "unicode": "1F1F1-1F1F7",
- "digest": "ed04334264953b4da570db8c392b99d2fab4e0b7efc2331427016c6a08e818be"
- },
- {
- "name": "lr",
- "unicode": "1F1F1-1F1F7",
+ "flag_lr": {
+ "category": "flags",
+ "moji": "🇱🇷",
+ "unicodeVersion": "6.0",
"digest": "ed04334264953b4da570db8c392b99d2fab4e0b7efc2331427016c6a08e818be"
},
- {
- "name": "flag_ls",
- "unicode": "1F1F1-1F1F8",
+ "flag_ls": {
+ "category": "flags",
+ "moji": "🇱🇸",
+ "unicodeVersion": "6.0",
"digest": "cd56022106d027317cc9bf4c848758cf29ffe277ce71fdb9c1cf89ac4fd6e6db"
},
- {
- "name": "ls",
- "unicode": "1F1F1-1F1F8",
- "digest": "cd56022106d027317cc9bf4c848758cf29ffe277ce71fdb9c1cf89ac4fd6e6db"
- },
- {
- "name": "flag_lt",
- "unicode": "1F1F1-1F1F9",
- "digest": "3c4395b068e421100fd97a102f170cb8d5c093885eef7cb40d3faff4f4e47fe9"
- },
- {
- "name": "lt",
- "unicode": "1F1F1-1F1F9",
+ "flag_lt": {
+ "category": "flags",
+ "moji": "🇱🇹",
+ "unicodeVersion": "6.0",
"digest": "3c4395b068e421100fd97a102f170cb8d5c093885eef7cb40d3faff4f4e47fe9"
},
- {
- "name": "flag_lu",
- "unicode": "1F1F1-1F1FA",
+ "flag_lu": {
+ "category": "flags",
+ "moji": "🇱🇺",
+ "unicodeVersion": "6.0",
"digest": "df15a2c47eecad17e0cc169bdf0d31c6a51eb22de7ca4e70d2431359a33f930d"
},
- {
- "name": "lu",
- "unicode": "1F1F1-1F1FA",
- "digest": "df15a2c47eecad17e0cc169bdf0d31c6a51eb22de7ca4e70d2431359a33f930d"
- },
- {
- "name": "flag_lv",
- "unicode": "1F1F1-1F1FB",
+ "flag_lv": {
+ "category": "flags",
+ "moji": "🇱🇻",
+ "unicodeVersion": "6.0",
"digest": "9b53c6ce23287935200da8ca8a8af78013a4b1572f9821e7e1724cbad248e7e2"
},
- {
- "name": "lv",
- "unicode": "1F1F1-1F1FB",
- "digest": "9b53c6ce23287935200da8ca8a8af78013a4b1572f9821e7e1724cbad248e7e2"
- },
- {
- "name": "flag_ly",
- "unicode": "1F1F1-1F1FE",
+ "flag_ly": {
+ "category": "flags",
+ "moji": "🇱🇾",
+ "unicodeVersion": "6.0",
"digest": "42efa9f3526ef006d6723fa17538a98ab9556ae25f14df1b06d21361bf7e1a44"
},
- {
- "name": "ly",
- "unicode": "1F1F1-1F1FE",
- "digest": "42efa9f3526ef006d6723fa17538a98ab9556ae25f14df1b06d21361bf7e1a44"
- },
- {
- "name": "flag_ma",
- "unicode": "1F1F2-1F1E6",
- "digest": "96c07296cfd7aa1cb642faed8ace26744105b81ca880157a4ef4caee0befe26e"
- },
- {
- "name": "ma",
- "unicode": "1F1F2-1F1E6",
+ "flag_ma": {
+ "category": "flags",
+ "moji": "🇲🇦",
+ "unicodeVersion": "6.0",
"digest": "96c07296cfd7aa1cb642faed8ace26744105b81ca880157a4ef4caee0befe26e"
},
- {
- "name": "flag_mc",
- "unicode": "1F1F2-1F1E8",
+ "flag_mc": {
+ "category": "flags",
+ "moji": "🇲🇨",
+ "unicodeVersion": "6.0",
"digest": "6b44608842fe849ae2b4bae5eb87ccd436459a427051dfda25080196273d4b9f"
},
- {
- "name": "mc",
- "unicode": "1F1F2-1F1E8",
- "digest": "6b44608842fe849ae2b4bae5eb87ccd436459a427051dfda25080196273d4b9f"
- },
- {
- "name": "flag_md",
- "unicode": "1F1F2-1F1E9",
- "digest": "78c7b01c698873a9129d52ba38b3eb4cfc683ef2ae10b7b922b17c07f1c938c8"
- },
- {
- "name": "md",
- "unicode": "1F1F2-1F1E9",
+ "flag_md": {
+ "category": "flags",
+ "moji": "🇲🇩",
+ "unicodeVersion": "6.0",
"digest": "78c7b01c698873a9129d52ba38b3eb4cfc683ef2ae10b7b922b17c07f1c938c8"
},
- {
- "name": "flag_me",
- "unicode": "1F1F2-1F1EA",
- "digest": "01aa0f9df89302edc4ae319b5dd78069ba8807c3f38cc7bfe01bff67c8efd416"
- },
- {
- "name": "me",
- "unicode": "1F1F2-1F1EA",
+ "flag_me": {
+ "category": "flags",
+ "moji": "🇲🇪",
+ "unicodeVersion": "6.0",
"digest": "01aa0f9df89302edc4ae319b5dd78069ba8807c3f38cc7bfe01bff67c8efd416"
},
- {
- "name": "flag_mf",
- "unicode": "1F1F2-1F1EB",
- "digest": "a0124683aa88cd7da886da70c65796c5ad84eb3751e356e9b2aa8ac249cf0bf9"
- },
- {
- "name": "mf",
- "unicode": "1F1F2-1F1EB",
+ "flag_mf": {
+ "category": "flags",
+ "moji": "🇲🇫",
+ "unicodeVersion": "6.0",
"digest": "a0124683aa88cd7da886da70c65796c5ad84eb3751e356e9b2aa8ac249cf0bf9"
},
- {
- "name": "flag_mg",
- "unicode": "1F1F2-1F1EC",
+ "flag_mg": {
+ "category": "flags",
+ "moji": "🇲🇬",
+ "unicodeVersion": "6.0",
"digest": "56ebcd2a2e144d656d3b38a62595138fe6e50f9c1144f70b0a120cce7a72eb5b"
},
- {
- "name": "mg",
- "unicode": "1F1F2-1F1EC",
- "digest": "56ebcd2a2e144d656d3b38a62595138fe6e50f9c1144f70b0a120cce7a72eb5b"
- },
- {
- "name": "flag_mh",
- "unicode": "1F1F2-1F1ED",
- "digest": "008660adc4c2e4d04830498988184d1ef8a372a6c085da369a94ee6b820dbbb7"
- },
- {
- "name": "mh",
- "unicode": "1F1F2-1F1ED",
+ "flag_mh": {
+ "category": "flags",
+ "moji": "🇲🇭",
+ "unicodeVersion": "6.0",
"digest": "008660adc4c2e4d04830498988184d1ef8a372a6c085da369a94ee6b820dbbb7"
},
- {
- "name": "flag_mk",
- "unicode": "1F1F2-1F1F0",
- "digest": "f3c4c5106ace81c21fc0c6a7cc5c5e04e9453468fbc6ccbc851bb8dd61ff237f"
- },
- {
- "name": "mk",
- "unicode": "1F1F2-1F1F0",
+ "flag_mk": {
+ "category": "flags",
+ "moji": "🇲🇰",
+ "unicodeVersion": "6.0",
"digest": "f3c4c5106ace81c21fc0c6a7cc5c5e04e9453468fbc6ccbc851bb8dd61ff237f"
},
- {
- "name": "flag_ml",
- "unicode": "1F1F2-1F1F1",
+ "flag_ml": {
+ "category": "flags",
+ "moji": "🇲🇱",
+ "unicodeVersion": "6.0",
"digest": "e70a6b30e46adc2e19684308a848fef2c3ad76e2cac4bb493ee3270ad39f9d1b"
},
- {
- "name": "ml",
- "unicode": "1F1F2-1F1F1",
- "digest": "e70a6b30e46adc2e19684308a848fef2c3ad76e2cac4bb493ee3270ad39f9d1b"
- },
- {
- "name": "flag_mm",
- "unicode": "1F1F2-1F1F2",
- "digest": "720f5d38887202ba049cd5a46c183679be6a01f169d99e6e656c73b515793a7d"
- },
- {
- "name": "mm",
- "unicode": "1F1F2-1F1F2",
+ "flag_mm": {
+ "category": "flags",
+ "moji": "🇲🇲",
+ "unicodeVersion": "6.0",
"digest": "720f5d38887202ba049cd5a46c183679be6a01f169d99e6e656c73b515793a7d"
},
- {
- "name": "flag_mn",
- "unicode": "1F1F2-1F1F3",
+ "flag_mn": {
+ "category": "flags",
+ "moji": "🇲🇳",
+ "unicodeVersion": "6.0",
"digest": "5f0fd6fcb2ed73a5a6d9396c3703612503c1f16283bbb4e9362a1c8324b762ad"
},
- {
- "name": "mn",
- "unicode": "1F1F2-1F1F3",
- "digest": "5f0fd6fcb2ed73a5a6d9396c3703612503c1f16283bbb4e9362a1c8324b762ad"
- },
- {
- "name": "flag_mo",
- "unicode": "1F1F2-1F1F4",
- "digest": "fc2a9e7323867cf195f551e59afdab778c56b84c96af28c20207c9870caa2c39"
- },
- {
- "name": "mo",
- "unicode": "1F1F2-1F1F4",
+ "flag_mo": {
+ "category": "flags",
+ "moji": "🇲🇴",
+ "unicodeVersion": "6.0",
"digest": "fc2a9e7323867cf195f551e59afdab778c56b84c96af28c20207c9870caa2c39"
},
- {
- "name": "flag_mp",
- "unicode": "1F1F2-1F1F5",
+ "flag_mp": {
+ "category": "flags",
+ "moji": "🇲🇵",
+ "unicodeVersion": "6.0",
"digest": "ddce3be9d72914240c42e1b97ea97af01016d0a3879999cb0e447552682c06ba"
},
- {
- "name": "mp",
- "unicode": "1F1F2-1F1F5",
- "digest": "ddce3be9d72914240c42e1b97ea97af01016d0a3879999cb0e447552682c06ba"
- },
- {
- "name": "flag_mq",
- "unicode": "1F1F2-1F1F6",
- "digest": "888f455b1322d6fb83dc9f469f5505fea3dd6ece77d17d0d7345319c3ebcec0e"
- },
- {
- "name": "mq",
- "unicode": "1F1F2-1F1F6",
+ "flag_mq": {
+ "category": "flags",
+ "moji": "🇲🇶",
+ "unicodeVersion": "6.0",
"digest": "888f455b1322d6fb83dc9f469f5505fea3dd6ece77d17d0d7345319c3ebcec0e"
},
- {
- "name": "flag_mr",
- "unicode": "1F1F2-1F1F7",
+ "flag_mr": {
+ "category": "flags",
+ "moji": "🇲🇷",
+ "unicodeVersion": "6.0",
"digest": "72621914c92dd9c9f3ac9973ee3589583bfe42b841cdd35f47af75e2f629726c"
},
- {
- "name": "mr",
- "unicode": "1F1F2-1F1F7",
- "digest": "72621914c92dd9c9f3ac9973ee3589583bfe42b841cdd35f47af75e2f629726c"
- },
- {
- "name": "flag_ms",
- "unicode": "1F1F2-1F1F8",
- "digest": "5944996295132f41ec55261ff7927518bd47aec95d274a6ff257c357b43657bc"
- },
- {
- "name": "ms",
- "unicode": "1F1F2-1F1F8",
+ "flag_ms": {
+ "category": "flags",
+ "moji": "🇲🇸",
+ "unicodeVersion": "6.0",
"digest": "5944996295132f41ec55261ff7927518bd47aec95d274a6ff257c357b43657bc"
},
- {
- "name": "flag_mt",
- "unicode": "1F1F2-1F1F9",
+ "flag_mt": {
+ "category": "flags",
+ "moji": "🇲🇹",
+ "unicodeVersion": "6.0",
"digest": "95f0550e8823441a4e69b26c540baea94f3ddcc282100fd0239021c00df0b469"
},
- {
- "name": "mt",
- "unicode": "1F1F2-1F1F9",
- "digest": "95f0550e8823441a4e69b26c540baea94f3ddcc282100fd0239021c00df0b469"
- },
- {
- "name": "flag_mu",
- "unicode": "1F1F2-1F1FA",
- "digest": "5fda78a6df0ea7f5cac5fb4c8fd68529c14c5e15bac4e0b167493cb6ac459253"
- },
- {
- "name": "mu",
- "unicode": "1F1F2-1F1FA",
+ "flag_mu": {
+ "category": "flags",
+ "moji": "🇲🇺",
+ "unicodeVersion": "6.0",
"digest": "5fda78a6df0ea7f5cac5fb4c8fd68529c14c5e15bac4e0b167493cb6ac459253"
},
- {
- "name": "flag_mv",
- "unicode": "1F1F2-1F1FB",
+ "flag_mv": {
+ "category": "flags",
+ "moji": "🇲🇻",
+ "unicodeVersion": "6.0",
"digest": "f75c8f6fd3a68f2944a04c833c649d4b576997f491100cf3f3160fe77117fabb"
},
- {
- "name": "mv",
- "unicode": "1F1F2-1F1FB",
- "digest": "f75c8f6fd3a68f2944a04c833c649d4b576997f491100cf3f3160fe77117fabb"
- },
- {
- "name": "flag_mw",
- "unicode": "1F1F2-1F1FC",
- "digest": "d46b484a97e5b90b6b259f8de1712b553f93f0dfb6391209200358bb9429ebf5"
- },
- {
- "name": "mw",
- "unicode": "1F1F2-1F1FC",
+ "flag_mw": {
+ "category": "flags",
+ "moji": "🇲🇼",
+ "unicodeVersion": "6.0",
"digest": "d46b484a97e5b90b6b259f8de1712b553f93f0dfb6391209200358bb9429ebf5"
},
- {
- "name": "flag_mx",
- "unicode": "1F1F2-1F1FD",
+ "flag_mx": {
+ "category": "flags",
+ "moji": "🇲🇽",
+ "unicodeVersion": "6.0",
"digest": "dc57c10307fc0aa09bd7fcd25ee0fca561f3b382276faa8432a927c1baea53fd"
},
- {
- "name": "mx",
- "unicode": "1F1F2-1F1FD",
- "digest": "dc57c10307fc0aa09bd7fcd25ee0fca561f3b382276faa8432a927c1baea53fd"
- },
- {
- "name": "flag_my",
- "unicode": "1F1F2-1F1FE",
- "digest": "15ca00660a1eb0096fdaa00b85a7b95fcf192bf2ee4781ba72c36d2d2cb015ef"
- },
- {
- "name": "my",
- "unicode": "1F1F2-1F1FE",
+ "flag_my": {
+ "category": "flags",
+ "moji": "🇲🇾",
+ "unicodeVersion": "6.0",
"digest": "15ca00660a1eb0096fdaa00b85a7b95fcf192bf2ee4781ba72c36d2d2cb015ef"
},
- {
- "name": "flag_mz",
- "unicode": "1F1F2-1F1FF",
+ "flag_mz": {
+ "category": "flags",
+ "moji": "🇲🇿",
+ "unicodeVersion": "6.0",
"digest": "0c8605a9319dcf86672a833b4c4d6acea5f6aa25a3f8e1dfac78fbf7c452ba97"
},
- {
- "name": "mz",
- "unicode": "1F1F2-1F1FF",
- "digest": "0c8605a9319dcf86672a833b4c4d6acea5f6aa25a3f8e1dfac78fbf7c452ba97"
- },
- {
- "name": "flag_na",
- "unicode": "1F1F3-1F1E6",
+ "flag_na": {
+ "category": "flags",
+ "moji": "🇳🇦",
+ "unicodeVersion": "6.0",
"digest": "e63cde5ee49d3ada1e33d2ab15dc24fbb129b90d65b6fd1d7c07455f71a53601"
},
- {
- "name": "na",
- "unicode": "1F1F3-1F1E6",
- "digest": "e63cde5ee49d3ada1e33d2ab15dc24fbb129b90d65b6fd1d7c07455f71a53601"
- },
- {
- "name": "flag_nc",
- "unicode": "1F1F3-1F1E8",
+ "flag_nc": {
+ "category": "flags",
+ "moji": "🇳🇨",
+ "unicodeVersion": "6.0",
"digest": "a4a350ce7404ba7bdda9a341e7a48fcfe16312be4964b1bd6eed7115acd2e329"
},
- {
- "name": "nc",
- "unicode": "1F1F3-1F1E8",
- "digest": "a4a350ce7404ba7bdda9a341e7a48fcfe16312be4964b1bd6eed7115acd2e329"
- },
- {
- "name": "flag_ne",
- "unicode": "1F1F3-1F1EA",
- "digest": "6b32483b4445bc52855509f618c570b9c9606de5649e4878b71b44ff2acbc9fd"
- },
- {
- "name": "ne",
- "unicode": "1F1F3-1F1EA",
+ "flag_ne": {
+ "category": "flags",
+ "moji": "🇳🇪",
+ "unicodeVersion": "6.0",
"digest": "6b32483b4445bc52855509f618c570b9c9606de5649e4878b71b44ff2acbc9fd"
},
- {
- "name": "flag_nf",
- "unicode": "1F1F3-1F1EB",
+ "flag_nf": {
+ "category": "flags",
+ "moji": "🇳🇫",
+ "unicodeVersion": "6.0",
"digest": "96b1ec33acbd2b1ffe42703c11a2a633b036e6779849b0e6fa8f399167820584"
},
- {
- "name": "nf",
- "unicode": "1F1F3-1F1EB",
- "digest": "96b1ec33acbd2b1ffe42703c11a2a633b036e6779849b0e6fa8f399167820584"
- },
- {
- "name": "flag_ng",
- "unicode": "1F1F3-1F1EC",
- "digest": "f97d0630cbfa5e75440251df7529a67b58c22598643390cbeea82fb04a1cd956"
- },
- {
- "name": "nigeria",
- "unicode": "1F1F3-1F1EC",
+ "flag_ng": {
+ "category": "flags",
+ "moji": "🇳🇬",
+ "unicodeVersion": "6.0",
"digest": "f97d0630cbfa5e75440251df7529a67b58c22598643390cbeea82fb04a1cd956"
},
- {
- "name": "flag_ni",
- "unicode": "1F1F3-1F1EE",
- "digest": "c52fb5f9134122a91defa75425be2c6b3c909e051d546244e0e7bdf5f9ee1710"
- },
- {
- "name": "ni",
- "unicode": "1F1F3-1F1EE",
+ "flag_ni": {
+ "category": "flags",
+ "moji": "🇳🇮",
+ "unicodeVersion": "6.0",
"digest": "c52fb5f9134122a91defa75425be2c6b3c909e051d546244e0e7bdf5f9ee1710"
},
- {
- "name": "flag_nl",
- "unicode": "1F1F3-1F1F1",
- "digest": "b8918f9c0c92513aa0ec6ba6cee5448270168cbe6f0a970fb06e7ceb9f52ec71"
- },
- {
- "name": "nl",
- "unicode": "1F1F3-1F1F1",
+ "flag_nl": {
+ "category": "flags",
+ "moji": "🇳🇱",
+ "unicodeVersion": "6.0",
"digest": "b8918f9c0c92513aa0ec6ba6cee5448270168cbe6f0a970fb06e7ceb9f52ec71"
},
- {
- "name": "flag_no",
- "unicode": "1F1F3-1F1F4",
+ "flag_no": {
+ "category": "flags",
+ "moji": "🇳🇴",
+ "unicodeVersion": "6.0",
"digest": "05ce84095f8d93407d611b39d8b6a67fd9f11df6cfab7a185bcb4eec186d85ef"
},
- {
- "name": "no",
- "unicode": "1F1F3-1F1F4",
- "digest": "05ce84095f8d93407d611b39d8b6a67fd9f11df6cfab7a185bcb4eec186d85ef"
- },
- {
- "name": "flag_np",
- "unicode": "1F1F3-1F1F5",
- "digest": "cc41c2f97ec2b38fe5781d553792f6aab5d37cc3be02586f361fe89d12683bee"
- },
- {
- "name": "np",
- "unicode": "1F1F3-1F1F5",
+ "flag_np": {
+ "category": "flags",
+ "moji": "🇳🇵",
+ "unicodeVersion": "6.0",
"digest": "cc41c2f97ec2b38fe5781d553792f6aab5d37cc3be02586f361fe89d12683bee"
},
- {
- "name": "flag_nr",
- "unicode": "1F1F3-1F1F7",
- "digest": "7837edf59ec33a25380d76afea5f04cfcab4f17df4e33fca0dcaacb517c5cbec"
- },
- {
- "name": "nr",
- "unicode": "1F1F3-1F1F7",
+ "flag_nr": {
+ "category": "flags",
+ "moji": "🇳🇷",
+ "unicodeVersion": "6.0",
"digest": "7837edf59ec33a25380d76afea5f04cfcab4f17df4e33fca0dcaacb517c5cbec"
},
- {
- "name": "flag_nu",
- "unicode": "1F1F3-1F1FA",
- "digest": "fd9ab45c6f32bc4da47542392e5beba73ddac302a4a9a00e6deedc913a4c087d"
- },
- {
- "name": "nu",
- "unicode": "1F1F3-1F1FA",
+ "flag_nu": {
+ "category": "flags",
+ "moji": "🇳🇺",
+ "unicodeVersion": "6.0",
"digest": "fd9ab45c6f32bc4da47542392e5beba73ddac302a4a9a00e6deedc913a4c087d"
},
- {
- "name": "flag_nz",
- "unicode": "1F1F3-1F1FF",
- "digest": "0719830dcca400cefb30ce399bb03f49dd84c9a98f7d6a28270f9278e2a7af75"
- },
- {
- "name": "nz",
- "unicode": "1F1F3-1F1FF",
+ "flag_nz": {
+ "category": "flags",
+ "moji": "🇳🇿",
+ "unicodeVersion": "6.0",
"digest": "0719830dcca400cefb30ce399bb03f49dd84c9a98f7d6a28270f9278e2a7af75"
},
- {
- "name": "flag_om",
- "unicode": "1F1F4-1F1F2",
- "digest": "3f9039becd52e3454fdf7611cdb0d7fb1196e053eea29ef87daab6c21a94f1ee"
- },
- {
- "name": "om",
- "unicode": "1F1F4-1F1F2",
+ "flag_om": {
+ "category": "flags",
+ "moji": "🇴🇲",
+ "unicodeVersion": "6.0",
"digest": "3f9039becd52e3454fdf7611cdb0d7fb1196e053eea29ef87daab6c21a94f1ee"
},
- {
- "name": "flag_pa",
- "unicode": "1F1F5-1F1E6",
- "digest": "1adf0e5d4084e072aa44bd9978829e77546e0be75785e9be69f92e326bd714a7"
- },
- {
- "name": "pa",
- "unicode": "1F1F5-1F1E6",
+ "flag_pa": {
+ "category": "flags",
+ "moji": "🇵🇦",
+ "unicodeVersion": "6.0",
"digest": "1adf0e5d4084e072aa44bd9978829e77546e0be75785e9be69f92e326bd714a7"
},
- {
- "name": "flag_pe",
- "unicode": "1F1F5-1F1EA",
- "digest": "f8a4e257676f4ab8962ffe5509b8417777a8be2f0e9dc7735d3e014ff221aab1"
- },
- {
- "name": "pe",
- "unicode": "1F1F5-1F1EA",
+ "flag_pe": {
+ "category": "flags",
+ "moji": "🇵🇪",
+ "unicodeVersion": "6.0",
"digest": "f8a4e257676f4ab8962ffe5509b8417777a8be2f0e9dc7735d3e014ff221aab1"
},
- {
- "name": "flag_pf",
- "unicode": "1F1F5-1F1EB",
- "digest": "1ace6cc71d130cdf09246297740a911f14828c322e35330cc548ca5975015c23"
- },
- {
- "name": "pf",
- "unicode": "1F1F5-1F1EB",
+ "flag_pf": {
+ "category": "flags",
+ "moji": "🇵🇫",
+ "unicodeVersion": "6.0",
"digest": "1ace6cc71d130cdf09246297740a911f14828c322e35330cc548ca5975015c23"
},
- {
- "name": "flag_pg",
- "unicode": "1F1F5-1F1EC",
- "digest": "9c37719d9f51ef31fec0f898d38e522b4253cd00344408e3f660132514efddb7"
- },
- {
- "name": "pg",
- "unicode": "1F1F5-1F1EC",
+ "flag_pg": {
+ "category": "flags",
+ "moji": "🇵🇬",
+ "unicodeVersion": "6.0",
"digest": "9c37719d9f51ef31fec0f898d38e522b4253cd00344408e3f660132514efddb7"
},
- {
- "name": "flag_ph",
- "unicode": "1F1F5-1F1ED",
- "digest": "f1af628cf6d1d290cedef3d564b2386e2d6f14ba4426d3fefc0312cb8772e517"
- },
- {
- "name": "ph",
- "unicode": "1F1F5-1F1ED",
+ "flag_ph": {
+ "category": "flags",
+ "moji": "🇵🇭",
+ "unicodeVersion": "6.0",
"digest": "f1af628cf6d1d290cedef3d564b2386e2d6f14ba4426d3fefc0312cb8772e517"
},
- {
- "name": "flag_pk",
- "unicode": "1F1F5-1F1F0",
+ "flag_pk": {
+ "category": "flags",
+ "moji": "🇵🇰",
+ "unicodeVersion": "6.0",
"digest": "61c77f73d2a10a5acb289fadfe0d25d1a1c343e1223bd802099ff4e0e9356521"
},
- {
- "name": "pk",
- "unicode": "1F1F5-1F1F0",
- "digest": "61c77f73d2a10a5acb289fadfe0d25d1a1c343e1223bd802099ff4e0e9356521"
- },
- {
- "name": "flag_pl",
- "unicode": "1F1F5-1F1F1",
- "digest": "38c2c8618446e1f72cf983ab33e736d943f0db7c4cce52a187299e8cec2ea895"
- },
- {
- "name": "pl",
- "unicode": "1F1F5-1F1F1",
+ "flag_pl": {
+ "category": "flags",
+ "moji": "🇵🇱",
+ "unicodeVersion": "6.0",
"digest": "38c2c8618446e1f72cf983ab33e736d943f0db7c4cce52a187299e8cec2ea895"
},
- {
- "name": "flag_pm",
- "unicode": "1F1F5-1F1F2",
+ "flag_pm": {
+ "category": "flags",
+ "moji": "🇵🇲",
+ "unicodeVersion": "6.0",
"digest": "656be9ea1a79c3885a759c7ce353d338345a198d7939556949affaf5490cb644"
},
- {
- "name": "pm",
- "unicode": "1F1F5-1F1F2",
- "digest": "656be9ea1a79c3885a759c7ce353d338345a198d7939556949affaf5490cb644"
- },
- {
- "name": "flag_pn",
- "unicode": "1F1F5-1F1F3",
- "digest": "2792260d8087ab0253b1214c1420f0160ab2eef9afe7315f9e7ff0b87cd15d72"
- },
- {
- "name": "pn",
- "unicode": "1F1F5-1F1F3",
+ "flag_pn": {
+ "category": "flags",
+ "moji": "🇵🇳",
+ "unicodeVersion": "6.0",
"digest": "2792260d8087ab0253b1214c1420f0160ab2eef9afe7315f9e7ff0b87cd15d72"
},
- {
- "name": "flag_pr",
- "unicode": "1F1F5-1F1F7",
+ "flag_pr": {
+ "category": "flags",
+ "moji": "🇵🇷",
+ "unicodeVersion": "6.0",
"digest": "c4cfa1f2201dcda9de310a8247e6ce32d2798ae426a14dd70a9ebb00a2804d46"
},
- {
- "name": "pr",
- "unicode": "1F1F5-1F1F7",
- "digest": "c4cfa1f2201dcda9de310a8247e6ce32d2798ae426a14dd70a9ebb00a2804d46"
- },
- {
- "name": "flag_ps",
- "unicode": "1F1F5-1F1F8",
- "digest": "197f2ec6294bf0ee4a08cf2f2d1e237ee867c98b3085454a3f42abc955eeb289"
- },
- {
- "name": "ps",
- "unicode": "1F1F5-1F1F8",
+ "flag_ps": {
+ "category": "flags",
+ "moji": "🇵🇸",
+ "unicodeVersion": "6.0",
"digest": "197f2ec6294bf0ee4a08cf2f2d1e237ee867c98b3085454a3f42abc955eeb289"
},
- {
- "name": "flag_pt",
- "unicode": "1F1F5-1F1F9",
+ "flag_pt": {
+ "category": "flags",
+ "moji": "🇵🇹",
+ "unicodeVersion": "6.0",
"digest": "86a50827963756b5bf471ed9df5b3f2a2058b4c5d778a303414b6b0556e2082b"
},
- {
- "name": "pt",
- "unicode": "1F1F5-1F1F9",
- "digest": "86a50827963756b5bf471ed9df5b3f2a2058b4c5d778a303414b6b0556e2082b"
- },
- {
- "name": "flag_pw",
- "unicode": "1F1F5-1F1FC",
- "digest": "a6321c47a0cd188fbfdf3b55f17a7170c63080d28d50e4f5463eb1ee09af2412"
- },
- {
- "name": "pw",
- "unicode": "1F1F5-1F1FC",
+ "flag_pw": {
+ "category": "flags",
+ "moji": "🇵🇼",
+ "unicodeVersion": "6.0",
"digest": "a6321c47a0cd188fbfdf3b55f17a7170c63080d28d50e4f5463eb1ee09af2412"
},
- {
- "name": "flag_py",
- "unicode": "1F1F5-1F1FE",
+ "flag_py": {
+ "category": "flags",
+ "moji": "🇵🇾",
+ "unicodeVersion": "6.0",
"digest": "1a169e8d8703c510c5a2265b57dbed2f811b03ec375bcb341ab4cd0b100a9dd6"
},
- {
- "name": "py",
- "unicode": "1F1F5-1F1FE",
- "digest": "1a169e8d8703c510c5a2265b57dbed2f811b03ec375bcb341ab4cd0b100a9dd6"
- },
- {
- "name": "flag_qa",
- "unicode": "1F1F6-1F1E6",
- "digest": "de6283965cd98a244b7fa6288174f9ff0d8feb497f191f2e4ab3b690138a3d5d"
- },
- {
- "name": "qa",
- "unicode": "1F1F6-1F1E6",
+ "flag_qa": {
+ "category": "flags",
+ "moji": "🇶🇦",
+ "unicodeVersion": "6.0",
"digest": "de6283965cd98a244b7fa6288174f9ff0d8feb497f191f2e4ab3b690138a3d5d"
},
- {
- "name": "flag_re",
- "unicode": "1F1F7-1F1EA",
+ "flag_re": {
+ "category": "flags",
+ "moji": "🇷🇪",
+ "unicodeVersion": "6.0",
"digest": "260e1b97abc1562e5a73d7e53652ffed8059fc9b1c969741c466f48ec6ab0e80"
},
- {
- "name": "re",
- "unicode": "1F1F7-1F1EA",
- "digest": "260e1b97abc1562e5a73d7e53652ffed8059fc9b1c969741c466f48ec6ab0e80"
- },
- {
- "name": "flag_ro",
- "unicode": "1F1F7-1F1F4",
- "digest": "6d648e03955fa2a9fd2bad6f60ec96d3e20ee57f5855f3721a4d4e0c8e99f95c"
- },
- {
- "name": "ro",
- "unicode": "1F1F7-1F1F4",
+ "flag_ro": {
+ "category": "flags",
+ "moji": "🇷🇴",
+ "unicodeVersion": "6.0",
"digest": "6d648e03955fa2a9fd2bad6f60ec96d3e20ee57f5855f3721a4d4e0c8e99f95c"
},
- {
- "name": "flag_rs",
- "unicode": "1F1F7-1F1F8",
+ "flag_rs": {
+ "category": "flags",
+ "moji": "🇷🇸",
+ "unicodeVersion": "6.0",
"digest": "95cd5e197ed364e403eeb7f1d18a83487d89166910ba8119ea994e5e19d6a7ee"
},
- {
- "name": "rs",
- "unicode": "1F1F7-1F1F8",
- "digest": "95cd5e197ed364e403eeb7f1d18a83487d89166910ba8119ea994e5e19d6a7ee"
- },
- {
- "name": "flag_ru",
- "unicode": "1F1F7-1F1FA",
- "digest": "a4a81617a59d9eaf3c526431ca6f90ed334a7c1f516bf70cbd3f1fdc6e6103d7"
- },
- {
- "name": "ru",
- "unicode": "1F1F7-1F1FA",
+ "flag_ru": {
+ "category": "flags",
+ "moji": "🇷🇺",
+ "unicodeVersion": "6.0",
"digest": "a4a81617a59d9eaf3c526431ca6f90ed334a7c1f516bf70cbd3f1fdc6e6103d7"
},
- {
- "name": "flag_rw",
- "unicode": "1F1F7-1F1FC",
+ "flag_rw": {
+ "category": "flags",
+ "moji": "🇷🇼",
+ "unicodeVersion": "6.0",
"digest": "7a369f60db0876ffef111c319a3e8c9eaed620c875c51b98ed9ad5207b836dca"
},
- {
- "name": "rw",
- "unicode": "1F1F7-1F1FC",
- "digest": "7a369f60db0876ffef111c319a3e8c9eaed620c875c51b98ed9ad5207b836dca"
- },
- {
- "name": "flag_sa",
- "unicode": "1F1F8-1F1E6",
+ "flag_sa": {
+ "category": "flags",
+ "moji": "🇸🇦",
+ "unicodeVersion": "6.0",
"digest": "b249fbfd7ed415943f60bbd841965cf721979f960ccbe09396aebac1eca913d7"
},
- {
- "name": "saudiarabia",
- "unicode": "1F1F8-1F1E6",
- "digest": "b249fbfd7ed415943f60bbd841965cf721979f960ccbe09396aebac1eca913d7"
- },
- {
- "name": "saudi",
- "unicode": "1F1F8-1F1E6",
- "digest": "b249fbfd7ed415943f60bbd841965cf721979f960ccbe09396aebac1eca913d7"
- },
- {
- "name": "flag_sb",
- "unicode": "1F1F8-1F1E7",
- "digest": "526b411260024ea7b6ea6c47f2549345c6cc6960e9a29bfa9aaec0772664d2dc"
- },
- {
- "name": "sb",
- "unicode": "1F1F8-1F1E7",
+ "flag_sb": {
+ "category": "flags",
+ "moji": "🇸🇧",
+ "unicodeVersion": "6.0",
"digest": "526b411260024ea7b6ea6c47f2549345c6cc6960e9a29bfa9aaec0772664d2dc"
},
- {
- "name": "flag_sc",
- "unicode": "1F1F8-1F1E8",
+ "flag_sc": {
+ "category": "flags",
+ "moji": "🇸🇨",
+ "unicodeVersion": "6.0",
"digest": "d036b0d068745926120eaf746fa2e4433306e2e14c6b540d0cd6265e34471056"
},
- {
- "name": "sc",
- "unicode": "1F1F8-1F1E8",
- "digest": "d036b0d068745926120eaf746fa2e4433306e2e14c6b540d0cd6265e34471056"
- },
- {
- "name": "flag_sd",
- "unicode": "1F1F8-1F1E9",
- "digest": "889615bdb9b1f9c59c5f83ed4d22d54a0ed5dd5de263e729c58544cb06c55885"
- },
- {
- "name": "sd",
- "unicode": "1F1F8-1F1E9",
+ "flag_sd": {
+ "category": "flags",
+ "moji": "🇸🇩",
+ "unicodeVersion": "6.0",
"digest": "889615bdb9b1f9c59c5f83ed4d22d54a0ed5dd5de263e729c58544cb06c55885"
},
- {
- "name": "flag_se",
- "unicode": "1F1F8-1F1EA",
- "digest": "f471d80cfff340960a752c8c152ed4fb482df2a3712b0a56dfab31b9b806926a"
- },
- {
- "name": "se",
- "unicode": "1F1F8-1F1EA",
+ "flag_se": {
+ "category": "flags",
+ "moji": "🇸🇪",
+ "unicodeVersion": "6.0",
"digest": "f471d80cfff340960a752c8c152ed4fb482df2a3712b0a56dfab31b9b806926a"
},
- {
- "name": "flag_sg",
- "unicode": "1F1F8-1F1EC",
- "digest": "82f58a09f98593cc87e545f7e5c03d2aedaf82e54e73f71f58c18e994c3085ac"
- },
- {
- "name": "sg",
- "unicode": "1F1F8-1F1EC",
+ "flag_sg": {
+ "category": "flags",
+ "moji": "🇸🇬",
+ "unicodeVersion": "6.0",
"digest": "82f58a09f98593cc87e545f7e5c03d2aedaf82e54e73f71f58c18e994c3085ac"
},
- {
- "name": "flag_sh",
- "unicode": "1F1F8-1F1ED",
- "digest": "53914b1fa8c1b4f30bae6c1f6717f138fb4dbf482c3e20e33f7aea4ecfc0438d"
- },
- {
- "name": "sh",
- "unicode": "1F1F8-1F1ED",
+ "flag_sh": {
+ "category": "flags",
+ "moji": "🇸🇭",
+ "unicodeVersion": "6.0",
"digest": "53914b1fa8c1b4f30bae6c1f6717f138fb4dbf482c3e20e33f7aea4ecfc0438d"
},
- {
- "name": "flag_si",
- "unicode": "1F1F8-1F1EE",
- "digest": "65d491daa69f9a11cec9ccc4df3a669f12ef95a5c312137776d4472719940ba3"
- },
- {
- "name": "si",
- "unicode": "1F1F8-1F1EE",
+ "flag_si": {
+ "category": "flags",
+ "moji": "🇸🇮",
+ "unicodeVersion": "6.0",
"digest": "65d491daa69f9a11cec9ccc4df3a669f12ef95a5c312137776d4472719940ba3"
},
- {
- "name": "flag_sj",
- "unicode": "1F1F8-1F1EF",
- "digest": "bbf6daa6174c6fbbbf541c8274f31b6757c3a16007c2687015ea041fd1e2c6b6"
- },
- {
- "name": "sj",
- "unicode": "1F1F8-1F1EF",
+ "flag_sj": {
+ "category": "flags",
+ "moji": "🇸🇯",
+ "unicodeVersion": "6.0",
"digest": "bbf6daa6174c6fbbbf541c8274f31b6757c3a16007c2687015ea041fd1e2c6b6"
},
- {
- "name": "flag_sk",
- "unicode": "1F1F8-1F1F0",
+ "flag_sk": {
+ "category": "flags",
+ "moji": "🇸🇰",
+ "unicodeVersion": "6.0",
"digest": "d4fd03eca5bd3c9fb324ee04fae37c9a2d852bac8335369e3e720ef9b98fff36"
},
- {
- "name": "sk",
- "unicode": "1F1F8-1F1F0",
- "digest": "d4fd03eca5bd3c9fb324ee04fae37c9a2d852bac8335369e3e720ef9b98fff36"
- },
- {
- "name": "flag_sl",
- "unicode": "1F1F8-1F1F1",
- "digest": "1455c98c11c248623d82be5484ab1c4dcd1dae449adc393eb1aa2d8c74aa3f02"
- },
- {
- "name": "sl",
- "unicode": "1F1F8-1F1F1",
+ "flag_sl": {
+ "category": "flags",
+ "moji": "🇸🇱",
+ "unicodeVersion": "6.0",
"digest": "1455c98c11c248623d82be5484ab1c4dcd1dae449adc393eb1aa2d8c74aa3f02"
},
- {
- "name": "flag_sm",
- "unicode": "1F1F8-1F1F2",
+ "flag_sm": {
+ "category": "flags",
+ "moji": "🇸🇲",
+ "unicodeVersion": "6.0",
"digest": "daec5864ac50c625d7bf49d6c1a170a094cf0d1b9a0bdf62a62406e7ec500a94"
},
- {
- "name": "sm",
- "unicode": "1F1F8-1F1F2",
- "digest": "daec5864ac50c625d7bf49d6c1a170a094cf0d1b9a0bdf62a62406e7ec500a94"
- },
- {
- "name": "flag_sn",
- "unicode": "1F1F8-1F1F3",
- "digest": "4e4d43c467e5eb84c70f535f37f4f468319bd4b06c6ec3db3b54f69efdafd334"
- },
- {
- "name": "sn",
- "unicode": "1F1F8-1F1F3",
+ "flag_sn": {
+ "category": "flags",
+ "moji": "🇸🇳",
+ "unicodeVersion": "6.0",
"digest": "4e4d43c467e5eb84c70f535f37f4f468319bd4b06c6ec3db3b54f69efdafd334"
},
- {
- "name": "flag_so",
- "unicode": "1F1F8-1F1F4",
+ "flag_so": {
+ "category": "flags",
+ "moji": "🇸🇴",
+ "unicodeVersion": "6.0",
"digest": "c1434dca361563a8e3ba88f1ad19c3f6c9cbb8f3ebc17ce128fde2351ff67d0c"
},
- {
- "name": "so",
- "unicode": "1F1F8-1F1F4",
- "digest": "c1434dca361563a8e3ba88f1ad19c3f6c9cbb8f3ebc17ce128fde2351ff67d0c"
- },
- {
- "name": "flag_sr",
- "unicode": "1F1F8-1F1F7",
- "digest": "f3c6bfee2a052f03d56ba917b88595450cef111ffa9e92c7f39ef8c3c3bd12d1"
- },
- {
- "name": "sr",
- "unicode": "1F1F8-1F1F7",
+ "flag_sr": {
+ "category": "flags",
+ "moji": "🇸🇷",
+ "unicodeVersion": "6.0",
"digest": "f3c6bfee2a052f03d56ba917b88595450cef111ffa9e92c7f39ef8c3c3bd12d1"
},
- {
- "name": "flag_ss",
- "unicode": "1F1F8-1F1F8",
+ "flag_ss": {
+ "category": "flags",
+ "moji": "🇸🇸",
+ "unicodeVersion": "6.0",
"digest": "c0ed7e4f41206f5363e8ebdc6c3f28080e2f07d99e6fb73c1f6226d83310e69d"
},
- {
- "name": "ss",
- "unicode": "1F1F8-1F1F8",
- "digest": "c0ed7e4f41206f5363e8ebdc6c3f28080e2f07d99e6fb73c1f6226d83310e69d"
- },
- {
- "name": "flag_st",
- "unicode": "1F1F8-1F1F9",
+ "flag_st": {
+ "category": "flags",
+ "moji": "🇸🇹",
+ "unicodeVersion": "6.0",
"digest": "b022ae5d6885e28c6e9c83c17dd0c24c731d4f3d5773c49051768cdd4df51330"
},
- {
- "name": "st",
- "unicode": "1F1F8-1F1F9",
- "digest": "b022ae5d6885e28c6e9c83c17dd0c24c731d4f3d5773c49051768cdd4df51330"
- },
- {
- "name": "flag_sv",
- "unicode": "1F1F8-1F1FB",
+ "flag_sv": {
+ "category": "flags",
+ "moji": "🇸🇻",
+ "unicodeVersion": "6.0",
"digest": "5bafdd04d243ee3f3998f4ec0a3d03ff5a3975e771b1f94f89d7713193d7a242"
},
- {
- "name": "sv",
- "unicode": "1F1F8-1F1FB",
- "digest": "5bafdd04d243ee3f3998f4ec0a3d03ff5a3975e771b1f94f89d7713193d7a242"
- },
- {
- "name": "flag_sx",
- "unicode": "1F1F8-1F1FD",
- "digest": "fb92e9f514bcc2f7abbd4e146edde50f030c940c833f184618cbb48e56af22bd"
- },
- {
- "name": "sx",
- "unicode": "1F1F8-1F1FD",
+ "flag_sx": {
+ "category": "flags",
+ "moji": "🇸🇽",
+ "unicodeVersion": "6.0",
"digest": "fb92e9f514bcc2f7abbd4e146edde50f030c940c833f184618cbb48e56af22bd"
},
- {
- "name": "flag_sy",
- "unicode": "1F1F8-1F1FE",
+ "flag_sy": {
+ "category": "flags",
+ "moji": "🇸🇾",
+ "unicodeVersion": "6.0",
"digest": "ee330da644d4ce1fdba98be5eaab5054aed8d91a34ab617199a4b2b77f62a10b"
},
- {
- "name": "sy",
- "unicode": "1F1F8-1F1FE",
- "digest": "ee330da644d4ce1fdba98be5eaab5054aed8d91a34ab617199a4b2b77f62a10b"
- },
- {
- "name": "flag_sz",
- "unicode": "1F1F8-1F1FF",
- "digest": "7fe0c7429efd9682cc39e57f4bba8d1491d301643ba999d57c4e1bc37517ed64"
- },
- {
- "name": "sz",
- "unicode": "1F1F8-1F1FF",
+ "flag_sz": {
+ "category": "flags",
+ "moji": "🇸🇿",
+ "unicodeVersion": "6.0",
"digest": "7fe0c7429efd9682cc39e57f4bba8d1491d301643ba999d57c4e1bc37517ed64"
},
- {
- "name": "flag_ta",
- "unicode": "1F1F9-1F1E6",
- "digest": "b47e245a2708072a4dbaf190c9606baa4daf02e51627eeae6f20c3b4c95024c0"
- },
- {
- "name": "ta",
- "unicode": "1F1F9-1F1E6",
+ "flag_ta": {
+ "category": "flags",
+ "moji": "🇹🇦",
+ "unicodeVersion": "6.0",
"digest": "b47e245a2708072a4dbaf190c9606baa4daf02e51627eeae6f20c3b4c95024c0"
},
- {
- "name": "flag_tc",
- "unicode": "1F1F9-1F1E8",
- "digest": "18cfff14c2503b9d24c91c668583d4a14efb17657d800eca86ae49b547c9da5c"
- },
- {
- "name": "tc",
- "unicode": "1F1F9-1F1E8",
+ "flag_tc": {
+ "category": "flags",
+ "moji": "🇹🇨",
+ "unicodeVersion": "6.0",
"digest": "18cfff14c2503b9d24c91c668583d4a14efb17657d800eca86ae49b547c9da5c"
},
- {
- "name": "flag_td",
- "unicode": "1F1F9-1F1E9",
+ "flag_td": {
+ "category": "flags",
+ "moji": "🇹🇩",
+ "unicodeVersion": "6.0",
"digest": "73d1db3365736915c4cdf9ba9343d9fd78962203b60334e8f3724d4b330b17db"
},
- {
- "name": "td",
- "unicode": "1F1F9-1F1E9",
- "digest": "73d1db3365736915c4cdf9ba9343d9fd78962203b60334e8f3724d4b330b17db"
- },
- {
- "name": "flag_tf",
- "unicode": "1F1F9-1F1EB",
- "digest": "3bffeb4bc9ceb9cbb150de88e957b6e46509862ca7d616d5693124af084eb435"
- },
- {
- "name": "tf",
- "unicode": "1F1F9-1F1EB",
+ "flag_tf": {
+ "category": "flags",
+ "moji": "🇹🇫",
+ "unicodeVersion": "6.0",
"digest": "3bffeb4bc9ceb9cbb150de88e957b6e46509862ca7d616d5693124af084eb435"
},
- {
- "name": "flag_tg",
- "unicode": "1F1F9-1F1EC",
- "digest": "eb13a0e85baf73326f3ae3bc75e8406eca42000d7e42b0641120e64c0ab7ebaa"
- },
- {
- "name": "tg",
- "unicode": "1F1F9-1F1EC",
+ "flag_tg": {
+ "category": "flags",
+ "moji": "🇹🇬",
+ "unicodeVersion": "6.0",
"digest": "eb13a0e85baf73326f3ae3bc75e8406eca42000d7e42b0641120e64c0ab7ebaa"
},
- {
- "name": "flag_th",
- "unicode": "1F1F9-1F1ED",
- "digest": "a4e42efa4bb94e90f3a92ae9ce14affaacd3a142c1e0da40d8cc839500e771fd"
- },
- {
- "name": "th",
- "unicode": "1F1F9-1F1ED",
+ "flag_th": {
+ "category": "flags",
+ "moji": "🇹🇭",
+ "unicodeVersion": "6.0",
"digest": "a4e42efa4bb94e90f3a92ae9ce14affaacd3a142c1e0da40d8cc839500e771fd"
},
- {
- "name": "flag_tj",
- "unicode": "1F1F9-1F1EF",
- "digest": "ff926fa3e86e095683a61c4754355a5b4dd0ecb74393306bd791d130fd1a909d"
- },
- {
- "name": "tj",
- "unicode": "1F1F9-1F1EF",
+ "flag_tj": {
+ "category": "flags",
+ "moji": "🇹🇯",
+ "unicodeVersion": "6.0",
"digest": "ff926fa3e86e095683a61c4754355a5b4dd0ecb74393306bd791d130fd1a909d"
},
- {
- "name": "flag_tk",
- "unicode": "1F1F9-1F1F0",
- "digest": "3fa732d457ded6c83cd5f73d934f64c4e687eb0cde7c157d2fdcdccaf3b5fb52"
- },
- {
- "name": "tk",
- "unicode": "1F1F9-1F1F0",
+ "flag_tk": {
+ "category": "flags",
+ "moji": "🇹🇰",
+ "unicodeVersion": "6.0",
"digest": "3fa732d457ded6c83cd5f73d934f64c4e687eb0cde7c157d2fdcdccaf3b5fb52"
},
- {
- "name": "flag_tl",
- "unicode": "1F1F9-1F1F1",
- "digest": "0ec2a4d22fb832060693089e518bbe370a4e13bfc28748f110fc13726409f473"
- },
- {
- "name": "tl",
- "unicode": "1F1F9-1F1F1",
+ "flag_tl": {
+ "category": "flags",
+ "moji": "🇹🇱",
+ "unicodeVersion": "6.0",
"digest": "0ec2a4d22fb832060693089e518bbe370a4e13bfc28748f110fc13726409f473"
},
- {
- "name": "flag_tm",
- "unicode": "1F1F9-1F1F2",
- "digest": "b4724aa7ad13352f16a0936e61cbb85f0bd147583fc66597aff7e8ee7cf19c21"
- },
- {
- "name": "turkmenistan",
- "unicode": "1F1F9-1F1F2",
+ "flag_tm": {
+ "category": "flags",
+ "moji": "🇹🇲",
+ "unicodeVersion": "6.0",
"digest": "b4724aa7ad13352f16a0936e61cbb85f0bd147583fc66597aff7e8ee7cf19c21"
},
- {
- "name": "flag_tn",
- "unicode": "1F1F9-1F1F3",
+ "flag_tn": {
+ "category": "flags",
+ "moji": "🇹🇳",
+ "unicodeVersion": "6.0",
"digest": "5ab308ffdde40f504d6ee080817bbddbe4f3f4ddb71f508c75e0144a8c8044d9"
},
- {
- "name": "tn",
- "unicode": "1F1F9-1F1F3",
- "digest": "5ab308ffdde40f504d6ee080817bbddbe4f3f4ddb71f508c75e0144a8c8044d9"
- },
- {
- "name": "flag_to",
- "unicode": "1F1F9-1F1F4",
- "digest": "75b7e7198fa42f87986882b8ca251a229afcaa0a1188ae7b9f5ece87dc31a723"
- },
- {
- "name": "to",
- "unicode": "1F1F9-1F1F4",
+ "flag_to": {
+ "category": "flags",
+ "moji": "🇹🇴",
+ "unicodeVersion": "6.0",
"digest": "75b7e7198fa42f87986882b8ca251a229afcaa0a1188ae7b9f5ece87dc31a723"
},
- {
- "name": "flag_tr",
- "unicode": "1F1F9-1F1F7",
- "digest": "9cc48a8f8fa9c17c1627272f68d4740da0e7ce17a2cf8c6b5c08cc9b95e1390c"
- },
- {
- "name": "tr",
- "unicode": "1F1F9-1F1F7",
+ "flag_tr": {
+ "category": "flags",
+ "moji": "🇹🇷",
+ "unicodeVersion": "6.0",
"digest": "9cc48a8f8fa9c17c1627272f68d4740da0e7ce17a2cf8c6b5c08cc9b95e1390c"
},
- {
- "name": "flag_tt",
- "unicode": "1F1F9-1F1F9",
+ "flag_tt": {
+ "category": "flags",
+ "moji": "🇹🇹",
+ "unicodeVersion": "6.0",
"digest": "f9e63543121bb3cd2e41bc7b0c2c4ba662bc1cc0520b79fc4e201ec6456fdf59"
},
- {
- "name": "tt",
- "unicode": "1F1F9-1F1F9",
- "digest": "f9e63543121bb3cd2e41bc7b0c2c4ba662bc1cc0520b79fc4e201ec6456fdf59"
- },
- {
- "name": "flag_tv",
- "unicode": "1F1F9-1F1FB",
- "digest": "6431e5f06cc7995ae7208c429ecf39339b545854cb6d6b7447f465fe53614dfc"
- },
- {
- "name": "tuvalu",
- "unicode": "1F1F9-1F1FB",
+ "flag_tv": {
+ "category": "flags",
+ "moji": "🇹🇻",
+ "unicodeVersion": "6.0",
"digest": "6431e5f06cc7995ae7208c429ecf39339b545854cb6d6b7447f465fe53614dfc"
},
- {
- "name": "flag_tw",
- "unicode": "1F1F9-1F1FC",
+ "flag_tw": {
+ "category": "flags",
+ "moji": "🇹🇼",
+ "unicodeVersion": "6.0",
"digest": "8395ab3c6a595023b006518a5345ac3612f2893d3a8f011b7e5802414236b03c"
},
- {
- "name": "tw",
- "unicode": "1F1F9-1F1FC",
- "digest": "8395ab3c6a595023b006518a5345ac3612f2893d3a8f011b7e5802414236b03c"
- },
- {
- "name": "flag_tz",
- "unicode": "1F1F9-1F1FF",
- "digest": "716181733cd9ac7a8f51a9a64bc5d21020e8112f6768e8c49c4d651a3ee0b8a4"
- },
- {
- "name": "tz",
- "unicode": "1F1F9-1F1FF",
+ "flag_tz": {
+ "category": "flags",
+ "moji": "🇹🇿",
+ "unicodeVersion": "6.0",
"digest": "716181733cd9ac7a8f51a9a64bc5d21020e8112f6768e8c49c4d651a3ee0b8a4"
},
- {
- "name": "flag_ua",
- "unicode": "1F1FA-1F1E6",
+ "flag_ua": {
+ "category": "flags",
+ "moji": "🇺🇦",
+ "unicodeVersion": "6.0",
"digest": "304570736345e28734f5ff84a2b0481c2bb00bf29d9892bd749b57dec7741e30"
},
- {
- "name": "ua",
- "unicode": "1F1FA-1F1E6",
- "digest": "304570736345e28734f5ff84a2b0481c2bb00bf29d9892bd749b57dec7741e30"
- },
- {
- "name": "flag_ug",
- "unicode": "1F1FA-1F1EC",
- "digest": "a1bafb74c54ee8c92cb025b55aebdb6081eec3fda6a7f86f2ee14d1b801a8e9c"
- },
- {
- "name": "ug",
- "unicode": "1F1FA-1F1EC",
+ "flag_ug": {
+ "category": "flags",
+ "moji": "🇺🇬",
+ "unicodeVersion": "6.0",
"digest": "a1bafb74c54ee8c92cb025b55aebdb6081eec3fda6a7f86f2ee14d1b801a8e9c"
},
- {
- "name": "flag_um",
- "unicode": "1F1FA-1F1F2",
+ "flag_um": {
+ "category": "flags",
+ "moji": "🇺🇲",
+ "unicodeVersion": "6.0",
"digest": "b3c9ac72211f481f50cde09e10b92aa03b1ea90abf85418e60a35b84963273ee"
},
- {
- "name": "um",
- "unicode": "1F1FA-1F1F2",
- "digest": "b3c9ac72211f481f50cde09e10b92aa03b1ea90abf85418e60a35b84963273ee"
- },
- {
- "name": "flag_us",
- "unicode": "1F1FA-1F1F8",
- "digest": "da79f9af0a188178a82e7dc3a62298fa416f4cfbcae432838df1abebca5c0d63"
- },
- {
- "name": "us",
- "unicode": "1F1FA-1F1F8",
+ "flag_us": {
+ "category": "flags",
+ "moji": "🇺🇸",
+ "unicodeVersion": "6.0",
"digest": "da79f9af0a188178a82e7dc3a62298fa416f4cfbcae432838df1abebca5c0d63"
},
- {
- "name": "flag_uy",
- "unicode": "1F1FA-1F1FE",
+ "flag_uy": {
+ "category": "flags",
+ "moji": "🇺🇾",
+ "unicodeVersion": "6.0",
"digest": "8348e901d775722497ee911c9c9b4bd767710760c507630a67ecb6d47cc646c7"
},
- {
- "name": "uy",
- "unicode": "1F1FA-1F1FE",
- "digest": "8348e901d775722497ee911c9c9b4bd767710760c507630a67ecb6d47cc646c7"
- },
- {
- "name": "flag_uz",
- "unicode": "1F1FA-1F1FF",
- "digest": "2a1dc1e9469e01c58ea91f545ef3fe0bdfe5544a73a80407f8960d01b1e5db5c"
- },
- {
- "name": "uz",
- "unicode": "1F1FA-1F1FF",
+ "flag_uz": {
+ "category": "flags",
+ "moji": "🇺🇿",
+ "unicodeVersion": "6.0",
"digest": "2a1dc1e9469e01c58ea91f545ef3fe0bdfe5544a73a80407f8960d01b1e5db5c"
},
- {
- "name": "flag_va",
- "unicode": "1F1FB-1F1E6",
+ "flag_va": {
+ "category": "flags",
+ "moji": "🇻🇦",
+ "unicodeVersion": "6.0",
"digest": "0e8134ec94bff032bfc63b0b08587d5298c9b7f31edd5a5b35633ae911434e61"
},
- {
- "name": "va",
- "unicode": "1F1FB-1F1E6",
- "digest": "0e8134ec94bff032bfc63b0b08587d5298c9b7f31edd5a5b35633ae911434e61"
- },
- {
- "name": "flag_vc",
- "unicode": "1F1FB-1F1E8",
- "digest": "e0290e1be72c8939ee6c398f00a107703b21b97d91b9bf465e553ffbf00304a7"
- },
- {
- "name": "vc",
- "unicode": "1F1FB-1F1E8",
+ "flag_vc": {
+ "category": "flags",
+ "moji": "🇻🇨",
+ "unicodeVersion": "6.0",
"digest": "e0290e1be72c8939ee6c398f00a107703b21b97d91b9bf465e553ffbf00304a7"
},
- {
- "name": "flag_ve",
- "unicode": "1F1FB-1F1EA",
+ "flag_ve": {
+ "category": "flags",
+ "moji": "🇻🇪",
+ "unicodeVersion": "6.0",
"digest": "76a6a6c2353def1f984d1a6980831e63f3aea5af2201b574197834e7c203d57a"
},
- {
- "name": "ve",
- "unicode": "1F1FB-1F1EA",
- "digest": "76a6a6c2353def1f984d1a6980831e63f3aea5af2201b574197834e7c203d57a"
- },
- {
- "name": "flag_vg",
- "unicode": "1F1FB-1F1EC",
- "digest": "56fc9317b8dd62cccc60010819f8b895dd4569a9b06368a9250f815c39177b8a"
- },
- {
- "name": "vg",
- "unicode": "1F1FB-1F1EC",
+ "flag_vg": {
+ "category": "flags",
+ "moji": "🇻🇬",
+ "unicodeVersion": "6.0",
"digest": "56fc9317b8dd62cccc60010819f8b895dd4569a9b06368a9250f815c39177b8a"
},
- {
- "name": "flag_vi",
- "unicode": "1F1FB-1F1EE",
+ "flag_vi": {
+ "category": "flags",
+ "moji": "🇻🇮",
+ "unicodeVersion": "6.0",
"digest": "2526a3e13b8ccd301f0763580430898c227bd209e3ce482c7951140b28948375"
},
- {
- "name": "vi",
- "unicode": "1F1FB-1F1EE",
- "digest": "2526a3e13b8ccd301f0763580430898c227bd209e3ce482c7951140b28948375"
- },
- {
- "name": "flag_vn",
- "unicode": "1F1FB-1F1F3",
+ "flag_vn": {
+ "category": "flags",
+ "moji": "🇻🇳",
+ "unicodeVersion": "6.0",
"digest": "0cf6b9896bbe4da8ed7718d0abfd56cef1a8321e26f89d3ad1b48488eaffb7a5"
},
- {
- "name": "vn",
- "unicode": "1F1FB-1F1F3",
- "digest": "0cf6b9896bbe4da8ed7718d0abfd56cef1a8321e26f89d3ad1b48488eaffb7a5"
- },
- {
- "name": "flag_vu",
- "unicode": "1F1FB-1F1FA",
+ "flag_vu": {
+ "category": "flags",
+ "moji": "🇻🇺",
+ "unicodeVersion": "6.0",
"digest": "9dfa282ce1aafc62beacab76e1fc19a141c8bdeaa30898f69b083067b775d362"
},
- {
- "name": "vu",
- "unicode": "1F1FB-1F1FA",
- "digest": "9dfa282ce1aafc62beacab76e1fc19a141c8bdeaa30898f69b083067b775d362"
- },
- {
- "name": "flag_wf",
- "unicode": "1F1FC-1F1EB",
- "digest": "a0124683aa88cd7da886da70c65796c5ad84eb3751e356e9b2aa8ac249cf0bf9"
- },
- {
- "name": "wf",
- "unicode": "1F1FC-1F1EB",
+ "flag_wf": {
+ "category": "flags",
+ "moji": "🇼🇫",
+ "unicodeVersion": "6.0",
"digest": "a0124683aa88cd7da886da70c65796c5ad84eb3751e356e9b2aa8ac249cf0bf9"
},
- {
- "name": "flag_white",
- "unicode": "1F3F3",
+ "flag_white": {
+ "category": "objects",
+ "moji": "🏳",
+ "unicodeVersion": "6.0",
"digest": "d9be4b7ceb8309c48f88cfd07a9f7ce6758ea6e620e73293cf14baec03ca381c"
},
- {
- "name": "waving_white_flag",
- "unicode": "1F3F3",
- "digest": "d9be4b7ceb8309c48f88cfd07a9f7ce6758ea6e620e73293cf14baec03ca381c"
- },
- {
- "name": "flag_ws",
- "unicode": "1F1FC-1F1F8",
- "digest": "53addd0dc304a3c8893389ed227986ef2431828b8c071926aa09f9efd815b649"
- },
- {
- "name": "ws",
- "unicode": "1F1FC-1F1F8",
+ "flag_ws": {
+ "category": "flags",
+ "moji": "🇼🇸",
+ "unicodeVersion": "6.0",
"digest": "53addd0dc304a3c8893389ed227986ef2431828b8c071926aa09f9efd815b649"
},
- {
- "name": "flag_xk",
- "unicode": "1F1FD-1F1F0",
- "digest": "eba1a832e489e1c2734e773e685df5d128271fa5559d23c060e68be067bf6469"
- },
- {
- "name": "xk",
- "unicode": "1F1FD-1F1F0",
+ "flag_xk": {
+ "category": "flags",
+ "moji": "🇽🇰",
+ "unicodeVersion": "6.0",
"digest": "eba1a832e489e1c2734e773e685df5d128271fa5559d23c060e68be067bf6469"
},
- {
- "name": "flag_ye",
- "unicode": "1F1FE-1F1EA",
- "digest": "edfa14266785042b6d5fe0f64fafa630b16a3ee7d010501de7cc8554c959afb0"
- },
- {
- "name": "ye",
- "unicode": "1F1FE-1F1EA",
+ "flag_ye": {
+ "category": "flags",
+ "moji": "🇾🇪",
+ "unicodeVersion": "6.0",
"digest": "edfa14266785042b6d5fe0f64fafa630b16a3ee7d010501de7cc8554c959afb0"
},
- {
- "name": "flag_yt",
- "unicode": "1F1FE-1F1F9",
+ "flag_yt": {
+ "category": "flags",
+ "moji": "🇾🇹",
+ "unicodeVersion": "6.0",
"digest": "472ebc676b5d31dec2ac5e02ce69014a3dd94609d30a95f39f3a752f49c85e8b"
},
- {
- "name": "yt",
- "unicode": "1F1FE-1F1F9",
- "digest": "472ebc676b5d31dec2ac5e02ce69014a3dd94609d30a95f39f3a752f49c85e8b"
- },
- {
- "name": "flag_za",
- "unicode": "1F1FF-1F1E6",
- "digest": "dad162942a43392b4cff6929bd5cbf58c382a03dbc0e552f03c07ad2d8ff08ce"
- },
- {
- "name": "za",
- "unicode": "1F1FF-1F1E6",
+ "flag_za": {
+ "category": "flags",
+ "moji": "🇿🇦",
+ "unicodeVersion": "6.0",
"digest": "dad162942a43392b4cff6929bd5cbf58c382a03dbc0e552f03c07ad2d8ff08ce"
},
- {
- "name": "flag_zm",
- "unicode": "1F1FF-1F1F2",
- "digest": "1521ecaf1d1fdc8c15f0c96a6b04e6d4050f26f943a826b3d3d661f6ded6d438"
- },
- {
- "name": "zm",
- "unicode": "1F1FF-1F1F2",
+ "flag_zm": {
+ "category": "flags",
+ "moji": "🇿🇲",
+ "unicodeVersion": "6.0",
"digest": "1521ecaf1d1fdc8c15f0c96a6b04e6d4050f26f943a826b3d3d661f6ded6d438"
},
- {
- "name": "flag_zw",
- "unicode": "1F1FF-1F1FC",
- "digest": "46d05b597c5c77c8e2dc7bd6d8dd62ebca01bc9c9dc9915dafe694ca56402825"
- },
- {
- "name": "zw",
- "unicode": "1F1FF-1F1FC",
+ "flag_zw": {
+ "category": "flags",
+ "moji": "🇿🇼",
+ "unicodeVersion": "6.0",
"digest": "46d05b597c5c77c8e2dc7bd6d8dd62ebca01bc9c9dc9915dafe694ca56402825"
},
- {
- "name": "flags",
- "unicode": "1F38F",
+ "flags": {
+ "category": "objects",
+ "moji": "🎏",
+ "unicodeVersion": "6.0",
"digest": "f860aa4df587cf140c3e9735bbd101e9fd5a1bfcea42e420d85ac0a9877fa21d"
},
- {
- "name": "flashlight",
- "unicode": "1F526",
+ "flashlight": {
+ "category": "objects",
+ "moji": "🔦",
+ "unicodeVersion": "6.0",
"digest": "e929bbe76e0fd2dc5bd6476858a0bbc717fd21467710435d35d80efb38033d73"
},
- {
- "name": "fleur-de-lis",
- "unicode": "269C",
+ "fleur-de-lis": {
+ "category": "symbols",
+ "moji": "⚜",
+ "unicodeVersion": "4.1",
"digest": "ebf49007f367dc05580e9dab942e93e9dda12fa1dc2caa410ac7f8d8cd55d2a3"
},
- {
- "name": "floppy_disk",
- "unicode": "1F4BE",
+ "floppy_disk": {
+ "category": "objects",
+ "moji": "💾",
+ "unicodeVersion": "6.0",
"digest": "4ee0b5bba41b9e301ed125d3ee1c263bef171ca499e6e1b89276b09af2bc03a0"
},
- {
- "name": "flower_playing_cards",
- "unicode": "1F3B4",
+ "flower_playing_cards": {
+ "category": "symbols",
+ "moji": "🎴",
+ "unicodeVersion": "6.0",
"digest": "edba47c2e3051b2c7effd98794ec977174052782edcb491daec82a2b0d853869"
},
- {
- "name": "flushed",
- "unicode": "1F633",
+ "flushed": {
+ "category": "people",
+ "moji": "😳",
+ "unicodeVersion": "6.0",
"digest": "e759d46bab92af5494d78b6c712c06568759afe397e7828ca0a0de1e3eab0165"
},
- {
- "name": "fog",
- "unicode": "1F32B",
+ "fog": {
+ "category": "nature",
+ "moji": "🌫",
+ "unicodeVersion": "7.0",
"digest": "0cbd4733961d30fe0f40f95dd1f37254aebbef26f82dd18ad2000e799eb2898e"
},
- {
- "name": "foggy",
- "unicode": "1F301",
+ "foggy": {
+ "category": "travel",
+ "moji": "🌁",
+ "unicodeVersion": "6.0",
"digest": "bc3631a4e9e8473b92e842008937add2cd9ffad5b7d772ce759fb5ff6c0e3dca"
},
- {
- "name": "football",
- "unicode": "1F3C8",
+ "football": {
+ "category": "activity",
+ "moji": "🏈",
+ "unicodeVersion": "6.0",
"digest": "ebd790471c3a28d3077818e3b31d915ffe443e06e299bc5cf0dd2534d080634c"
},
- {
- "name": "footprints",
- "unicode": "1F463",
+ "footprints": {
+ "category": "people",
+ "moji": "👣",
+ "unicodeVersion": "6.0",
"digest": "85bbf2bc0ae8e6259d83a06f513600095d7fcfc44372670f5b2405d380b78811"
},
- {
- "name": "fork_and_knife",
- "unicode": "1F374",
+ "fork_and_knife": {
+ "category": "food",
+ "moji": "🍴",
+ "unicodeVersion": "6.0",
"digest": "f228accd36ddccb4ec636207c19d7185191ec79723b780a1bd5c3d00a4b1ef3b"
},
- {
- "name": "fork_knife_plate",
- "unicode": "1F37D",
- "digest": "ec6be99dac8efd3d145807fa60d2b6d8f6d3c02cb95552b55cc0fac39a4db48e"
- },
- {
- "name": "fork_and_knife_with_plate",
- "unicode": "1F37D",
+ "fork_knife_plate": {
+ "category": "food",
+ "moji": "🍽",
+ "unicodeVersion": "7.0",
"digest": "ec6be99dac8efd3d145807fa60d2b6d8f6d3c02cb95552b55cc0fac39a4db48e"
},
- {
- "name": "fountain",
- "unicode": "26F2",
+ "fountain": {
+ "category": "travel",
+ "moji": "⛲",
+ "unicodeVersion": "5.2",
"digest": "87043f9256e1d4615159307fcfd21bf6ae2aba0bada7de2bd50d7d6f2ab82395"
},
- {
- "name": "four",
- "unicode": "0034-20E3",
+ "four": {
+ "category": "symbols",
+ "moji": "4️⃣",
+ "unicodeVersion": "3.0",
"digest": "c2c82a966bbb599aae557d930a4fc42604f2081aa45528872f5caf4942ee79d9"
},
- {
- "name": "four_leaf_clover",
- "unicode": "1F340",
+ "four_leaf_clover": {
+ "category": "nature",
+ "moji": "🍀",
+ "unicodeVersion": "6.0",
"digest": "ebee16e86bc9be843dfc72ab5372fb462f06be4486b5b25d7d4cac9b2c8b01c8"
},
- {
- "name": "fox",
- "unicode": "1F98A",
- "digest": "e9903cb0396f7e49bdd2c384b38e614c13bfa576b3ecc1ec7b9819e4a40d91d1"
- },
- {
- "name": "fox_face",
- "unicode": "1F98A",
+ "fox": {
+ "category": "nature",
+ "moji": "🦊",
+ "unicodeVersion": "9.0",
"digest": "e9903cb0396f7e49bdd2c384b38e614c13bfa576b3ecc1ec7b9819e4a40d91d1"
},
- {
- "name": "frame_photo",
- "unicode": "1F5BC",
- "digest": "d5074f748a15055ec1fb812c1e5e169e6e3cc73c522c54be1359b0e26c0fc75c"
- },
- {
- "name": "frame_with_picture",
- "unicode": "1F5BC",
+ "frame_photo": {
+ "category": "objects",
+ "moji": "🖼",
+ "unicodeVersion": "7.0",
"digest": "d5074f748a15055ec1fb812c1e5e169e6e3cc73c522c54be1359b0e26c0fc75c"
},
- {
- "name": "free",
- "unicode": "1F193",
+ "free": {
+ "category": "symbols",
+ "moji": "🆓",
+ "unicodeVersion": "6.0",
"digest": "9973522457158362fc5bdd7da858e6371e28a8403d1ef9e4b6427195c7f72cfa"
},
- {
- "name": "french_bread",
- "unicode": "1F956",
- "digest": "47518a4312f57207b8e8c38188d4a2bd8b16830a885cfcf2d281cfab50c1bc6e"
- },
- {
- "name": "baguette_bread",
- "unicode": "1F956",
+ "french_bread": {
+ "category": "food",
+ "moji": "🥖",
+ "unicodeVersion": "9.0",
"digest": "47518a4312f57207b8e8c38188d4a2bd8b16830a885cfcf2d281cfab50c1bc6e"
},
- {
- "name": "fried_shrimp",
- "unicode": "1F364",
+ "fried_shrimp": {
+ "category": "food",
+ "moji": "🍤",
+ "unicodeVersion": "6.0",
"digest": "0792bdc4484852de970c8f43bc3a1a339dc0e48090ec77d6de97cbfcdd17f9e1"
},
- {
- "name": "fries",
- "unicode": "1F35F",
+ "fries": {
+ "category": "food",
+ "moji": "🍟",
+ "unicodeVersion": "6.0",
"digest": "47915aea67251d358d91a0e4dc3dcc347155336007d6b931a192be72a743b4e9"
},
- {
- "name": "frog",
- "unicode": "1F438",
+ "frog": {
+ "category": "nature",
+ "moji": "🐸",
+ "unicodeVersion": "6.0",
"digest": "d024b2ce771df64040534fb0906737d18b562bc3578dee62c2f25ec03c7caffd"
},
- {
- "name": "frowning",
- "unicode": "1F626",
- "digest": "c01af48537b0011d313d8f65103e1401fce4f5c0269c68e0e9806926c59acc44"
- },
- {
- "name": "anguished",
- "unicode": "1F626",
+ "frowning": {
+ "category": "people",
+ "moji": "😦",
+ "unicodeVersion": "6.1",
"digest": "c01af48537b0011d313d8f65103e1401fce4f5c0269c68e0e9806926c59acc44"
},
- {
- "name": "frowning2",
- "unicode": "2639",
- "digest": "6568ee393b950c852d440112e86908c456b89fb7780e27778c5fcec168373fbf"
- },
- {
- "name": "white_frowning_face",
- "unicode": "2639",
+ "frowning2": {
+ "category": "people",
+ "moji": "☹",
+ "unicodeVersion": "1.1",
"digest": "6568ee393b950c852d440112e86908c456b89fb7780e27778c5fcec168373fbf"
},
- {
- "name": "fuelpump",
- "unicode": "26FD",
+ "fuelpump": {
+ "category": "travel",
+ "moji": "⛽",
+ "unicodeVersion": "5.2",
"digest": "105e736469f19911b8bab4ab6d29f949ded4b061b54e3dd763726577d6453095"
},
- {
- "name": "full_moon",
- "unicode": "1F315",
+ "full_moon": {
+ "category": "nature",
+ "moji": "🌕",
+ "unicodeVersion": "6.0",
"digest": "aaa87f4676a5aaa29c1b721a3b582e89db6c1f35a25c52e4b480bd193ef39c43"
},
- {
- "name": "full_moon_with_face",
- "unicode": "1F31D",
+ "full_moon_with_face": {
+ "category": "nature",
+ "moji": "🌝",
+ "unicodeVersion": "6.0",
"digest": "05c4b9c339fcdf81ae67027641522baa99c370d87873ff4c8133b8349e627e33"
},
- {
- "name": "game_die",
- "unicode": "1F3B2",
+ "game_die": {
+ "category": "activity",
+ "moji": "🎲",
+ "unicodeVersion": "6.0",
"digest": "00d19ce8e21dba2cdfeb18709fa8741f3af9d6207f81d5657b68e05e64f105a8"
},
- {
- "name": "gear",
- "unicode": "2699",
+ "gear": {
+ "category": "objects",
+ "moji": "⚙",
+ "unicodeVersion": "4.1",
"digest": "c5ba354c0f7a36dce95477091984e352ecc59af8c9f26a94ad8e296dc042b9de"
},
- {
- "name": "gem",
- "unicode": "1F48E",
+ "gem": {
+ "category": "objects",
+ "moji": "💎",
+ "unicodeVersion": "6.0",
"digest": "180e66f19d9285e02d0a5e859722c608206826e80323942b9938fc49d44973b1"
},
- {
- "name": "gemini",
- "unicode": "264A",
+ "gemini": {
+ "category": "symbols",
+ "moji": "♊",
+ "unicodeVersion": "1.1",
"digest": "278239c598d490a110f1f3f52fc3b85259be8e76034b38228ef3f68d7ddd8cdd"
},
- {
- "name": "ghost",
- "unicode": "1F47B",
+ "ghost": {
+ "category": "people",
+ "moji": "👻",
+ "unicodeVersion": "6.0",
"digest": "80d528fcf8ef9198631527547e43a608a4332a799f9e5550b8318dec67c9c4d2"
},
- {
- "name": "gift",
- "unicode": "1F381",
+ "gift": {
+ "category": "objects",
+ "moji": "🎁",
+ "unicodeVersion": "6.0",
"digest": "4061a84a59f0300473299678c43e533341eb965db09597fffc6e221fd7b77376"
},
- {
- "name": "gift_heart",
- "unicode": "1F49D",
+ "gift_heart": {
+ "category": "symbols",
+ "moji": "💝",
+ "unicodeVersion": "6.0",
"digest": "5420199b515b9b32c964a3c19d87e07461639e3068a939dae26c6436335c0cee"
},
- {
- "name": "girl",
- "unicode": "1F467",
+ "girl": {
+ "category": "people",
+ "moji": "👧",
+ "unicodeVersion": "6.0",
"digest": "8d2d0b72a91e6e44921b71030ffc4c89c0f50f1364787784afe1e7e568cf1bc6"
},
- {
- "name": "girl_tone1",
- "unicode": "1F467-1F3FB",
+ "girl_tone1": {
+ "category": "people",
+ "moji": "👧🏻",
+ "unicodeVersion": "8.0",
"digest": "bda12a6b38994a578ee65166bbdd93ea04df4101697b52ed236de8d687df09de"
},
- {
- "name": "girl_tone2",
- "unicode": "1F467-1F3FC",
+ "girl_tone2": {
+ "category": "people",
+ "moji": "👧🏼",
+ "unicodeVersion": "8.0",
"digest": "de7a0925c30b7181a289f71b1a849c1b7751ee8c104e8f2029bd9c2fe3f91c64"
},
- {
- "name": "girl_tone3",
- "unicode": "1F467-1F3FD",
+ "girl_tone3": {
+ "category": "people",
+ "moji": "👧🏽",
+ "unicodeVersion": "8.0",
"digest": "e41272816db0e642d003dce7cb262e1593a592251f46729f7830f4515149e1f2"
},
- {
- "name": "girl_tone4",
- "unicode": "1F467-1F3FE",
+ "girl_tone4": {
+ "category": "people",
+ "moji": "👧🏾",
+ "unicodeVersion": "8.0",
"digest": "8d6a4513ecbf08408c0ecc5336767777a2216f7a19437faf9e51f65101822469"
},
- {
- "name": "girl_tone5",
- "unicode": "1F467-1F3FF",
+ "girl_tone5": {
+ "category": "people",
+ "moji": "👧🏿",
+ "unicodeVersion": "8.0",
"digest": "f55e4b16a41b6f5e3c817a301420360ba4486e4e82e1092a56a3e3cc4069087d"
},
- {
- "name": "globe_with_meridians",
- "unicode": "1F310",
+ "globe_with_meridians": {
+ "category": "symbols",
+ "moji": "🌐",
+ "unicodeVersion": "6.0",
"digest": "725bebeb3c09a9e3701ebe49e672dcfbf2b73575e05f0821263511577b013b75"
},
- {
- "name": "goal",
- "unicode": "1F945",
- "digest": "7088c432f276ff6f447dc0d431b9062b394fb401de1072fe59ca56267bfd6717"
- },
- {
- "name": "goal_net",
- "unicode": "1F945",
+ "goal": {
+ "category": "activity",
+ "moji": "🥅",
+ "unicodeVersion": "9.0",
"digest": "7088c432f276ff6f447dc0d431b9062b394fb401de1072fe59ca56267bfd6717"
},
- {
- "name": "goat",
- "unicode": "1F410",
+ "goat": {
+ "category": "nature",
+ "moji": "🐐",
+ "unicodeVersion": "6.0",
"digest": "d07e384d08529ddcaddd2710f2ad913e5665dc15d5f99c28e16dadd245a111e8"
},
- {
- "name": "golf",
- "unicode": "26F3",
+ "golf": {
+ "category": "activity",
+ "moji": "⛳",
+ "unicodeVersion": "5.2",
"digest": "eed79364754eec97855e3c7b584f347ae139d9ddb4eb7fb66c00867610b8f1c1"
},
- {
- "name": "golfer",
- "unicode": "1F3CC",
+ "golfer": {
+ "category": "activity",
+ "moji": "🏌",
+ "unicodeVersion": "7.0",
"digest": "7d7ecc6e226596f646030a4109c2b0001ef0cc690e4863e450bf5d29e7a90344"
},
- {
- "name": "gorilla",
- "unicode": "1F98D",
+ "gorilla": {
+ "category": "nature",
+ "moji": "🦍",
+ "unicodeVersion": "9.0",
"digest": "4a564dc14f8ae5450d094f6410ec7f099a7f07dc5254b6395f44a35527bdb4b7"
},
- {
- "name": "grapes",
- "unicode": "1F347",
+ "grapes": {
+ "category": "food",
+ "moji": "🍇",
+ "unicodeVersion": "6.0",
"digest": "74d1a09ab411234a84d025a2e717e7ec5791bc02aad29853896d21c0f0283c50"
},
- {
- "name": "green_apple",
- "unicode": "1F34F",
+ "green_apple": {
+ "category": "food",
+ "moji": "🍏",
+ "unicodeVersion": "6.0",
"digest": "457490e9b2b20894f50768262d63f1021717079da104d4847076b3fa779e9a21"
},
- {
- "name": "green_book",
- "unicode": "1F4D7",
+ "green_book": {
+ "category": "objects",
+ "moji": "📗",
+ "unicodeVersion": "6.0",
"digest": "370f635b200efe5e4a9f17da58bd22500e258e61d17795cef375f19c9a45468f"
},
- {
- "name": "green_heart",
- "unicode": "1F49A",
+ "green_heart": {
+ "category": "symbols",
+ "moji": "💚",
+ "unicodeVersion": "6.0",
"digest": "f71e30416d9019873f2ed38ef375c48386424ff60b5a07b89b15dc9e0a3970f9"
},
- {
- "name": "grey_exclamation",
- "unicode": "2755",
+ "grey_exclamation": {
+ "category": "symbols",
+ "moji": "❕",
+ "unicodeVersion": "6.0",
"digest": "2fa1d356e12c17cc4025e43afb6c3070385f677102a35223302fda46c47a9b03"
},
- {
- "name": "grey_question",
- "unicode": "2754",
+ "grey_question": {
+ "category": "symbols",
+ "moji": "❔",
+ "unicodeVersion": "6.0",
"digest": "e1035bcbf0f66d238ef478ba451f5cf2c51627fbf101ed03bad3b2bf38db8aa2"
},
- {
- "name": "grimacing",
- "unicode": "1F62C",
+ "grimacing": {
+ "category": "people",
+ "moji": "😬",
+ "unicodeVersion": "6.1",
"digest": "2cedad13b8b2a1d4385ca6fa88a251eb7757a4c65dd6d362267864a01247846b"
},
- {
- "name": "grin",
- "unicode": "1F601",
+ "grin": {
+ "category": "people",
+ "moji": "😁",
+ "unicodeVersion": "6.0",
"digest": "634b2f37e32e57ed6edc7f371993a92e34137dd21ba393de5227cfbbe2422815"
},
- {
- "name": "grinning",
- "unicode": "1F600",
+ "grinning": {
+ "category": "people",
+ "moji": "😀",
+ "unicodeVersion": "6.1",
"digest": "cef76aa41771db9fd1d6bd9b4233c22c1fb1931494af54cab29e6347ed9b678d"
},
- {
- "name": "guardsman",
- "unicode": "1F482",
+ "guardsman": {
+ "category": "people",
+ "moji": "💂",
+ "unicodeVersion": "6.0",
"digest": "17bc7fad6b8c8dbd015bb709380d129f8b8e1e971062d15e6ab0b2e63e500564"
},
- {
- "name": "guardsman_tone1",
- "unicode": "1F482-1F3FB",
+ "guardsman_tone1": {
+ "category": "people",
+ "moji": "💂🏻",
+ "unicodeVersion": "8.0",
"digest": "c531ecb101bdf9ce1db18e1567882e6db927410237100b0a2492a1401860246e"
},
- {
- "name": "guardsman_tone2",
- "unicode": "1F482-1F3FC",
+ "guardsman_tone2": {
+ "category": "people",
+ "moji": "💂🏼",
+ "unicodeVersion": "8.0",
"digest": "602168c5204af0f1de8b4aa5863b192ef20c19d263999377aa5eb60f98311732"
},
- {
- "name": "guardsman_tone3",
- "unicode": "1F482-1F3FD",
+ "guardsman_tone3": {
+ "category": "people",
+ "moji": "💂🏽",
+ "unicodeVersion": "8.0",
"digest": "d0a85de46dd02c7bd6cb14bff0f22d2db9083d4b171a8806c83363b49f3dd9ef"
},
- {
- "name": "guardsman_tone4",
- "unicode": "1F482-1F3FE",
+ "guardsman_tone4": {
+ "category": "people",
+ "moji": "💂🏾",
+ "unicodeVersion": "8.0",
"digest": "1c9d4d72b6b50bdac8271613b6d2a38340ec2067bc344e8ee2a3c863fd5c23a1"
},
- {
- "name": "guardsman_tone5",
- "unicode": "1F482-1F3FF",
+ "guardsman_tone5": {
+ "category": "people",
+ "moji": "💂🏿",
+ "unicodeVersion": "8.0",
"digest": "9899a796d01842e495d716fbe737a16d85724f7d3e23f50807ec2bc70f057318"
},
- {
- "name": "guitar",
- "unicode": "1F3B8",
+ "guitar": {
+ "category": "activity",
+ "moji": "🎸",
+ "unicodeVersion": "6.0",
"digest": "a1027ceae4dd3ea270740587c9d373329e5677e375c9e00af6ae3275e0b67500"
},
- {
- "name": "gun",
- "unicode": "1F52B",
+ "gun": {
+ "category": "objects",
+ "moji": "🔫",
+ "unicodeVersion": "6.0",
"digest": "fc12b577df2283e7b336f23774f9cfe5b79f1d26ddd28a64a560519b28d94ca5"
},
- {
- "name": "haircut",
- "unicode": "1F487",
+ "haircut": {
+ "category": "people",
+ "moji": "💇",
+ "unicodeVersion": "6.0",
"digest": "b243a04f5ca889accd45e7abe095ac5caa92274ed95103f5966a36b415fff412"
},
- {
- "name": "haircut_tone1",
- "unicode": "1F487-1F3FB",
+ "haircut_tone1": {
+ "category": "people",
+ "moji": "💇🏻",
+ "unicodeVersion": "8.0",
"digest": "a58d0cff1427b80dfd7a9ea5267b4a181e9faaac6a51a0165db522f668b4cf91"
},
- {
- "name": "haircut_tone2",
- "unicode": "1F487-1F3FC",
+ "haircut_tone2": {
+ "category": "people",
+ "moji": "💇🏼",
+ "unicodeVersion": "8.0",
"digest": "675083ff40001405f8de99268477d50dd8594ff6ca40ddfd442dd42ad76e8216"
},
- {
- "name": "haircut_tone3",
- "unicode": "1F487-1F3FD",
+ "haircut_tone3": {
+ "category": "people",
+ "moji": "💇🏽",
+ "unicodeVersion": "8.0",
"digest": "70d7581e49c315a3771dd61a3713229886db32aaaeb3af078a69cc042f809150"
},
- {
- "name": "haircut_tone4",
- "unicode": "1F487-1F3FE",
+ "haircut_tone4": {
+ "category": "people",
+ "moji": "💇🏾",
+ "unicodeVersion": "8.0",
"digest": "ec5e3e909eb3bc375ef9cc0fe0e0f90b33f44f273ada91ccf415bbc43b8ffbfc"
},
- {
- "name": "haircut_tone5",
- "unicode": "1F487-1F3FF",
+ "haircut_tone5": {
+ "category": "people",
+ "moji": "💇🏿",
+ "unicodeVersion": "8.0",
"digest": "7c89739ee458546a808fded7f96d9354c47a76883ebb262d5f5abeafd021260e"
},
- {
- "name": "hamburger",
- "unicode": "1F354",
+ "hamburger": {
+ "category": "food",
+ "moji": "🍔",
+ "unicodeVersion": "6.0",
"digest": "48204235238bd89d3a69f319f65135102f3d6b181eec241d4d86b302bbffa9bf"
},
- {
- "name": "hammer",
- "unicode": "1F528",
+ "hammer": {
+ "category": "objects",
+ "moji": "🔨",
+ "unicodeVersion": "6.0",
"digest": "d0e7830539d935fcd82820c4e0c1d724f0756dfc83a51171fe0f4b36b69fac42"
},
- {
- "name": "hammer_pick",
- "unicode": "2692",
+ "hammer_pick": {
+ "category": "objects",
+ "moji": "⚒",
+ "unicodeVersion": "4.1",
"digest": "aa0445f43bca58d17afa7f3577632ca7775f5a28336385b3020b268b15b18142"
},
- {
- "name": "hammer_and_pick",
- "unicode": "2692",
- "digest": "aa0445f43bca58d17afa7f3577632ca7775f5a28336385b3020b268b15b18142"
- },
- {
- "name": "hamster",
- "unicode": "1F439",
+ "hamster": {
+ "category": "nature",
+ "moji": "🐹",
+ "unicodeVersion": "6.0",
"digest": "a7e7582e8b1bccd5b7df27ccb05e353a3f0e39bdeb40877732706b9d74a70de1"
},
- {
- "name": "hand_splayed",
- "unicode": "1F590",
- "digest": "c51a30cb7e575d29ffed16780a6c95ae3f300b8ac523012f4a6e116d68c1fd15"
- },
- {
- "name": "raised_hand_with_fingers_splayed",
- "unicode": "1F590",
+ "hand_splayed": {
+ "category": "people",
+ "moji": "🖐",
+ "unicodeVersion": "7.0",
"digest": "c51a30cb7e575d29ffed16780a6c95ae3f300b8ac523012f4a6e116d68c1fd15"
},
- {
- "name": "hand_splayed_tone1",
- "unicode": "1F590-1F3FB",
+ "hand_splayed_tone1": {
+ "category": "people",
+ "moji": "🖐🏻",
+ "unicodeVersion": "8.0",
"digest": "c31fb44a982ed8808e1c311ec1b0b9c5afcb47f16bb1fc731dc483adf8f0d049"
},
- {
- "name": "raised_hand_with_fingers_splayed_tone1",
- "unicode": "1F590-1F3FB",
- "digest": "c31fb44a982ed8808e1c311ec1b0b9c5afcb47f16bb1fc731dc483adf8f0d049"
- },
- {
- "name": "hand_splayed_tone2",
- "unicode": "1F590-1F3FC",
- "digest": "56a236881184e9ffad54613fa08a67368c432af738f5254fb1cd87b20368acdf"
- },
- {
- "name": "raised_hand_with_fingers_splayed_tone2",
- "unicode": "1F590-1F3FC",
+ "hand_splayed_tone2": {
+ "category": "people",
+ "moji": "🖐🏼",
+ "unicodeVersion": "8.0",
"digest": "56a236881184e9ffad54613fa08a67368c432af738f5254fb1cd87b20368acdf"
},
- {
- "name": "hand_splayed_tone3",
- "unicode": "1F590-1F3FD",
+ "hand_splayed_tone3": {
+ "category": "people",
+ "moji": "🖐🏽",
+ "unicodeVersion": "8.0",
"digest": "9242ca97dfd2bbc1947228f6535029afb31f8feb72c14ff4b7f2deea30217425"
},
- {
- "name": "raised_hand_with_fingers_splayed_tone3",
- "unicode": "1F590-1F3FD",
- "digest": "9242ca97dfd2bbc1947228f6535029afb31f8feb72c14ff4b7f2deea30217425"
- },
- {
- "name": "hand_splayed_tone4",
- "unicode": "1F590-1F3FE",
- "digest": "43348d9fd3d43b3c45cebaf663bf181bcad3b6df841a5aeed838180db2cdd481"
- },
- {
- "name": "raised_hand_with_fingers_splayed_tone4",
- "unicode": "1F590-1F3FE",
+ "hand_splayed_tone4": {
+ "category": "people",
+ "moji": "🖐🏾",
+ "unicodeVersion": "8.0",
"digest": "43348d9fd3d43b3c45cebaf663bf181bcad3b6df841a5aeed838180db2cdd481"
},
- {
- "name": "hand_splayed_tone5",
- "unicode": "1F590-1F3FF",
+ "hand_splayed_tone5": {
+ "category": "people",
+ "moji": "🖐🏿",
+ "unicodeVersion": "8.0",
"digest": "4b3a0aba7829772fec09f26d6facc19a2f822d2998015297b18b5cab85190ee2"
},
- {
- "name": "raised_hand_with_fingers_splayed_tone5",
- "unicode": "1F590-1F3FF",
- "digest": "4b3a0aba7829772fec09f26d6facc19a2f822d2998015297b18b5cab85190ee2"
- },
- {
- "name": "handbag",
- "unicode": "1F45C",
+ "handbag": {
+ "category": "people",
+ "moji": "👜",
+ "unicodeVersion": "6.0",
"digest": "45410a3eed0c2e3f68748d7649fa9e33a90f4e80d5291206bdd0b40380c6da45"
},
- {
- "name": "handball",
- "unicode": "1F93E",
+ "handball": {
+ "category": "activity",
+ "moji": "🤾",
+ "unicodeVersion": "9.0",
"digest": "94ceb28024eb3259d8b137cafd7438773e717fbc04f5da810f85e43ca0fa9e00"
},
- {
- "name": "handball_tone1",
- "unicode": "1F93E-1F3FB",
+ "handball_tone1": {
+ "category": "activity",
+ "moji": "🤾🏻",
+ "unicodeVersion": "9.0",
"digest": "8bec4de0d05c80e335e44d65598d186ca92696977353c9fd9c2a5efa122cb842"
},
- {
- "name": "handball_tone2",
- "unicode": "1F93E-1F3FC",
+ "handball_tone2": {
+ "category": "activity",
+ "moji": "🤾🏼",
+ "unicodeVersion": "9.0",
"digest": "2ff4131e1e2f089b315d8e176c9348877c26c2bd03706fb75d41bc61bc99bf93"
},
- {
- "name": "handball_tone3",
- "unicode": "1F93E-1F3FD",
+ "handball_tone3": {
+ "category": "activity",
+ "moji": "🤾🏽",
+ "unicodeVersion": "9.0",
"digest": "224a71f94dd37d3729325d11412334667a81422e21f6d7c008730ff350f51a80"
},
- {
- "name": "handball_tone4",
- "unicode": "1F93E-1F3FE",
+ "handball_tone4": {
+ "category": "activity",
+ "moji": "🤾🏾",
+ "unicodeVersion": "9.0",
"digest": "a5f7a9db790565981bad2d0d9e09554c8c509a8179b4705a418300d58a7894b4"
},
- {
- "name": "handball_tone5",
- "unicode": "1F93E-1F3FF",
+ "handball_tone5": {
+ "category": "activity",
+ "moji": "🤾🏿",
+ "unicodeVersion": "9.0",
"digest": "00404572d4683f2e8e8a494aa733e96fbec1723634d0a8cb8d75f2829a789d27"
},
- {
- "name": "handshake",
- "unicode": "1F91D",
+ "handshake": {
+ "category": "people",
+ "moji": "🤝",
+ "unicodeVersion": "9.0",
"digest": "cb4b08b70560908f96bda0aecd2f4c966bea180f9b7200e4c81d342dc8d36087"
},
- {
- "name": "shaking_hands",
- "unicode": "1F91D",
- "digest": "cb4b08b70560908f96bda0aecd2f4c966bea180f9b7200e4c81d342dc8d36087"
- },
- {
- "name": "handshake_tone1",
- "unicode": "1F91D-1F3FB",
+ "handshake_tone1": {
+ "category": "people",
+ "moji": "🤝🏻",
+ "unicodeVersion": "9.0",
"digest": "40470e224683ba375ed8698c0cbd560556be5a8898237ddf504377a3a7e89ff0"
},
- {
- "name": "shaking_hands_tone1",
- "unicode": "1F91D-1F3FB",
- "digest": "40470e224683ba375ed8698c0cbd560556be5a8898237ddf504377a3a7e89ff0"
- },
- {
- "name": "handshake_tone2",
- "unicode": "1F91D-1F3FC",
- "digest": "77ed378243bf682f1f4f1a8caeabcbedf772f54631cc40ea46c099e46a499b18"
- },
- {
- "name": "shaking_hands_tone2",
- "unicode": "1F91D-1F3FC",
+ "handshake_tone2": {
+ "category": "people",
+ "moji": "🤝🏼",
+ "unicodeVersion": "9.0",
"digest": "77ed378243bf682f1f4f1a8caeabcbedf772f54631cc40ea46c099e46a499b18"
},
- {
- "name": "handshake_tone3",
- "unicode": "1F91D-1F3FD",
+ "handshake_tone3": {
+ "category": "people",
+ "moji": "🤝🏽",
+ "unicodeVersion": "9.0",
"digest": "81b95050f0878b617f5d2640e34031c26a0072e46ca5a688eb4356e48bc74c92"
},
- {
- "name": "shaking_hands_tone3",
- "unicode": "1F91D-1F3FD",
- "digest": "81b95050f0878b617f5d2640e34031c26a0072e46ca5a688eb4356e48bc74c92"
- },
- {
- "name": "handshake_tone4",
- "unicode": "1F91D-1F3FE",
- "digest": "74919a6f026fbbd0ccdbdbd4288d1b2ef3bda8930e9142c07736db4a7f3ef345"
- },
- {
- "name": "shaking_hands_tone4",
- "unicode": "1F91D-1F3FE",
+ "handshake_tone4": {
+ "category": "people",
+ "moji": "🤝🏾",
+ "unicodeVersion": "9.0",
"digest": "74919a6f026fbbd0ccdbdbd4288d1b2ef3bda8930e9142c07736db4a7f3ef345"
},
- {
- "name": "handshake_tone5",
- "unicode": "1F91D-1F3FF",
- "digest": "a30d662bfad0074ca7e32cf6f7229b643b636c4beaec496777eb7e1d5b6fc470"
- },
- {
- "name": "shaking_hands_tone5",
- "unicode": "1F91D-1F3FF",
+ "handshake_tone5": {
+ "category": "people",
+ "moji": "🤝🏿",
+ "unicodeVersion": "9.0",
"digest": "a30d662bfad0074ca7e32cf6f7229b643b636c4beaec496777eb7e1d5b6fc470"
},
- {
- "name": "hash",
- "unicode": "0023-20E3",
+ "hash": {
+ "category": "symbols",
+ "moji": "#⃣",
+ "unicodeVersion": "3.0",
"digest": "01c8b577953010bff0c20f797c2c96ab5d98d4e6ac179c4895a78f34ea904655"
},
- {
- "name": "hatched_chick",
- "unicode": "1F425",
+ "hatched_chick": {
+ "category": "nature",
+ "moji": "🐥",
+ "unicodeVersion": "6.0",
"digest": "006571b9e9e839ec9fcb1a911b935c8ca71eb8bcdce9775bee6a2a4c7c927277"
},
- {
- "name": "hatching_chick",
- "unicode": "1F423",
+ "hatching_chick": {
+ "category": "nature",
+ "moji": "🐣",
+ "unicodeVersion": "6.0",
"digest": "fd7f69fa186407f80de59dec5116e318325a5743ee0e8bba1db541f1e57e7f74"
},
- {
- "name": "head_bandage",
- "unicode": "1F915",
- "digest": "d09019a73e203b38cc43729a96163147de88e09eab8adb073888e55366854c72"
- },
- {
- "name": "face_with_head_bandage",
- "unicode": "1F915",
+ "head_bandage": {
+ "category": "people",
+ "moji": "🤕",
+ "unicodeVersion": "8.0",
"digest": "d09019a73e203b38cc43729a96163147de88e09eab8adb073888e55366854c72"
},
- {
- "name": "headphones",
- "unicode": "1F3A7",
+ "headphones": {
+ "category": "activity",
+ "moji": "🎧",
+ "unicodeVersion": "6.0",
"digest": "34f9d5598158d5d6f978a5ea5c5aa9948bb2990625565a3afad7710f864fbe2f"
},
- {
- "name": "hear_no_evil",
- "unicode": "1F649",
+ "hear_no_evil": {
+ "category": "nature",
+ "moji": "🙉",
+ "unicodeVersion": "6.0",
"digest": "53b030b6d6f4ed1a734fa7d48b46f42eb1b2b01653202c1838b742082f08c4bf"
},
- {
- "name": "heart",
- "unicode": "2764",
+ "heart": {
+ "category": "symbols",
+ "moji": "❤",
+ "unicodeVersion": "1.1",
"digest": "92be652ec3e50c6e7393440b5d52b88a367f98a28dffe12660095ed3253aa6c0"
},
- {
- "name": "heart_decoration",
- "unicode": "1F49F",
+ "heart_decoration": {
+ "category": "symbols",
+ "moji": "💟",
+ "unicodeVersion": "6.0",
"digest": "6ec5bbf3aa75c6f43eb3dc05e9204366936e8b6b4219310bacdc2fc45f51e245"
},
- {
- "name": "heart_exclamation",
- "unicode": "2763",
+ "heart_exclamation": {
+ "category": "symbols",
+ "moji": "❣",
+ "unicodeVersion": "1.1",
"digest": "5985ea4d82232a2a07052a59db268aed9ac943895d0c82f637595bb5386329a6"
},
- {
- "name": "heavy_heart_exclamation_mark_ornament",
- "unicode": "2763",
- "digest": "5985ea4d82232a2a07052a59db268aed9ac943895d0c82f637595bb5386329a6"
- },
- {
- "name": "heart_eyes",
- "unicode": "1F60D",
+ "heart_eyes": {
+ "category": "people",
+ "moji": "😍",
+ "unicodeVersion": "6.0",
"digest": "0eff616517a6252ec89d47d9b4ad85589bcf2bdc7f490578934350acb84b2fcc"
},
- {
- "name": "heart_eyes_cat",
- "unicode": "1F63B",
+ "heart_eyes_cat": {
+ "category": "people",
+ "moji": "😻",
+ "unicodeVersion": "6.0",
"digest": "8a1f28b97d661ca4cff5ee13889ca61b5fa745ccb590e80832b7d7701df101d6"
},
- {
- "name": "heartbeat",
- "unicode": "1F493",
+ "heartbeat": {
+ "category": "symbols",
+ "moji": "💓",
+ "unicodeVersion": "6.0",
"digest": "c9ec024943439d476df6f5ec3a6b30508365a7af3427671a80de3ef2f4f95ffe"
},
- {
- "name": "heartpulse",
- "unicode": "1F497",
+ "heartpulse": {
+ "category": "symbols",
+ "moji": "💗",
+ "unicodeVersion": "6.0",
"digest": "281d8aebfea37db5b7fe82d9115be167006881fe29ab64a5b09ac92ac27a2309"
},
- {
- "name": "hearts",
- "unicode": "2665",
+ "hearts": {
+ "category": "symbols",
+ "moji": "♥",
+ "unicodeVersion": "1.1",
"digest": "271429d12c40be921897005b7bdd08f9518960af1e1e6f56bb0060f1f183651e"
},
- {
- "name": "heavy_check_mark",
- "unicode": "2714",
+ "heavy_check_mark": {
+ "category": "symbols",
+ "moji": "✔",
+ "unicodeVersion": "1.1",
"digest": "e347728e1290eb9e7b0742d628e2fd124fc049e0774f8a6ddf8e5286e7318718"
},
- {
- "name": "heavy_division_sign",
- "unicode": "2797",
+ "heavy_division_sign": {
+ "category": "symbols",
+ "moji": "➗",
+ "unicodeVersion": "6.0",
"digest": "c1e8c40f0788f140b1c5fcb81ed9b5ce1bcfa5988bb8140ed2808e9cb7e0d651"
},
- {
- "name": "heavy_dollar_sign",
- "unicode": "1F4B2",
+ "heavy_dollar_sign": {
+ "category": "symbols",
+ "moji": "💲",
+ "unicodeVersion": "6.0",
"digest": "7cdeef38348654b93d566e01a48973281cb404a63d0b75b3bad51032887f3f55"
},
- {
- "name": "heavy_minus_sign",
- "unicode": "2796",
+ "heavy_minus_sign": {
+ "category": "symbols",
+ "moji": "➖",
+ "unicodeVersion": "6.0",
"digest": "e5335cc6b22abdce49a6127c34269b65a4a6643ddd3253d9baac425089143e7d"
},
- {
- "name": "heavy_multiplication_x",
- "unicode": "2716",
+ "heavy_multiplication_x": {
+ "category": "symbols",
+ "moji": "✖",
+ "unicodeVersion": "1.1",
"digest": "64bbe9e9716a922e405d2f6d3b6d803863a53fac80ff8cd775899971046cb1ca"
},
- {
- "name": "heavy_plus_sign",
- "unicode": "2795",
+ "heavy_plus_sign": {
+ "category": "symbols",
+ "moji": "➕",
+ "unicodeVersion": "6.0",
"digest": "d0d8ade2020ceb252205180b85c66e665856e6cb505518d395b9913b0b24b746"
},
- {
- "name": "helicopter",
- "unicode": "1F681",
+ "helicopter": {
+ "category": "travel",
+ "moji": "🚁",
+ "unicodeVersion": "6.0",
"digest": "4bd6fd13650fbe3a19cfffeffe6c21b1cda74bd6af64c5dc5999185e35444bc3"
},
- {
- "name": "helmet_with_cross",
- "unicode": "26D1",
- "digest": "8286107391d44b9cd7fce5dc83bfdebbcdcf5a8214c46a8990732ec40263ed77"
- },
- {
- "name": "helmet_with_white_cross",
- "unicode": "26D1",
+ "helmet_with_cross": {
+ "category": "people",
+ "moji": "⛑",
+ "unicodeVersion": "5.2",
"digest": "8286107391d44b9cd7fce5dc83bfdebbcdcf5a8214c46a8990732ec40263ed77"
},
- {
- "name": "herb",
- "unicode": "1F33F",
+ "herb": {
+ "category": "nature",
+ "moji": "🌿",
+ "unicodeVersion": "6.0",
"digest": "9fe8ed65515ede59d0926dcf98f14e2498785e1965610aa0dd56eca9b4bedad9"
},
- {
- "name": "hibiscus",
- "unicode": "1F33A",
+ "hibiscus": {
+ "category": "nature",
+ "moji": "🌺",
+ "unicodeVersion": "6.0",
"digest": "c442e8eacbd8727bd154bd39692a9a2a03ea2f674b9670ad8361f78a038afe49"
},
- {
- "name": "high_brightness",
- "unicode": "1F506",
+ "high_brightness": {
+ "category": "symbols",
+ "moji": "🔆",
+ "unicodeVersion": "6.0",
"digest": "35ced42426dcfd5214c2c6c577dce84bb708156433945e6b6adaff7ea530cc57"
},
- {
- "name": "high_heel",
- "unicode": "1F460",
+ "high_heel": {
+ "category": "people",
+ "moji": "👠",
+ "unicodeVersion": "6.0",
"digest": "1e7c7aba50eb1d02cf1d9aa372caca741a6005cf47f68dfa75b7310c3cb18f05"
},
- {
- "name": "hockey",
- "unicode": "1F3D2",
+ "hockey": {
+ "category": "activity",
+ "moji": "🏒",
+ "unicodeVersion": "8.0",
"digest": "2d00fb17baa617e799db8e9b1771cc365bb4545c7633df0123e66e1a6e2ed25d"
},
- {
- "name": "hole",
- "unicode": "1F573",
+ "hole": {
+ "category": "objects",
+ "moji": "🕳",
+ "unicodeVersion": "7.0",
"digest": "8b5539f6f24f09d5d68ffd56be5aa2a8a2f753a8dfbf64892fb02c8f2703e920"
},
- {
- "name": "homes",
- "unicode": "1F3D8",
- "digest": "cd512f2b4ce747325607d47da48e083dbfe38a44b85b2522bc372bd105afd25f"
- },
- {
- "name": "house_buildings",
- "unicode": "1F3D8",
+ "homes": {
+ "category": "travel",
+ "moji": "🏘",
+ "unicodeVersion": "7.0",
"digest": "cd512f2b4ce747325607d47da48e083dbfe38a44b85b2522bc372bd105afd25f"
},
- {
- "name": "honey_pot",
- "unicode": "1F36F",
+ "honey_pot": {
+ "category": "food",
+ "moji": "🍯",
+ "unicodeVersion": "6.0",
"digest": "f6eec8c32fbd1b461446dc6c5d5031c43e6ee9685dc9b1ea1b839114e48c4eee"
},
- {
- "name": "horse",
- "unicode": "1F434",
+ "horse": {
+ "category": "nature",
+ "moji": "🐴",
+ "unicodeVersion": "6.0",
"digest": "e377649a9549835770a2a721a92570f699255f88efa646029638eb8ec5f10e3d"
},
- {
- "name": "horse_racing",
- "unicode": "1F3C7",
+ "horse_racing": {
+ "category": "activity",
+ "moji": "🏇",
+ "unicodeVersion": "6.0",
"digest": "3b98e94e9c028ad85b9a750cc61db5ee3ac23cf5ad9243ea3e996b1f772bad54"
},
- {
- "name": "horse_racing_tone1",
- "unicode": "1F3C7-1F3FB",
+ "horse_racing_tone1": {
+ "category": "activity",
+ "moji": "🏇🏻",
+ "unicodeVersion": "8.0",
"digest": "382d8e4502ed34fc1bbf1779ce483bc2e22b83f89c91746c11a5d7aea656d446"
},
- {
- "name": "horse_racing_tone2",
- "unicode": "1F3C7-1F3FC",
+ "horse_racing_tone2": {
+ "category": "activity",
+ "moji": "🏇🏼",
+ "unicodeVersion": "8.0",
"digest": "198df9973b492ea63e5cfc210dd9591750ccce04a6380adc1dc5b4cb0462a8cd"
},
- {
- "name": "horse_racing_tone3",
- "unicode": "1F3C7-1F3FD",
+ "horse_racing_tone3": {
+ "category": "activity",
+ "moji": "🏇🏽",
+ "unicodeVersion": "8.0",
"digest": "a67f95fc92c366750ebad3c4db92982893d67a5ed78163c8cc809ac40d2ab9a3"
},
- {
- "name": "horse_racing_tone4",
- "unicode": "1F3C7-1F3FE",
+ "horse_racing_tone4": {
+ "category": "activity",
+ "moji": "🏇🏾",
+ "unicodeVersion": "8.0",
"digest": "986b1706c4a3395b58a8ae3b7609ffdd4424dfefcbf26c88c8085f4f6379734e"
},
- {
- "name": "horse_racing_tone5",
- "unicode": "1F3C7-1F3FF",
+ "horse_racing_tone5": {
+ "category": "activity",
+ "moji": "🏇🏿",
+ "unicodeVersion": "8.0",
"digest": "66656b5e3d0f43f16f983f9db6214b07aac73b143eeff6475782f98aa5b9ba53"
},
- {
- "name": "hospital",
- "unicode": "1F3E5",
+ "hospital": {
+ "category": "travel",
+ "moji": "🏥",
+ "unicodeVersion": "6.0",
"digest": "034573e76df444f5b0eb7aff3a4103e4b49a1813869155ab3ae29a6fc0c6c8a2"
},
- {
- "name": "hot_pepper",
- "unicode": "1F336",
+ "hot_pepper": {
+ "category": "food",
+ "moji": "🌶",
+ "unicodeVersion": "7.0",
"digest": "0b05777d42698196a10db17d04030175b1dfa772d06288f71d666d5f8d3fddbc"
},
- {
- "name": "hotdog",
- "unicode": "1F32D",
+ "hotdog": {
+ "category": "food",
+ "moji": "🌭",
+ "unicodeVersion": "8.0",
"digest": "7a25bbd1a7531fd34a22c654c0931d9e74bea2bbe7baa9f9cbd88f43baa79fb5"
},
- {
- "name": "hot_dog",
- "unicode": "1F32D",
- "digest": "7a25bbd1a7531fd34a22c654c0931d9e74bea2bbe7baa9f9cbd88f43baa79fb5"
- },
- {
- "name": "hotel",
- "unicode": "1F3E8",
+ "hotel": {
+ "category": "travel",
+ "moji": "🏨",
+ "unicodeVersion": "6.0",
"digest": "2d78e0ad4cfb0caad778c7de49fefd6e8356afe902a43e3f1c40bceb6b0be422"
},
- {
- "name": "hotsprings",
- "unicode": "2668",
+ "hotsprings": {
+ "category": "symbols",
+ "moji": "♨",
+ "unicodeVersion": "1.1",
"digest": "4c10c3a974b44693e8cbe91365c8b8d7f14f62db234cc516b6e54c08a6bacaed"
},
- {
- "name": "hourglass",
- "unicode": "231B",
+ "hourglass": {
+ "category": "objects",
+ "moji": "⌛",
+ "unicodeVersion": "1.1",
"digest": "f0bae8392aaf6f75a83f5d8914936b8650665b24ba1b232fa546b71545dd9acd"
},
- {
- "name": "hourglass_flowing_sand",
- "unicode": "23F3",
+ "hourglass_flowing_sand": {
+ "category": "objects",
+ "moji": "⏳",
+ "unicodeVersion": "6.0",
"digest": "2d077729f40fc04007a933e97356bd511cbd8be76b8c55962ca3fa0d8b828e23"
},
- {
- "name": "house",
- "unicode": "1F3E0",
+ "house": {
+ "category": "travel",
+ "moji": "🏠",
+ "unicodeVersion": "6.0",
"digest": "b4ac25979fbe161ada0d2a75769aa7552d2371d37d78cddba4ffdc7f076d3279"
},
- {
- "name": "house_abandoned",
- "unicode": "1F3DA",
- "digest": "6e1a58533fbfe88a0eb03668c9f17c5c654a6cc7734ed798d4a885400f823610"
- },
- {
- "name": "derelict_house_building",
- "unicode": "1F3DA",
+ "house_abandoned": {
+ "category": "travel",
+ "moji": "🏚",
+ "unicodeVersion": "7.0",
"digest": "6e1a58533fbfe88a0eb03668c9f17c5c654a6cc7734ed798d4a885400f823610"
},
- {
- "name": "house_with_garden",
- "unicode": "1F3E1",
+ "house_with_garden": {
+ "category": "travel",
+ "moji": "🏡",
+ "unicodeVersion": "6.0",
"digest": "817463f23ec0a849393ba75c333e822b4d253cd4db998c127e90d1b924f35d20"
},
- {
- "name": "hugging",
- "unicode": "1F917",
+ "hugging": {
+ "category": "people",
+ "moji": "🤗",
+ "unicodeVersion": "8.0",
"digest": "69810a98b1247e1f1e496aa757e428189ef5cc086764fabd8189cf1eef82234f"
},
- {
- "name": "hugging_face",
- "unicode": "1F917",
- "digest": "69810a98b1247e1f1e496aa757e428189ef5cc086764fabd8189cf1eef82234f"
- },
- {
- "name": "hushed",
- "unicode": "1F62F",
+ "hushed": {
+ "category": "people",
+ "moji": "😯",
+ "unicodeVersion": "6.1",
"digest": "22586107f7399eff64538a52929dade152633aa268fc5ec4e6fe1c0e00a7bd89"
},
- {
- "name": "ice_cream",
- "unicode": "1F368",
+ "ice_cream": {
+ "category": "food",
+ "moji": "🍨",
+ "unicodeVersion": "6.0",
"digest": "d1a8e685f2ecf83dead28733859e369d6ce120a2669cdab97dc4423547d472ac"
},
- {
- "name": "ice_skate",
- "unicode": "26F8",
+ "ice_skate": {
+ "category": "activity",
+ "moji": "⛸",
+ "unicodeVersion": "5.2",
"digest": "41ef65c143bc068868fa64080ffd447d91aa3fe2a39e69ecaa97022820af4dcd"
},
- {
- "name": "icecream",
- "unicode": "1F366",
+ "icecream": {
+ "category": "food",
+ "moji": "🍦",
+ "unicodeVersion": "6.0",
"digest": "22cfe17b80cbd2a0377ee90da45bd40d33533c914b2639d363fbb1f00714e194"
},
- {
- "name": "id",
- "unicode": "1F194",
+ "id": {
+ "category": "symbols",
+ "moji": "🆔",
+ "unicodeVersion": "6.0",
"digest": "bcf0922e083821d3be7951893084ea0d72a0110ef0b20d11dfec24dd70633893"
},
- {
- "name": "ideograph_advantage",
- "unicode": "1F250",
+ "ideograph_advantage": {
+ "category": "symbols",
+ "moji": "🉐",
+ "unicodeVersion": "6.0",
"digest": "0b6bf59f63fda1afa92d652814a778a056c3f4abdd9cf3f6796068bd71783051"
},
- {
- "name": "imp",
- "unicode": "1F47F",
+ "imp": {
+ "category": "people",
+ "moji": "👿",
+ "unicodeVersion": "6.0",
"digest": "52598cf2441988f875ccb4e479637baefc679e3ca64e9a6400e56488b0fde811"
},
- {
- "name": "inbox_tray",
- "unicode": "1F4E5",
+ "inbox_tray": {
+ "category": "objects",
+ "moji": "📥",
+ "unicodeVersion": "6.0",
"digest": "d5d9497022b5318fcfbfdfcd56df9c65dd8f4a4cb5e6283ca260836df57da301"
},
- {
- "name": "incoming_envelope",
- "unicode": "1F4E8",
+ "incoming_envelope": {
+ "category": "objects",
+ "moji": "📨",
+ "unicodeVersion": "6.0",
"digest": "310b7bdcca93452fe10c72c03d0aafa12b98e5d3408896d275d06d3693812c7a"
},
- {
- "name": "information_desk_person",
- "unicode": "1F481",
+ "information_desk_person": {
+ "category": "people",
+ "moji": "💁",
+ "unicodeVersion": "6.0",
"digest": "9f12a4a58a650e8e1d3836ef857003c3ccd42ad4203a2479eb95100bf6559064"
},
- {
- "name": "information_desk_person_tone1",
- "unicode": "1F481-1F3FB",
+ "information_desk_person_tone1": {
+ "category": "people",
+ "moji": "💁🏻",
+ "unicodeVersion": "8.0",
"digest": "6674f2e059eff7cfd7fd6abc800da37c4f1087feb4ff26c9e4e31aa29fdf9921"
},
- {
- "name": "information_desk_person_tone2",
- "unicode": "1F481-1F3FC",
+ "information_desk_person_tone2": {
+ "category": "people",
+ "moji": "💁🏼",
+ "unicodeVersion": "8.0",
"digest": "9983412ecd130b7e9cfb078167016c06fd043b6f9f3c26d21733ca3f059fd109"
},
- {
- "name": "information_desk_person_tone3",
- "unicode": "1F481-1F3FD",
+ "information_desk_person_tone3": {
+ "category": "people",
+ "moji": "💁🏽",
+ "unicodeVersion": "8.0",
"digest": "d8907bf47af5722127afca8fc0da587eab33044a6c60a94890983deb8d6f7a66"
},
- {
- "name": "information_desk_person_tone4",
- "unicode": "1F481-1F3FE",
+ "information_desk_person_tone4": {
+ "category": "people",
+ "moji": "💁🏾",
+ "unicodeVersion": "8.0",
"digest": "3be086d4edfe9ca8e4a364b4e8d09b81b5b594b5eeb9ffdf6370179fb3118658"
},
- {
- "name": "information_desk_person_tone5",
- "unicode": "1F481-1F3FF",
+ "information_desk_person_tone5": {
+ "category": "people",
+ "moji": "💁🏿",
+ "unicodeVersion": "8.0",
"digest": "2fde4e98dd11c5c29c89cad7cbb7bd2d5077dfad07913b20e01955b2d0dfad40"
},
- {
- "name": "information_source",
- "unicode": "2139",
+ "information_source": {
+ "category": "symbols",
+ "moji": "ℹ",
+ "unicodeVersion": "3.0",
"digest": "b6bf3cce86d42c2e3c46470baab4af01e900b8ae337b605c3da07c3eba671269"
},
- {
- "name": "innocent",
- "unicode": "1F607",
+ "innocent": {
+ "category": "people",
+ "moji": "😇",
+ "unicodeVersion": "6.0",
"digest": "20f8d856bc3e46f4b1173cea05d4577e1c61f06b2daba46e57db90f4066bb428"
},
- {
- "name": "interrobang",
- "unicode": "2049",
+ "interrobang": {
+ "category": "symbols",
+ "moji": "⁉",
+ "unicodeVersion": "3.0",
"digest": "92a2d5b4c0bd6714e402f6f12fe19774cb41d081b5e9c23c415ce794224d8117"
},
- {
- "name": "iphone",
- "unicode": "1F4F1",
+ "iphone": {
+ "category": "objects",
+ "moji": "📱",
+ "unicodeVersion": "6.0",
"digest": "1ebc54215713cd4bf1c1e50770999f2512bb4fea29e37d0bb3a8aa2460ff875d"
},
- {
- "name": "island",
- "unicode": "1F3DD",
- "digest": "7f9eb5c0cd865762f7a0f187e09c1be442de7010e7c2e113d56aae998597c90d"
- },
- {
- "name": "desert_island",
- "unicode": "1F3DD",
+ "island": {
+ "category": "travel",
+ "moji": "🏝",
+ "unicodeVersion": "7.0",
"digest": "7f9eb5c0cd865762f7a0f187e09c1be442de7010e7c2e113d56aae998597c90d"
},
- {
- "name": "izakaya_lantern",
- "unicode": "1F3EE",
+ "izakaya_lantern": {
+ "category": "objects",
+ "moji": "🏮",
+ "unicodeVersion": "6.0",
"digest": "fbdc290e666d43d0776a73b955c26df4518692b35e72742e073705fc4ca2ae88"
},
- {
- "name": "jack_o_lantern",
- "unicode": "1F383",
+ "jack_o_lantern": {
+ "category": "nature",
+ "moji": "🎃",
+ "unicodeVersion": "6.0",
"digest": "78d666c2e80f64bfb6796f53e5ba4960a83ec36192110e8661031bee2b5e370a"
},
- {
- "name": "japan",
- "unicode": "1F5FE",
+ "japan": {
+ "category": "travel",
+ "moji": "🗾",
+ "unicodeVersion": "6.0",
"digest": "e7d9d6ebf9047fdd3c52e074ba259659c6d8e51a6abae3cdb8d6cf6dbf9a93fe"
},
- {
- "name": "japanese_castle",
- "unicode": "1F3EF",
+ "japanese_castle": {
+ "category": "travel",
+ "moji": "🏯",
+ "unicodeVersion": "6.0",
"digest": "938ae132c403330288223b88d28c19a47224d4f254fbc2366ecef73d9633112c"
},
- {
- "name": "japanese_goblin",
- "unicode": "1F47A",
+ "japanese_goblin": {
+ "category": "people",
+ "moji": "👺",
+ "unicodeVersion": "6.0",
"digest": "63d4bcf58b9d0c29612994432aad2ae35819fdd2890674e60a2f1d51601b742e"
},
- {
- "name": "japanese_ogre",
- "unicode": "1F479",
+ "japanese_ogre": {
+ "category": "people",
+ "moji": "👹",
+ "unicodeVersion": "6.0",
"digest": "434ceedd102e7dcbc07e086811673dd63659ddf8c3ec4d029a3d759a0abfcbdb"
},
- {
- "name": "jeans",
- "unicode": "1F456",
+ "jeans": {
+ "category": "people",
+ "moji": "👖",
+ "unicodeVersion": "6.0",
"digest": "f986ad32e419cca81c995f8371f0189d1490172a97ebbeac60054a1af08949c5"
},
- {
- "name": "joy",
- "unicode": "1F602",
+ "joy": {
+ "category": "people",
+ "moji": "😂",
+ "unicodeVersion": "6.0",
"digest": "75d7a05043523d290c46d3b313b19ed3c95271f1110bcf234cf13d4273625b08"
},
- {
- "name": "joy_cat",
- "unicode": "1F639",
+ "joy_cat": {
+ "category": "people",
+ "moji": "😹",
+ "unicodeVersion": "6.0",
"digest": "a65c999604147e5e20170fcb14f80a1ff0a633f991492e1f790b2ad4caec7b7e"
},
- {
- "name": "joystick",
- "unicode": "1F579",
+ "joystick": {
+ "category": "objects",
+ "moji": "🕹",
+ "unicodeVersion": "7.0",
"digest": "671ee588f397a96f27056a67e6a06d6e8d22c2109ec57b2859badb5fec9cf8dd"
},
- {
- "name": "juggling",
- "unicode": "1F939",
+ "juggling": {
+ "category": "activity",
+ "moji": "🤹",
+ "unicodeVersion": "9.0",
"digest": "1f5dafa78de8b37f3df88fdf3084d2380666bd74ab2f449754d8724f6f8dbfa5"
},
- {
- "name": "juggler",
- "unicode": "1F939",
- "digest": "1f5dafa78de8b37f3df88fdf3084d2380666bd74ab2f449754d8724f6f8dbfa5"
- },
- {
- "name": "juggling_tone1",
- "unicode": "1F939-1F3FB",
- "digest": "b0b4d020148c896be69c28b08e3c486f6db270d138c7ccf4be362b29eb99878d"
- },
- {
- "name": "juggler_tone1",
- "unicode": "1F939-1F3FB",
+ "juggling_tone1": {
+ "category": "activity",
+ "moji": "🤹🏻",
+ "unicodeVersion": "9.0",
"digest": "b0b4d020148c896be69c28b08e3c486f6db270d138c7ccf4be362b29eb99878d"
},
- {
- "name": "juggling_tone2",
- "unicode": "1F939-1F3FC",
+ "juggling_tone2": {
+ "category": "activity",
+ "moji": "🤹🏼",
+ "unicodeVersion": "9.0",
"digest": "cfe0c1649b2fdca03673e0e64f3a7d06d4bd49b8954c769aeb7eb88b70ec99f4"
},
- {
- "name": "juggler_tone2",
- "unicode": "1F939-1F3FC",
- "digest": "cfe0c1649b2fdca03673e0e64f3a7d06d4bd49b8954c769aeb7eb88b70ec99f4"
- },
- {
- "name": "juggling_tone3",
- "unicode": "1F939-1F3FD",
- "digest": "7f87022722008bb265abe245e8157dc7a61944f5da62b3cf86f26ee1b3bdef63"
- },
- {
- "name": "juggler_tone3",
- "unicode": "1F939-1F3FD",
+ "juggling_tone3": {
+ "category": "activity",
+ "moji": "🤹🏽",
+ "unicodeVersion": "9.0",
"digest": "7f87022722008bb265abe245e8157dc7a61944f5da62b3cf86f26ee1b3bdef63"
},
- {
- "name": "juggling_tone4",
- "unicode": "1F939-1F3FE",
+ "juggling_tone4": {
+ "category": "activity",
+ "moji": "🤹🏾",
+ "unicodeVersion": "9.0",
"digest": "1f00da8c05582c95501cc6c3fe5ce0f9bfbc16789dcee59844a8fe7831198583"
},
- {
- "name": "juggler_tone4",
- "unicode": "1F939-1F3FE",
- "digest": "1f00da8c05582c95501cc6c3fe5ce0f9bfbc16789dcee59844a8fe7831198583"
- },
- {
- "name": "juggling_tone5",
- "unicode": "1F939-1F3FF",
- "digest": "a195bf734788eb7961c00dbc05255a49da8b9d5042fada29b26cc20393d3ce52"
- },
- {
- "name": "juggler_tone5",
- "unicode": "1F939-1F3FF",
+ "juggling_tone5": {
+ "category": "activity",
+ "moji": "🤹🏿",
+ "unicodeVersion": "9.0",
"digest": "a195bf734788eb7961c00dbc05255a49da8b9d5042fada29b26cc20393d3ce52"
},
- {
- "name": "kaaba",
- "unicode": "1F54B",
+ "kaaba": {
+ "category": "travel",
+ "moji": "🕋",
+ "unicodeVersion": "8.0",
"digest": "a4618782f9583f077bd383965f1c91b9985a949bb7b6cec7af22914e7f5e9ab6"
},
- {
- "name": "key",
- "unicode": "1F511",
+ "key": {
+ "category": "objects",
+ "moji": "🔑",
+ "unicodeVersion": "6.0",
"digest": "66719fa77a50a0827c8d47237e2704c03e38186e6fef80627a765473b2294c2e"
},
- {
- "name": "key2",
- "unicode": "1F5DD",
+ "key2": {
+ "category": "objects",
+ "moji": "🗝",
+ "unicodeVersion": "7.0",
"digest": "f57240a014a9da5da3d4d98c17d0a55e0ff2e5f2d22731d2fc867105cff54c6e"
},
- {
- "name": "old_key",
- "unicode": "1F5DD",
- "digest": "f57240a014a9da5da3d4d98c17d0a55e0ff2e5f2d22731d2fc867105cff54c6e"
- },
- {
- "name": "keyboard",
- "unicode": "2328",
+ "keyboard": {
+ "category": "objects",
+ "moji": "⌨",
+ "unicodeVersion": "1.1",
"digest": "34da8ff62ca964142f9281b80123dbba74deaac8d77fa61758c30cfb36c31386"
},
- {
- "name": "kimono",
- "unicode": "1F458",
+ "kimono": {
+ "category": "people",
+ "moji": "👘",
+ "unicodeVersion": "6.0",
"digest": "637182590e256c8fb74ce4c0565f5180c07f06e3bdebf30138ed3259b209c27f"
},
- {
- "name": "kiss",
- "unicode": "1F48B",
+ "kiss": {
+ "category": "people",
+ "moji": "💋",
+ "unicodeVersion": "6.0",
"digest": "62f9b9ffcb01558cd5bb829344a1d1d399511663ff5235405c1f786c9416a94d"
},
- {
- "name": "kiss_mm",
- "unicode": "1F468-2764-1F48B-1F468",
- "digest": "6b0ae32ecb7ec0f0f43dc7a1350711185cce114c52752395f364ddbfb4f1fff4"
- },
- {
- "name": "couplekiss_mm",
- "unicode": "1F468-2764-1F48B-1F468",
+ "kiss_mm": {
+ "category": "people",
+ "moji": "👨‍❤️‍💋‍👨",
+ "unicodeVersion": "6.0",
"digest": "6b0ae32ecb7ec0f0f43dc7a1350711185cce114c52752395f364ddbfb4f1fff4"
},
- {
- "name": "kiss_ww",
- "unicode": "1F469-2764-1F48B-1F469",
+ "kiss_ww": {
+ "category": "people",
+ "moji": "👩‍❤️‍💋‍👩",
+ "unicodeVersion": "6.0",
"digest": "6de420cf752e706b1b7e9522b1b9be62eda069cb028c8fd587caf39f6a142e6a"
},
- {
- "name": "couplekiss_ww",
- "unicode": "1F469-2764-1F48B-1F469",
- "digest": "6de420cf752e706b1b7e9522b1b9be62eda069cb028c8fd587caf39f6a142e6a"
- },
- {
- "name": "kissing",
- "unicode": "1F617",
+ "kissing": {
+ "category": "people",
+ "moji": "😗",
+ "unicodeVersion": "6.1",
"digest": "b4a505f9e3d7fbd0ac60111f0e678cf425a5fd1abc65a3e9db59ae4abcfb8e85"
},
- {
- "name": "kissing_cat",
- "unicode": "1F63D",
+ "kissing_cat": {
+ "category": "people",
+ "moji": "😽",
+ "unicodeVersion": "6.0",
"digest": "a00431bf10601db4998e78433279167e52cbd36aed885399482529d5cdab8636"
},
- {
- "name": "kissing_closed_eyes",
- "unicode": "1F61A",
+ "kissing_closed_eyes": {
+ "category": "people",
+ "moji": "😚",
+ "unicodeVersion": "6.0",
"digest": "ae474db7daf80fe0b82ae1f2a11672cfcd9f9126e100f6e6d4b8a0d135dce39d"
},
- {
- "name": "kissing_heart",
- "unicode": "1F618",
+ "kissing_heart": {
+ "category": "people",
+ "moji": "😘",
+ "unicodeVersion": "6.0",
"digest": "bce372573bd3b347b555c1cd22087e03e650df73c8e0284ab668bf6633251632"
},
- {
- "name": "kissing_smiling_eyes",
- "unicode": "1F619",
+ "kissing_smiling_eyes": {
+ "category": "people",
+ "moji": "😙",
+ "unicodeVersion": "6.1",
"digest": "f0f8636cb1a02b93cc72ce1b194b890fca823d91e35926b889be3ecfae79207f"
},
- {
- "name": "kiwi",
- "unicode": "1F95D",
- "digest": "70a3a05f333d9455d2da12eed970bc3baae416286848fed8e5dd31b5be0819be"
- },
- {
- "name": "kiwifruit",
- "unicode": "1F95D",
+ "kiwi": {
+ "category": "food",
+ "moji": "🥝",
+ "unicodeVersion": "9.0",
"digest": "70a3a05f333d9455d2da12eed970bc3baae416286848fed8e5dd31b5be0819be"
},
- {
- "name": "knife",
- "unicode": "1F52A",
+ "knife": {
+ "category": "objects",
+ "moji": "🔪",
+ "unicodeVersion": "6.0",
"digest": "e6189e4843c6e80875b4952fcddb0c858f7c6039b9214bbec6a261a1358425df"
},
- {
- "name": "koala",
- "unicode": "1F428",
+ "koala": {
+ "category": "nature",
+ "moji": "🐨",
+ "unicodeVersion": "6.0",
"digest": "c58f7e0abae42c2218a85efed0e04151df67187815bebca7f3db6f435e0dab4d"
},
- {
- "name": "koko",
- "unicode": "1F201",
+ "koko": {
+ "category": "symbols",
+ "moji": "🈁",
+ "unicodeVersion": "6.0",
"digest": "5f45eb49bbf298e1fadedfe6cccc297850fcaaa4535e4cc911d48d979af55807"
},
- {
- "name": "label",
- "unicode": "1F3F7",
+ "label": {
+ "category": "objects",
+ "moji": "🏷",
+ "unicodeVersion": "7.0",
"digest": "9550ed50cedbc56eb1bd22a8a0809d837048a33d6e2e6e7d65c50d95fa05a85d"
},
- {
- "name": "large_blue_circle",
- "unicode": "1F535",
+ "large_blue_circle": {
+ "category": "symbols",
+ "moji": "🔵",
+ "unicodeVersion": "6.0",
"digest": "0df3fb3b09a6269459a3d9a1fe78db572190a948680844cfe758f53b6a482ff4"
},
- {
- "name": "large_blue_diamond",
- "unicode": "1F537",
+ "large_blue_diamond": {
+ "category": "symbols",
+ "moji": "🔷",
+ "unicodeVersion": "6.0",
"digest": "7f646b4e9de2788ed09e45f72cb512c269dda4989029b39bf9a2556659321651"
},
- {
- "name": "large_orange_diamond",
- "unicode": "1F536",
+ "large_orange_diamond": {
+ "category": "symbols",
+ "moji": "🔶",
+ "unicodeVersion": "6.0",
"digest": "80ae005ef9d79190c777f00de0993f8b3cb783f7051d76e971640c8c0827c338"
},
- {
- "name": "last_quarter_moon",
- "unicode": "1F317",
+ "last_quarter_moon": {
+ "category": "nature",
+ "moji": "🌗",
+ "unicodeVersion": "6.0",
"digest": "3d1f276607c685d50f4b70d00a57750a57ad9ad84256dafd2dc8eef8c72300c3"
},
- {
- "name": "last_quarter_moon_with_face",
- "unicode": "1F31C",
+ "last_quarter_moon_with_face": {
+ "category": "nature",
+ "moji": "🌜",
+ "unicodeVersion": "6.0",
"digest": "d516825ba52dc67f5a01433fb9df2aa77742d38efde4225983ebc4882cbdfe5d"
},
- {
- "name": "laughing",
- "unicode": "1F606",
+ "laughing": {
+ "category": "people",
+ "moji": "😆",
+ "unicodeVersion": "6.0",
"digest": "e9ea994b39650740c4961f070ed492d86b3acf6e6a830a6dadaa3a6872e81b81"
},
- {
- "name": "satisfied",
- "unicode": "1F606",
- "digest": "e9ea994b39650740c4961f070ed492d86b3acf6e6a830a6dadaa3a6872e81b81"
- },
- {
- "name": "leaves",
- "unicode": "1F343",
+ "leaves": {
+ "category": "nature",
+ "moji": "🍃",
+ "unicodeVersion": "6.0",
"digest": "56a7a0e767a6f214d340d1b5989efd99fec52c6aa306ec5c3328e32234a1631b"
},
- {
- "name": "ledger",
- "unicode": "1F4D2",
+ "ledger": {
+ "category": "objects",
+ "moji": "📒",
+ "unicodeVersion": "6.0",
"digest": "e58cb714353e96a2891a5d97910ff79660e637af909b81c49c919d3735db55b4"
},
- {
- "name": "left_facing_fist",
- "unicode": "1F91B",
+ "left_facing_fist": {
+ "category": "people",
+ "moji": "🤛",
+ "unicodeVersion": "9.0",
"digest": "7861be485beefae0de341df2f21576666e22f63511a033e785752f30c07291da"
},
- {
- "name": "left_fist",
- "unicode": "1F91B",
- "digest": "7861be485beefae0de341df2f21576666e22f63511a033e785752f30c07291da"
- },
- {
- "name": "left_facing_fist_tone1",
- "unicode": "1F91B-1F3FB",
+ "left_facing_fist_tone1": {
+ "category": "people",
+ "moji": "🤛🏻",
+ "unicodeVersion": "9.0",
"digest": "2e4c4dd96b0e4b46fe0f9ce5666344d266d0f17a8544cbae73d96638d1955296"
},
- {
- "name": "left_fist_tone1",
- "unicode": "1F91B-1F3FB",
- "digest": "2e4c4dd96b0e4b46fe0f9ce5666344d266d0f17a8544cbae73d96638d1955296"
- },
- {
- "name": "left_facing_fist_tone2",
- "unicode": "1F91B-1F3FC",
- "digest": "b96a63a801175ce98a75f0edad7b5574251a3fbbd894d8ab3f21aeeda366cc13"
- },
- {
- "name": "left_fist_tone2",
- "unicode": "1F91B-1F3FC",
+ "left_facing_fist_tone2": {
+ "category": "people",
+ "moji": "🤛🏼",
+ "unicodeVersion": "9.0",
"digest": "b96a63a801175ce98a75f0edad7b5574251a3fbbd894d8ab3f21aeeda366cc13"
},
- {
- "name": "left_facing_fist_tone3",
- "unicode": "1F91B-1F3FD",
+ "left_facing_fist_tone3": {
+ "category": "people",
+ "moji": "🤛🏽",
+ "unicodeVersion": "9.0",
"digest": "99df84635513c2ebfef24df1bd3705233e02149eef788c7b82ca0548df6f6ea5"
},
- {
- "name": "left_fist_tone3",
- "unicode": "1F91B-1F3FD",
- "digest": "99df84635513c2ebfef24df1bd3705233e02149eef788c7b82ca0548df6f6ea5"
- },
- {
- "name": "left_facing_fist_tone4",
- "unicode": "1F91B-1F3FE",
- "digest": "68954842ca725aec0aa39bce4aa81aad17ac30f5f298561dfa411feb07414cd3"
- },
- {
- "name": "left_fist_tone4",
- "unicode": "1F91B-1F3FE",
+ "left_facing_fist_tone4": {
+ "category": "people",
+ "moji": "🤛🏾",
+ "unicodeVersion": "9.0",
"digest": "68954842ca725aec0aa39bce4aa81aad17ac30f5f298561dfa411feb07414cd3"
},
- {
- "name": "left_facing_fist_tone5",
- "unicode": "1F91B-1F3FF",
- "digest": "a419b33fae82612dc860ff48950c0547a1642d4f0c94b6547324440837d3bb21"
- },
- {
- "name": "left_fist_tone5",
- "unicode": "1F91B-1F3FF",
+ "left_facing_fist_tone5": {
+ "category": "people",
+ "moji": "🤛🏿",
+ "unicodeVersion": "9.0",
"digest": "a419b33fae82612dc860ff48950c0547a1642d4f0c94b6547324440837d3bb21"
},
- {
- "name": "left_luggage",
- "unicode": "1F6C5",
+ "left_luggage": {
+ "category": "symbols",
+ "moji": "🛅",
+ "unicodeVersion": "6.0",
"digest": "6625077767a51163ea20cbc299f3c13fd5ccf1b5ce365ee702ef1fef6be3dadf"
},
- {
- "name": "left_right_arrow",
- "unicode": "2194",
+ "left_right_arrow": {
+ "category": "symbols",
+ "moji": "↔",
+ "unicodeVersion": "1.1",
"digest": "560fcf1b794eb0d5269c73b3f8da57540cbb8a6f1a9af7a9d10b202252247e34"
},
- {
- "name": "leftwards_arrow_with_hook",
- "unicode": "21A9",
+ "leftwards_arrow_with_hook": {
+ "category": "symbols",
+ "moji": "↩",
+ "unicodeVersion": "1.1",
"digest": "504714c5559b1bd35aa469be83069a923d1a25f364cac08c10df0195749e7b26"
},
- {
- "name": "lemon",
- "unicode": "1F34B",
+ "lemon": {
+ "category": "food",
+ "moji": "🍋",
+ "unicodeVersion": "6.0",
"digest": "ccca25bb6ac47770dba3aaf75144128f9a73299061969b25a35ad1733dcde5fe"
},
- {
- "name": "leo",
- "unicode": "264C",
+ "leo": {
+ "category": "symbols",
+ "moji": "♌",
+ "unicodeVersion": "1.1",
"digest": "f2ed930e279699962f189e0cac519cc29d339b3e82debfdc90c5b0935a7543bb"
},
- {
- "name": "leopard",
- "unicode": "1F406",
+ "leopard": {
+ "category": "nature",
+ "moji": "🐆",
+ "unicodeVersion": "6.0",
"digest": "d4a8964b6f2cdf6ddf074d0f1f2f65783a1a43eb4af426905fad0e60899939c7"
},
- {
- "name": "level_slider",
- "unicode": "1F39A",
+ "level_slider": {
+ "category": "objects",
+ "moji": "🎚",
+ "unicodeVersion": "7.0",
"digest": "48842324f54d971ebf548a89a82ac7f29e235702081c91b477b1a92d427290e7"
},
- {
- "name": "levitate",
- "unicode": "1F574",
- "digest": "453c24bf2544ed3ef3c710a7fabbd5fdace4dc65cddd377274d30d921523b50b"
- },
- {
- "name": "man_in_business_suit_levitating",
- "unicode": "1F574",
+ "levitate": {
+ "category": "activity",
+ "moji": "🕴",
+ "unicodeVersion": "7.0",
"digest": "453c24bf2544ed3ef3c710a7fabbd5fdace4dc65cddd377274d30d921523b50b"
},
- {
- "name": "libra",
- "unicode": "264E",
+ "libra": {
+ "category": "symbols",
+ "moji": "♎",
+ "unicodeVersion": "1.1",
"digest": "e330ba05bb449db074bc23d1514246ca5e249110f44ddb5804e5510eef6deac1"
},
- {
- "name": "lifter",
- "unicode": "1F3CB",
+ "lifter": {
+ "category": "activity",
+ "moji": "🏋",
+ "unicodeVersion": "7.0",
"digest": "d6c94a32eb863d14a2a01add8ab95040f42a55d9e3f90641a0fe143d58127558"
},
- {
- "name": "weight_lifter",
- "unicode": "1F3CB",
- "digest": "d6c94a32eb863d14a2a01add8ab95040f42a55d9e3f90641a0fe143d58127558"
- },
- {
- "name": "lifter_tone1",
- "unicode": "1F3CB-1F3FB",
- "digest": "870acf2f554fce360b58d3e98b4c0558d7ec7775587776c0f9d40c6fb1bdacf9"
- },
- {
- "name": "weight_lifter_tone1",
- "unicode": "1F3CB-1F3FB",
+ "lifter_tone1": {
+ "category": "activity",
+ "moji": "🏋🏻",
+ "unicodeVersion": "8.0",
"digest": "870acf2f554fce360b58d3e98b4c0558d7ec7775587776c0f9d40c6fb1bdacf9"
},
- {
- "name": "lifter_tone2",
- "unicode": "1F3CB-1F3FC",
- "digest": "1a7ece8512e42241cdd95c85ccc509bc0ff9c7c6ffaff2be343c77f417a27576"
- },
- {
- "name": "weight_lifter_tone2",
- "unicode": "1F3CB-1F3FC",
+ "lifter_tone2": {
+ "category": "activity",
+ "moji": "🏋🏼",
+ "unicodeVersion": "8.0",
"digest": "1a7ece8512e42241cdd95c85ccc509bc0ff9c7c6ffaff2be343c77f417a27576"
},
- {
- "name": "lifter_tone3",
- "unicode": "1F3CB-1F3FD",
- "digest": "4bc633ee82a0fb59feba379fb6901a489e4ac849d758f9c8e7a1a0a26eaa380c"
- },
- {
- "name": "weight_lifter_tone3",
- "unicode": "1F3CB-1F3FD",
+ "lifter_tone3": {
+ "category": "activity",
+ "moji": "🏋🏽",
+ "unicodeVersion": "8.0",
"digest": "4bc633ee82a0fb59feba379fb6901a489e4ac849d758f9c8e7a1a0a26eaa380c"
},
- {
- "name": "lifter_tone4",
- "unicode": "1F3CB-1F3FE",
- "digest": "d086fe5577b5ba80676f2224d886f8ebe4588314f429f12a34c52c971ed71b5c"
- },
- {
- "name": "weight_lifter_tone4",
- "unicode": "1F3CB-1F3FE",
+ "lifter_tone4": {
+ "category": "activity",
+ "moji": "🏋🏾",
+ "unicodeVersion": "8.0",
"digest": "d086fe5577b5ba80676f2224d886f8ebe4588314f429f12a34c52c971ed71b5c"
},
- {
- "name": "lifter_tone5",
- "unicode": "1F3CB-1F3FF",
- "digest": "79b0edf6ce1fd024dd7f458e322ad8588af0b789a04cc1cf38380dc8b9c76f55"
- },
- {
- "name": "weight_lifter_tone5",
- "unicode": "1F3CB-1F3FF",
+ "lifter_tone5": {
+ "category": "activity",
+ "moji": "🏋🏿",
+ "unicodeVersion": "8.0",
"digest": "79b0edf6ce1fd024dd7f458e322ad8588af0b789a04cc1cf38380dc8b9c76f55"
},
- {
- "name": "light_rail",
- "unicode": "1F688",
+ "light_rail": {
+ "category": "travel",
+ "moji": "🚈",
+ "unicodeVersion": "6.0",
"digest": "2f30b23a738371690b2f00d96ddb5ceb90a1442b5478754626a3dfa263ed2fc1"
},
- {
- "name": "link",
- "unicode": "1F517",
+ "link": {
+ "category": "objects",
+ "moji": "🔗",
+ "unicodeVersion": "6.0",
"digest": "7bf567aabd1fc38b3d70422f9db3a13b50950cf6207e70962c9938827c196ccb"
},
- {
- "name": "lion_face",
- "unicode": "1F981",
- "digest": "dd24f2668e973ec973e97dc111f59a2cc14e9b608387401191dd53368d28d4fa"
- },
- {
- "name": "lion",
- "unicode": "1F981",
+ "lion_face": {
+ "category": "nature",
+ "moji": "🦁",
+ "unicodeVersion": "8.0",
"digest": "dd24f2668e973ec973e97dc111f59a2cc14e9b608387401191dd53368d28d4fa"
},
- {
- "name": "lips",
- "unicode": "1F444",
+ "lips": {
+ "category": "people",
+ "moji": "👄",
+ "unicodeVersion": "6.0",
"digest": "8740d8086525c7a836d64625a6915cc1c59af69ba143456dbb59e0179276895e"
},
- {
- "name": "lipstick",
- "unicode": "1F484",
+ "lipstick": {
+ "category": "people",
+ "moji": "💄",
+ "unicodeVersion": "6.0",
"digest": "751dcb22706a796033b13a2ccb94304236ec13207ad4d011e02d230ae33ab5c1"
},
- {
- "name": "lizard",
- "unicode": "1F98E",
+ "lizard": {
+ "category": "nature",
+ "moji": "🦎",
+ "unicodeVersion": "9.0",
"digest": "fb9191f9eab58b8403d4c4626ccbb14ba05c1f6944011751a8edcc4dd03c66e6"
},
- {
- "name": "lock",
- "unicode": "1F512",
+ "lock": {
+ "category": "objects",
+ "moji": "🔒",
+ "unicodeVersion": "6.0",
"digest": "043b4fc0b8c79d47a07d91308e628e1ac262aea6c1ec05e6b84bf7bcdf89dc83"
},
- {
- "name": "lock_with_ink_pen",
- "unicode": "1F50F",
+ "lock_with_ink_pen": {
+ "category": "objects",
+ "moji": "🔏",
+ "unicodeVersion": "6.0",
"digest": "7b5e959b26cf7296c7b230fc2be9feb9e38391c5001951a019d16b169a71aba9"
},
- {
- "name": "lollipop",
- "unicode": "1F36D",
+ "lollipop": {
+ "category": "food",
+ "moji": "🍭",
+ "unicodeVersion": "6.0",
"digest": "17b6a0df47ec758a2f9c087b46a6902cee344d39407ef4c321e408505cbb72ca"
},
- {
- "name": "loop",
- "unicode": "27BF",
+ "loop": {
+ "category": "symbols",
+ "moji": "➿",
+ "unicodeVersion": "6.0",
"digest": "9f20ecc34b3c871789ba7d0712aa31e7a74b6c1558ac8bea385bc40590056726"
},
- {
- "name": "loud_sound",
- "unicode": "1F50A",
+ "loud_sound": {
+ "category": "symbols",
+ "moji": "🔊",
+ "unicodeVersion": "6.0",
"digest": "64b12db9ddd8adf74a9fc2bd83c7979ea865113347f7ce8666e9ccf5019e715f"
},
- {
- "name": "loudspeaker",
- "unicode": "1F4E2",
+ "loudspeaker": {
+ "category": "symbols",
+ "moji": "📢",
+ "unicodeVersion": "6.0",
"digest": "1e1f35d16dd2898ebaa6f2b2868203df6e09c8a70df069c92d6d1b5cb2ac0976"
},
- {
- "name": "love_hotel",
- "unicode": "1F3E9",
+ "love_hotel": {
+ "category": "travel",
+ "moji": "🏩",
+ "unicodeVersion": "6.0",
"digest": "ff8966a50fd47a216855488eb09a367d231fea21f49e7e5325191d32fb494473"
},
- {
- "name": "love_letter",
- "unicode": "1F48C",
+ "love_letter": {
+ "category": "objects",
+ "moji": "💌",
+ "unicodeVersion": "6.0",
"digest": "037261c8ca4d72f7205e51664591696da2ae7ceb19f1c1c9f6123da5a5979d29"
},
- {
- "name": "low_brightness",
- "unicode": "1F505",
+ "low_brightness": {
+ "category": "symbols",
+ "moji": "🔅",
+ "unicodeVersion": "6.0",
"digest": "a065d00a416e297c168b0a675cafcf492fedf94865cb21801a1be5a3914593d4"
},
- {
- "name": "lying_face",
- "unicode": "1F925",
- "digest": "ce836170165e1b70938273f289c02c2106873cd9ab5472dbcd487c2f9f53f13d"
- },
- {
- "name": "liar",
- "unicode": "1F925",
+ "lying_face": {
+ "category": "people",
+ "moji": "🤥",
+ "unicodeVersion": "9.0",
"digest": "ce836170165e1b70938273f289c02c2106873cd9ab5472dbcd487c2f9f53f13d"
},
- {
- "name": "m",
- "unicode": "24C2",
+ "m": {
+ "category": "symbols",
+ "moji": "Ⓜ",
+ "unicodeVersion": "1.1",
"digest": "54588ac2b7fcd53a96f17124e9de69b617613fcd5af9ad2930a094cb795bb9f4"
},
- {
- "name": "mag",
- "unicode": "1F50D",
+ "mag": {
+ "category": "objects",
+ "moji": "🔍",
+ "unicodeVersion": "6.0",
"digest": "a6e31a2efa7d9427aaa30b45d9f4181ee55c44be08aea2df165a86e0e6d9eaa1"
},
- {
- "name": "mag_right",
- "unicode": "1F50E",
+ "mag_right": {
+ "category": "objects",
+ "moji": "🔎",
+ "unicodeVersion": "6.0",
"digest": "c7d8ceeb05db261e5eaab31dc4da432d0d5592a2ed71e526c5a542daa230bbaf"
},
- {
- "name": "mahjong",
- "unicode": "1F004",
+ "mahjong": {
+ "category": "symbols",
+ "moji": "🀄",
+ "unicodeVersion": "5.1",
"digest": "755d69f988434ce1c17531a8b7ac92ead6f5607c2635a22f10e0ad70f09fc3e6"
},
- {
- "name": "mailbox",
- "unicode": "1F4EB",
+ "mailbox": {
+ "category": "objects",
+ "moji": "📫",
+ "unicodeVersion": "6.0",
"digest": "2069091be90a530a43ef29d5ec7688c351bf4d5b08d63a0d20d72b67d639ec62"
},
- {
- "name": "mailbox_closed",
- "unicode": "1F4EA",
+ "mailbox_closed": {
+ "category": "objects",
+ "moji": "📪",
+ "unicodeVersion": "6.0",
"digest": "d88d65bfebb8216535fd055c69f319564b2cf0b0901820f8312f581864557ed4"
},
- {
- "name": "mailbox_with_mail",
- "unicode": "1F4EC",
+ "mailbox_with_mail": {
+ "category": "objects",
+ "moji": "📬",
+ "unicodeVersion": "6.0",
"digest": "69e966b4659128991a70c6a2dd4d647551bedb91bdf5ce688958686bbec56381"
},
- {
- "name": "mailbox_with_no_mail",
- "unicode": "1F4ED",
+ "mailbox_with_no_mail": {
+ "category": "objects",
+ "moji": "📭",
+ "unicodeVersion": "6.0",
"digest": "9e92d8ee88f660ce56da61077c80ec26c5d8f54ebd2306c4cfa16f6c1b981f83"
},
- {
- "name": "man",
- "unicode": "1F468",
+ "man": {
+ "category": "people",
+ "moji": "👨",
+ "unicodeVersion": "6.0",
"digest": "42b882d2c6aa095f1afcf901203838d95c1908bdc725519779186b9c33c728d7"
},
- {
- "name": "man_dancing",
- "unicode": "1F57A",
- "digest": "9f632ee0c886d5f03c61e5f3a27668262c0cc2693b857a91c23c1e5ea3785b9e"
- },
- {
- "name": "male_dancer",
- "unicode": "1F57A",
+ "man_dancing": {
+ "category": "people",
+ "moji": "🕺",
+ "unicodeVersion": "9.0",
"digest": "9f632ee0c886d5f03c61e5f3a27668262c0cc2693b857a91c23c1e5ea3785b9e"
},
- {
- "name": "man_dancing_tone1",
- "unicode": "1F57A-1F3FB",
- "digest": "6c56a16cb105bcdd97472645b3a351cebdbb1132cbfd18b0118f289db5fbe741"
- },
- {
- "name": "male_dancer_tone1",
- "unicode": "1F57A-1F3FB",
+ "man_dancing_tone1": {
+ "category": "activity",
+ "moji": "🕺🏻",
+ "unicodeVersion": "9.0",
"digest": "6c56a16cb105bcdd97472645b3a351cebdbb1132cbfd18b0118f289db5fbe741"
},
- {
- "name": "man_dancing_tone2",
- "unicode": "1F57A-1F3FC",
- "digest": "ed7e78c14d205a03fdd5581e5213add69a55e13b4cbaf76a6d5a0d6c80f53327"
- },
- {
- "name": "male_dancer_tone2",
- "unicode": "1F57A-1F3FC",
+ "man_dancing_tone2": {
+ "category": "activity",
+ "moji": "🕺🏼",
+ "unicodeVersion": "9.0",
"digest": "ed7e78c14d205a03fdd5581e5213add69a55e13b4cbaf76a6d5a0d6c80f53327"
},
- {
- "name": "man_dancing_tone3",
- "unicode": "1F57A-1F3FD",
+ "man_dancing_tone3": {
+ "category": "activity",
+ "moji": "🕺🏽",
+ "unicodeVersion": "9.0",
"digest": "13b45403e11800163406206eedeb8b579cc83eca2f60246be97e099164387bc8"
},
- {
- "name": "male_dancer_tone3",
- "unicode": "1F57A-1F3FD",
- "digest": "13b45403e11800163406206eedeb8b579cc83eca2f60246be97e099164387bc8"
- },
- {
- "name": "man_dancing_tone4",
- "unicode": "1F57A-1F3FE",
- "digest": "f6feb1b0b83565fadcdd1a8737d3daa08893e919547d2a06de899160162d9c4a"
- },
- {
- "name": "male_dancer_tone4",
- "unicode": "1F57A-1F3FE",
+ "man_dancing_tone4": {
+ "category": "activity",
+ "moji": "🕺🏾",
+ "unicodeVersion": "9.0",
"digest": "f6feb1b0b83565fadcdd1a8737d3daa08893e919547d2a06de899160162d9c4a"
},
- {
- "name": "man_dancing_tone5",
- "unicode": "1F57A-1F3FF",
+ "man_dancing_tone5": {
+ "category": "activity",
+ "moji": "🕺🏿",
+ "unicodeVersion": "9.0",
"digest": "fe20a9ed9ba991653b4d0683de347ed7c226a5d75610307584a2ddd6fcd1e3f2"
},
- {
- "name": "male_dancer_tone5",
- "unicode": "1F57A-1F3FF",
- "digest": "fe20a9ed9ba991653b4d0683de347ed7c226a5d75610307584a2ddd6fcd1e3f2"
- },
- {
- "name": "man_in_tuxedo",
- "unicode": "1F935",
+ "man_in_tuxedo": {
+ "category": "people",
+ "moji": "🤵",
+ "unicodeVersion": "9.0",
"digest": "4d451a971dfefedc4830ba78e19b123f250e09ae65baddccdc56c0f8aa3a9b50"
},
- {
- "name": "man_in_tuxedo_tone1",
- "unicode": "1F935-1F3FB",
- "digest": "2814833334fb211ae2ecb1fb5964e9752282d0fb4d7f3477de5dd2a4f812a793"
- },
- {
- "name": "tuxedo_tone1",
- "unicode": "1F935-1F3FB",
+ "man_in_tuxedo_tone1": {
+ "category": "people",
+ "moji": "🤵🏻",
+ "unicodeVersion": "9.0",
"digest": "2814833334fb211ae2ecb1fb5964e9752282d0fb4d7f3477de5dd2a4f812a793"
},
- {
- "name": "man_in_tuxedo_tone2",
- "unicode": "1F935-1F3FC",
+ "man_in_tuxedo_tone2": {
+ "category": "people",
+ "moji": "🤵🏼",
+ "unicodeVersion": "9.0",
"digest": "cd1bab9ee0e2335d3cd99d51216cccdc4fc3c2cf20129b8b7e11a51a77258f68"
},
- {
- "name": "tuxedo_tone2",
- "unicode": "1F935-1F3FC",
- "digest": "cd1bab9ee0e2335d3cd99d51216cccdc4fc3c2cf20129b8b7e11a51a77258f68"
- },
- {
- "name": "man_in_tuxedo_tone3",
- "unicode": "1F935-1F3FD",
- "digest": "f387775f925fe60b9f3e7cad63a55d4d196ddd41658029a70440d14c17cb99f9"
- },
- {
- "name": "tuxedo_tone3",
- "unicode": "1F935-1F3FD",
+ "man_in_tuxedo_tone3": {
+ "category": "people",
+ "moji": "🤵🏽",
+ "unicodeVersion": "9.0",
"digest": "f387775f925fe60b9f3e7cad63a55d4d196ddd41658029a70440d14c17cb99f9"
},
- {
- "name": "man_in_tuxedo_tone4",
- "unicode": "1F935-1F3FE",
+ "man_in_tuxedo_tone4": {
+ "category": "people",
+ "moji": "🤵🏾",
+ "unicodeVersion": "9.0",
"digest": "08debd7a573d1201aee8a2f281ef7cb638d4a2a096222150391f36963f07c622"
},
- {
- "name": "tuxedo_tone4",
- "unicode": "1F935-1F3FE",
- "digest": "08debd7a573d1201aee8a2f281ef7cb638d4a2a096222150391f36963f07c622"
- },
- {
- "name": "man_in_tuxedo_tone5",
- "unicode": "1F935-1F3FF",
- "digest": "e3b10e0619f0911cf9b665a265f4ef829b8f6ba6e9c3a021d0539a27e315f8fe"
- },
- {
- "name": "tuxedo_tone5",
- "unicode": "1F935-1F3FF",
+ "man_in_tuxedo_tone5": {
+ "category": "people",
+ "moji": "🤵🏿",
+ "unicodeVersion": "9.0",
"digest": "e3b10e0619f0911cf9b665a265f4ef829b8f6ba6e9c3a021d0539a27e315f8fe"
},
- {
- "name": "man_tone1",
- "unicode": "1F468-1F3FB",
+ "man_tone1": {
+ "category": "people",
+ "moji": "👨🏻",
+ "unicodeVersion": "8.0",
"digest": "7053e265fa7d2594de54a6c5d06c21795b9a7dfb36a1c5594ca43c4c6cc56504"
},
- {
- "name": "man_tone2",
- "unicode": "1F468-1F3FC",
+ "man_tone2": {
+ "category": "people",
+ "moji": "👨🏼",
+ "unicodeVersion": "8.0",
"digest": "7ebc64de40d3ac60fb761be5cf94f53fa10b4f03fb66add46c90f5d98eaf71eb"
},
- {
- "name": "man_tone3",
- "unicode": "1F468-1F3FD",
+ "man_tone3": {
+ "category": "people",
+ "moji": "👨🏽",
+ "unicodeVersion": "8.0",
"digest": "77ceef4d3740ed4751acb83dd45b6b754cf625c522c6757309cd4d61202d7149"
},
- {
- "name": "man_tone4",
- "unicode": "1F468-1F3FE",
+ "man_tone4": {
+ "category": "people",
+ "moji": "👨🏾",
+ "unicodeVersion": "8.0",
"digest": "41e6037c393f61cca61b9a81b27ed14a95d75fe380e3a00153c33a371a836ffd"
},
- {
- "name": "man_tone5",
- "unicode": "1F468-1F3FF",
+ "man_tone5": {
+ "category": "people",
+ "moji": "👨🏿",
+ "unicodeVersion": "8.0",
"digest": "a8cebfd39a5b9c79af7cc37f205e1135376056fee287af967c9f55d415572d99"
},
- {
- "name": "man_with_gua_pi_mao",
- "unicode": "1F472",
+ "man_with_gua_pi_mao": {
+ "category": "people",
+ "moji": "👲",
+ "unicodeVersion": "6.0",
"digest": "3dae285e900c69986a48db0fa89d4f371a49f38608059cdae52be098030c5ac4"
},
- {
- "name": "man_with_gua_pi_mao_tone1",
- "unicode": "1F472-1F3FB",
+ "man_with_gua_pi_mao_tone1": {
+ "category": "people",
+ "moji": "👲🏻",
+ "unicodeVersion": "8.0",
"digest": "35404d8e266920c78edd9e7143fb052b42f65242a5698494c4f4365e9183cc67"
},
- {
- "name": "man_with_gua_pi_mao_tone2",
- "unicode": "1F472-1F3FC",
+ "man_with_gua_pi_mao_tone2": {
+ "category": "people",
+ "moji": "👲🏼",
+ "unicodeVersion": "8.0",
"digest": "82d4f968665a93c7543372c8a1eeb0f25d0ea6842d5e518bd91c226c6c3ab8c2"
},
- {
- "name": "man_with_gua_pi_mao_tone3",
- "unicode": "1F472-1F3FD",
+ "man_with_gua_pi_mao_tone3": {
+ "category": "people",
+ "moji": "👲🏽",
+ "unicodeVersion": "8.0",
"digest": "f44159f0c672b9b833449382896180e799abf574f5b3c6cd9541caa992fa18ce"
},
- {
- "name": "man_with_gua_pi_mao_tone4",
- "unicode": "1F472-1F3FE",
+ "man_with_gua_pi_mao_tone4": {
+ "category": "people",
+ "moji": "👲🏾",
+ "unicodeVersion": "8.0",
"digest": "c79060188f9461ca34eaa225b7682d8c410883609509fb731c992db69bfeeb50"
},
- {
- "name": "man_with_gua_pi_mao_tone5",
- "unicode": "1F472-1F3FF",
+ "man_with_gua_pi_mao_tone5": {
+ "category": "people",
+ "moji": "👲🏿",
+ "unicodeVersion": "8.0",
"digest": "de9e4acdb10f7abddeeabc0b48d91139fc8b544a601c530db811f099991b0d38"
},
- {
- "name": "man_with_turban",
- "unicode": "1F473",
+ "man_with_turban": {
+ "category": "people",
+ "moji": "👳",
+ "unicodeVersion": "6.0",
"digest": "db72c944e93983f38d00e3e936ebb5b243c6069f1f1236d46f6a9f1beb8d6634"
},
- {
- "name": "man_with_turban_tone1",
- "unicode": "1F473-1F3FB",
+ "man_with_turban_tone1": {
+ "category": "people",
+ "moji": "👳🏻",
+ "unicodeVersion": "8.0",
"digest": "b6d7489c4cd151af09fff48b62c54c336303e14866e6ef38f94cd834b085d09e"
},
- {
- "name": "man_with_turban_tone2",
- "unicode": "1F473-1F3FC",
+ "man_with_turban_tone2": {
+ "category": "people",
+ "moji": "👳🏼",
+ "unicodeVersion": "8.0",
"digest": "7854ef973c21847f452d7e78e5c460ea300e12b539ce92c69dabe8f1bf3a4382"
},
- {
- "name": "man_with_turban_tone3",
- "unicode": "1F473-1F3FD",
+ "man_with_turban_tone3": {
+ "category": "people",
+ "moji": "👳🏽",
+ "unicodeVersion": "8.0",
"digest": "1dbd9bd78f5263cbadee7d0d5754c14cfbc914f7329e25fbd97d9f5b8ce0737e"
},
- {
- "name": "man_with_turban_tone4",
- "unicode": "1F473-1F3FE",
+ "man_with_turban_tone4": {
+ "category": "people",
+ "moji": "👳🏾",
+ "unicodeVersion": "8.0",
"digest": "4f4804da4a7c98ad4f9db3ae3eaf674c8977c638e73414e33ef1f65098e413a3"
},
- {
- "name": "man_with_turban_tone5",
- "unicode": "1F473-1F3FF",
+ "man_with_turban_tone5": {
+ "category": "people",
+ "moji": "👳🏿",
+ "unicodeVersion": "8.0",
"digest": "240282aa346ef9b1d0d475ea93a02597697f0f56f086305879b532b0b933210a"
},
- {
- "name": "mans_shoe",
- "unicode": "1F45E",
+ "mans_shoe": {
+ "category": "people",
+ "moji": "👞",
+ "unicodeVersion": "6.0",
"digest": "f53fe74abd9906cd3e2dd7e7bddbe1feb9f8f7be28b807fabe452f1f60ca1b84"
},
- {
- "name": "map",
- "unicode": "1F5FA",
+ "map": {
+ "category": "objects",
+ "moji": "🗺",
+ "unicodeVersion": "7.0",
"digest": "84f496a062b5c3ae1e8013506175a69036038c8130891bcf780a69ce7fcbe4de"
},
- {
- "name": "world_map",
- "unicode": "1F5FA",
- "digest": "84f496a062b5c3ae1e8013506175a69036038c8130891bcf780a69ce7fcbe4de"
- },
- {
- "name": "maple_leaf",
- "unicode": "1F341",
+ "maple_leaf": {
+ "category": "nature",
+ "moji": "🍁",
+ "unicodeVersion": "6.0",
"digest": "72629a205e33f89337815ad7e51bb5c73947d1a9f98afe5072bdf4846827ae72"
},
- {
- "name": "martial_arts_uniform",
- "unicode": "1F94B",
- "digest": "a1ae797b31081425b388ab31efc635d8eb73a40980fd0fae4708aa5313e2a964"
- },
- {
- "name": "karate_uniform",
- "unicode": "1F94B",
+ "martial_arts_uniform": {
+ "category": "activity",
+ "moji": "🥋",
+ "unicodeVersion": "9.0",
"digest": "a1ae797b31081425b388ab31efc635d8eb73a40980fd0fae4708aa5313e2a964"
},
- {
- "name": "mask",
- "unicode": "1F637",
+ "mask": {
+ "category": "people",
+ "moji": "😷",
+ "unicodeVersion": "6.0",
"digest": "1b58af9ae599308aabf41bbd38f599fa896bd9fe5df7a40be9f2dc7e0e230600"
},
- {
- "name": "massage",
- "unicode": "1F486",
+ "massage": {
+ "category": "people",
+ "moji": "💆",
+ "unicodeVersion": "6.0",
"digest": "6ee48b4d8cec0bf31e11d7803ad9fc1f909457c8c00cb320b5671395af3c170c"
},
- {
- "name": "massage_tone1",
- "unicode": "1F486-1F3FB",
+ "massage_tone1": {
+ "category": "people",
+ "moji": "💆🏻",
+ "unicodeVersion": "8.0",
"digest": "9da162c2f39628156b87db986a6ada59372a9e9a6b3f0488d21c9e65ec3309bb"
},
- {
- "name": "massage_tone2",
- "unicode": "1F486-1F3FC",
+ "massage_tone2": {
+ "category": "people",
+ "moji": "💆🏼",
+ "unicodeVersion": "8.0",
"digest": "ac259188549b5b429b8c4929e1da2314859e8857ee49720551467aedfcc96567"
},
- {
- "name": "massage_tone3",
- "unicode": "1F486-1F3FD",
+ "massage_tone3": {
+ "category": "people",
+ "moji": "💆🏽",
+ "unicodeVersion": "8.0",
"digest": "cfd9c105b6debc10448f172afcb20d4192899f7ae5aa8af54c834153a5466364"
},
- {
- "name": "massage_tone4",
- "unicode": "1F486-1F3FE",
+ "massage_tone4": {
+ "category": "people",
+ "moji": "💆🏾",
+ "unicodeVersion": "8.0",
"digest": "38ab715c621c58454f3cb09153a96380118cf082568554b6edc5f83fb62e9297"
},
- {
- "name": "massage_tone5",
- "unicode": "1F486-1F3FF",
+ "massage_tone5": {
+ "category": "people",
+ "moji": "💆🏿",
+ "unicodeVersion": "8.0",
"digest": "32480457734121b0c83e9be6d693ae379c95535f43f963c0c2f0f20434ee12c6"
},
- {
- "name": "meat_on_bone",
- "unicode": "1F356",
+ "meat_on_bone": {
+ "category": "food",
+ "moji": "🍖",
+ "unicodeVersion": "6.0",
"digest": "d71a8e0b118d5e6ca60690793ce9649afb78e707fcbd7be890a75564c94434fd"
},
- {
- "name": "medal",
- "unicode": "1F3C5",
+ "medal": {
+ "category": "activity",
+ "moji": "🏅",
+ "unicodeVersion": "7.0",
"digest": "9600cbe57e08da090c60629bcafd2821c87322e738c2454f8e883ceb756e7391"
},
- {
- "name": "sports_medal",
- "unicode": "1F3C5",
- "digest": "9600cbe57e08da090c60629bcafd2821c87322e738c2454f8e883ceb756e7391"
- },
- {
- "name": "mega",
- "unicode": "1F4E3",
+ "mega": {
+ "category": "symbols",
+ "moji": "📣",
+ "unicodeVersion": "6.0",
"digest": "4b1def6b5b051c5045514063f0ac006222ad81fbfe56d840e14bb950713e331b"
},
- {
- "name": "melon",
- "unicode": "1F348",
+ "melon": {
+ "category": "food",
+ "moji": "🍈",
+ "unicodeVersion": "6.0",
"digest": "0cdd663e6f2129808856cdf0746e6571b62aac641f224adb553baf3bb63ba3bd"
},
- {
- "name": "menorah",
- "unicode": "1F54E",
+ "menorah": {
+ "category": "symbols",
+ "moji": "🕎",
+ "unicodeVersion": "8.0",
"digest": "49fca8c3bc00ea69653ee2f8d4e21e561856ba39716c13e9d107db3e805a2997"
},
- {
- "name": "mens",
- "unicode": "1F6B9",
+ "mens": {
+ "category": "symbols",
+ "moji": "🚹",
+ "unicodeVersion": "6.0",
"digest": "7d92292586ee12a5d1a557c37da4d14708dc3ce701cf32d3280dcc83d91e5df8"
},
- {
- "name": "metal",
- "unicode": "1F918",
- "digest": "ffb750caf187f5d821c990108e2699ac3e216492bcff6ee543f4a7aa55b9fd29"
- },
- {
- "name": "sign_of_the_horns",
- "unicode": "1F918",
+ "metal": {
+ "category": "people",
+ "moji": "🤘",
+ "unicodeVersion": "8.0",
"digest": "ffb750caf187f5d821c990108e2699ac3e216492bcff6ee543f4a7aa55b9fd29"
},
- {
- "name": "metal_tone1",
- "unicode": "1F918-1F3FB",
+ "metal_tone1": {
+ "category": "people",
+ "moji": "🤘🏻",
+ "unicodeVersion": "8.0",
"digest": "5505f0b0340f9ba572db8897e40adf598cfa784686ad5ee360a7351bf44ddc1d"
},
- {
- "name": "sign_of_the_horns_tone1",
- "unicode": "1F918-1F3FB",
- "digest": "5505f0b0340f9ba572db8897e40adf598cfa784686ad5ee360a7351bf44ddc1d"
- },
- {
- "name": "metal_tone2",
- "unicode": "1F918-1F3FC",
- "digest": "8f9eee3ad5fc7eeeb30118d16d27467b16fd87297e0ecf02656db77e701f5aeb"
- },
- {
- "name": "sign_of_the_horns_tone2",
- "unicode": "1F918-1F3FC",
+ "metal_tone2": {
+ "category": "people",
+ "moji": "🤘🏼",
+ "unicodeVersion": "8.0",
"digest": "8f9eee3ad5fc7eeeb30118d16d27467b16fd87297e0ecf02656db77e701f5aeb"
},
- {
- "name": "metal_tone3",
- "unicode": "1F918-1F3FD",
+ "metal_tone3": {
+ "category": "people",
+ "moji": "🤘🏽",
+ "unicodeVersion": "8.0",
"digest": "8270a7ecf5eb11431a07ef04cc476c2651ac8aacb0d4768e5cb69355f8a5e84e"
},
- {
- "name": "sign_of_the_horns_tone3",
- "unicode": "1F918-1F3FD",
- "digest": "8270a7ecf5eb11431a07ef04cc476c2651ac8aacb0d4768e5cb69355f8a5e84e"
- },
- {
- "name": "metal_tone4",
- "unicode": "1F918-1F3FE",
+ "metal_tone4": {
+ "category": "people",
+ "moji": "🤘🏾",
+ "unicodeVersion": "8.0",
"digest": "f24f7b137dd6c7899dc0a8794204bbde7ad43ec1e63b419c90dd70a8b77871e8"
},
- {
- "name": "sign_of_the_horns_tone4",
- "unicode": "1F918-1F3FE",
- "digest": "f24f7b137dd6c7899dc0a8794204bbde7ad43ec1e63b419c90dd70a8b77871e8"
- },
- {
- "name": "metal_tone5",
- "unicode": "1F918-1F3FF",
+ "metal_tone5": {
+ "category": "people",
+ "moji": "🤘🏿",
+ "unicodeVersion": "8.0",
"digest": "07b0726a632653b980df775f460cd3fe1ea8d4a7b0b46fe29e089b66579482d2"
},
- {
- "name": "sign_of_the_horns_tone5",
- "unicode": "1F918-1F3FF",
- "digest": "07b0726a632653b980df775f460cd3fe1ea8d4a7b0b46fe29e089b66579482d2"
- },
- {
- "name": "metro",
- "unicode": "1F687",
+ "metro": {
+ "category": "travel",
+ "moji": "🚇",
+ "unicodeVersion": "6.0",
"digest": "b380247b61b5e2ca1b9b70fabff65907b2c3a5191a14b169ae094af94659b9b1"
},
- {
- "name": "microphone",
- "unicode": "1F3A4",
+ "microphone": {
+ "category": "activity",
+ "moji": "🎤",
+ "unicodeVersion": "6.0",
"digest": "9ef4fc2e40d5391c4bb2d30f34f59662cff7cbb1b04341c9dac210d0e21b44ae"
},
- {
- "name": "microphone2",
- "unicode": "1F399",
- "digest": "8a30464d51f7f101335778444c43270ac0679900f49463e6556682d9db1cb4dc"
- },
- {
- "name": "studio_microphone",
- "unicode": "1F399",
+ "microphone2": {
+ "category": "objects",
+ "moji": "🎙",
+ "unicodeVersion": "7.0",
"digest": "8a30464d51f7f101335778444c43270ac0679900f49463e6556682d9db1cb4dc"
},
- {
- "name": "microscope",
- "unicode": "1F52C",
+ "microscope": {
+ "category": "objects",
+ "moji": "🔬",
+ "unicodeVersion": "6.0",
"digest": "4ca4322c6ba99b8c15acdb8b605f84f87398769e504b262b134c1f3868b2692f"
},
- {
- "name": "middle_finger",
- "unicode": "1F595",
+ "middle_finger": {
+ "category": "people",
+ "moji": "🖕",
+ "unicodeVersion": "7.0",
"digest": "0c3f1cc0ec7323f6d19508ad22fa90050845f7b5cc83f599ab2cacb89cf5dd0e"
},
- {
- "name": "reversed_hand_with_middle_finger_extended",
- "unicode": "1F595",
- "digest": "0c3f1cc0ec7323f6d19508ad22fa90050845f7b5cc83f599ab2cacb89cf5dd0e"
- },
- {
- "name": "middle_finger_tone1",
- "unicode": "1F595-1F3FB",
- "digest": "4ebecf1058a3059aaa826eaad39c1a791120f115f65dde6d6ae32fc5561f60f7"
- },
- {
- "name": "reversed_hand_with_middle_finger_extended_tone1",
- "unicode": "1F595-1F3FB",
+ "middle_finger_tone1": {
+ "category": "people",
+ "moji": "🖕🏻",
+ "unicodeVersion": "8.0",
"digest": "4ebecf1058a3059aaa826eaad39c1a791120f115f65dde6d6ae32fc5561f60f7"
},
- {
- "name": "middle_finger_tone2",
- "unicode": "1F595-1F3FC",
- "digest": "85ff506a08c38663c2dfa2e3a90584c02a36aa3dda33af47cdb49834bf9baf83"
- },
- {
- "name": "reversed_hand_with_middle_finger_extended_tone2",
- "unicode": "1F595-1F3FC",
+ "middle_finger_tone2": {
+ "category": "people",
+ "moji": "🖕🏼",
+ "unicodeVersion": "8.0",
"digest": "85ff506a08c38663c2dfa2e3a90584c02a36aa3dda33af47cdb49834bf9baf83"
},
- {
- "name": "middle_finger_tone3",
- "unicode": "1F595-1F3FD",
- "digest": "cac697ff5207bf8a4e091912f3127f4e73c88ef69b5c6561d1d7b12ed60be8f1"
- },
- {
- "name": "reversed_hand_with_middle_finger_extended_tone3",
- "unicode": "1F595-1F3FD",
+ "middle_finger_tone3": {
+ "category": "people",
+ "moji": "🖕🏽",
+ "unicodeVersion": "8.0",
"digest": "cac697ff5207bf8a4e091912f3127f4e73c88ef69b5c6561d1d7b12ed60be8f1"
},
- {
- "name": "middle_finger_tone4",
- "unicode": "1F595-1F3FE",
- "digest": "9324a5a4e3986b798ad8c61f31c18fb507ca7a4abfd6e9ae1408b80b185bf8c7"
- },
- {
- "name": "reversed_hand_with_middle_finger_extended_tone4",
- "unicode": "1F595-1F3FE",
+ "middle_finger_tone4": {
+ "category": "people",
+ "moji": "🖕🏾",
+ "unicodeVersion": "8.0",
"digest": "9324a5a4e3986b798ad8c61f31c18fb507ca7a4abfd6e9ae1408b80b185bf8c7"
},
- {
- "name": "middle_finger_tone5",
- "unicode": "1F595-1F3FF",
- "digest": "078f917cd4d8be08a880724e9400449980d92740ccbee4a57f5046a9cf7f6575"
- },
- {
- "name": "reversed_hand_with_middle_finger_extended_tone5",
- "unicode": "1F595-1F3FF",
+ "middle_finger_tone5": {
+ "category": "people",
+ "moji": "🖕🏿",
+ "unicodeVersion": "8.0",
"digest": "078f917cd4d8be08a880724e9400449980d92740ccbee4a57f5046a9cf7f6575"
},
- {
- "name": "military_medal",
- "unicode": "1F396",
+ "military_medal": {
+ "category": "activity",
+ "moji": "🎖",
+ "unicodeVersion": "7.0",
"digest": "5da18351dc14b66cfc070148c83b7c8e67e6b1e3f515ae501133c38ee5c28d3d"
},
- {
- "name": "milk",
- "unicode": "1F95B",
- "digest": "38b28ea40399601fabc95bac5eaaf5a9e4e25548ec80325bd5069395ea884f85"
- },
- {
- "name": "glass_of_milk",
- "unicode": "1F95B",
+ "milk": {
+ "category": "food",
+ "moji": "🥛",
+ "unicodeVersion": "9.0",
"digest": "38b28ea40399601fabc95bac5eaaf5a9e4e25548ec80325bd5069395ea884f85"
},
- {
- "name": "milky_way",
- "unicode": "1F30C",
+ "milky_way": {
+ "category": "travel",
+ "moji": "🌌",
+ "unicodeVersion": "6.0",
"digest": "17405ff31d94b13a1fb0adcda204b8adb95ca340bc3980d9ad9f42ba1e366e7d"
},
- {
- "name": "minibus",
- "unicode": "1F690",
+ "minibus": {
+ "category": "travel",
+ "moji": "🚐",
+ "unicodeVersion": "6.0",
"digest": "08ccb4b1bf397b7c9aed901e2b5dcdd6cb8ca5c5487ef26775bb3120f7b92524"
},
- {
- "name": "minidisc",
- "unicode": "1F4BD",
+ "minidisc": {
+ "category": "objects",
+ "moji": "💽",
+ "unicodeVersion": "6.0",
"digest": "bebf82c0b91ef66321e7ae7a0abf322e59b2f7d8e6fbf9a94243210c00229c59"
},
- {
- "name": "mobile_phone_off",
- "unicode": "1F4F4",
+ "mobile_phone_off": {
+ "category": "symbols",
+ "moji": "📴",
+ "unicodeVersion": "6.0",
"digest": "6f9d8d6a32fc998f5d8144a5ff7e2ad00de37ad464cd97285e7c72efb09a1feb"
},
- {
- "name": "money_mouth",
- "unicode": "1F911",
+ "money_mouth": {
+ "category": "people",
+ "moji": "🤑",
+ "unicodeVersion": "8.0",
"digest": "5a43973dadf48a89201b1816fea9972c5cfe501a26fe457b6f7eee0a6362018e"
},
- {
- "name": "money_mouth_face",
- "unicode": "1F911",
- "digest": "5a43973dadf48a89201b1816fea9972c5cfe501a26fe457b6f7eee0a6362018e"
- },
- {
- "name": "money_with_wings",
- "unicode": "1F4B8",
+ "money_with_wings": {
+ "category": "objects",
+ "moji": "💸",
+ "unicodeVersion": "6.0",
"digest": "15fcf0595021374ba091ca00efdb4167770da4d421eab930964108545f4edab9"
},
- {
- "name": "moneybag",
- "unicode": "1F4B0",
+ "moneybag": {
+ "category": "objects",
+ "moji": "💰",
+ "unicodeVersion": "6.0",
"digest": "02d708e2f603b0df6f6c169b5c49b3452e1c02e7d72e96f228b73d0b0a20bff4"
},
- {
- "name": "monkey",
- "unicode": "1F412",
+ "monkey": {
+ "category": "nature",
+ "moji": "🐒",
+ "unicodeVersion": "6.0",
"digest": "3588a544d6d9e9995b45d60327a1a42002fa1faa4d48224b140facd249af1c67"
},
- {
- "name": "monkey_face",
- "unicode": "1F435",
+ "monkey_face": {
+ "category": "nature",
+ "moji": "🐵",
+ "unicodeVersion": "6.0",
"digest": "9e263ef5ca42bb76d1b1d1e3cbf020bcf05023a6e9f91301d30c9eb406363a2a"
},
- {
- "name": "monorail",
- "unicode": "1F69D",
+ "monorail": {
+ "category": "travel",
+ "moji": "🚝",
+ "unicodeVersion": "6.0",
"digest": "2c9f185babcb4001fcef2b8dfc4a32126729843084d0076c3e3ccdc845ab23ad"
},
- {
- "name": "mortar_board",
- "unicode": "1F393",
+ "mortar_board": {
+ "category": "people",
+ "moji": "🎓",
+ "unicodeVersion": "6.0",
"digest": "d7fbe41d4b340d3564e484aec46a22c9613521414b2ba6eece2180db4d23e410"
},
- {
- "name": "mosque",
- "unicode": "1F54C",
+ "mosque": {
+ "category": "travel",
+ "moji": "🕌",
+ "unicodeVersion": "8.0",
"digest": "5f3d3de7feac953a70a318113531c2857d760a516c3d8d6f42d2a3b3b67ed196"
},
- {
- "name": "motor_scooter",
- "unicode": "1F6F5",
- "digest": "e2dc7c981744a71f46858bd0858ff91af704ac06425ed80377bc3b119e57c872"
- },
- {
- "name": "motorbike",
- "unicode": "1F6F5",
+ "motor_scooter": {
+ "category": "travel",
+ "moji": "🛵",
+ "unicodeVersion": "9.0",
"digest": "e2dc7c981744a71f46858bd0858ff91af704ac06425ed80377bc3b119e57c872"
},
- {
- "name": "motorboat",
- "unicode": "1F6E5",
+ "motorboat": {
+ "category": "travel",
+ "moji": "🛥",
+ "unicodeVersion": "7.0",
"digest": "81c156643528c5a94a12d6d478e52a019f5a4e3eb58ee365cdd9d2361a7fdb01"
},
- {
- "name": "motorcycle",
- "unicode": "1F3CD",
+ "motorcycle": {
+ "category": "travel",
+ "moji": "🏍",
+ "unicodeVersion": "7.0",
"digest": "354aa8157732184ad50eff9330f7a8915309dc9b7893cc308226adb429311a62"
},
- {
- "name": "racing_motorcycle",
- "unicode": "1F3CD",
- "digest": "354aa8157732184ad50eff9330f7a8915309dc9b7893cc308226adb429311a62"
- },
- {
- "name": "motorway",
- "unicode": "1F6E3",
+ "motorway": {
+ "category": "travel",
+ "moji": "🛣",
+ "unicodeVersion": "7.0",
"digest": "148c3c13c7c4565453d16e504e0d4b8d007e4f2cad1ab56b1b51fefe39162d17"
},
- {
- "name": "mount_fuji",
- "unicode": "1F5FB",
+ "mount_fuji": {
+ "category": "travel",
+ "moji": "🗻",
+ "unicodeVersion": "6.0",
"digest": "f8093b9dba62b22c6c88f137be88b2fd3971c560714db15ec053cf697a3820bc"
},
- {
- "name": "mountain",
- "unicode": "26F0",
+ "mountain": {
+ "category": "travel",
+ "moji": "⛰",
+ "unicodeVersion": "5.2",
"digest": "07423804ad79da68f140948d29df193f5d5343b7b2c23758c086697c4d3a50da"
},
- {
- "name": "mountain_bicyclist",
- "unicode": "1F6B5",
+ "mountain_bicyclist": {
+ "category": "activity",
+ "moji": "🚵",
+ "unicodeVersion": "6.0",
"digest": "91084b6c887cb7e34f3d7ec30656ecb82c36cc987f53a6c83ccb4c6f7950f96a"
},
- {
- "name": "mountain_bicyclist_tone1",
- "unicode": "1F6B5-1F3FB",
+ "mountain_bicyclist_tone1": {
+ "category": "activity",
+ "moji": "🚵🏻",
+ "unicodeVersion": "8.0",
"digest": "5d57fcfad61bca26c3e8965eb57602a1993a3117ebdda0f24569af730310ab6e"
},
- {
- "name": "mountain_bicyclist_tone2",
- "unicode": "1F6B5-1F3FC",
+ "mountain_bicyclist_tone2": {
+ "category": "activity",
+ "moji": "🚵🏼",
+ "unicodeVersion": "8.0",
"digest": "c0da7fb85d99aa01a665f64063cd7e2d994f8a16d3f6fbf52df5d471e771a98a"
},
- {
- "name": "mountain_bicyclist_tone3",
- "unicode": "1F6B5-1F3FD",
+ "mountain_bicyclist_tone3": {
+ "category": "activity",
+ "moji": "🚵🏽",
+ "unicodeVersion": "8.0",
"digest": "b099e7ee84eae44ebc99023fa06bdf37ffa0d69767c7c0163a89f7ced2a26765"
},
- {
- "name": "mountain_bicyclist_tone4",
- "unicode": "1F6B5-1F3FE",
+ "mountain_bicyclist_tone4": {
+ "category": "activity",
+ "moji": "🚵🏾",
+ "unicodeVersion": "8.0",
"digest": "9d09f7b3899ea44e736f237a161ef8d5170dccfa162a872c59532ceaf65ee007"
},
- {
- "name": "mountain_bicyclist_tone5",
- "unicode": "1F6B5-1F3FF",
+ "mountain_bicyclist_tone5": {
+ "category": "activity",
+ "moji": "🚵🏿",
+ "unicodeVersion": "8.0",
"digest": "71e374981d955056748a60c6d1820b45e9688a156b55318b4ea54a3a67ca801c"
},
- {
- "name": "mountain_cableway",
- "unicode": "1F6A0",
+ "mountain_cableway": {
+ "category": "travel",
+ "moji": "🚠",
+ "unicodeVersion": "6.0",
"digest": "e261c3292758b1c0063c5a0d0c7f5c9803306d2265e08677027e1210506ced94"
},
- {
- "name": "mountain_railway",
- "unicode": "1F69E",
+ "mountain_railway": {
+ "category": "travel",
+ "moji": "🚞",
+ "unicodeVersion": "6.0",
"digest": "b0987f8f391b3cbc7a56b9b8945ebfca240e01d12f8fd163877ebebe51d6b277"
},
- {
- "name": "mountain_snow",
- "unicode": "1F3D4",
- "digest": "49aac2b851aa6f2bd2ca641efa8060f93e89395357f49d211658d46f5a2b0189"
- },
- {
- "name": "snow_capped_mountain",
- "unicode": "1F3D4",
+ "mountain_snow": {
+ "category": "travel",
+ "moji": "🏔",
+ "unicodeVersion": "7.0",
"digest": "49aac2b851aa6f2bd2ca641efa8060f93e89395357f49d211658d46f5a2b0189"
},
- {
- "name": "mouse",
- "unicode": "1F42D",
+ "mouse": {
+ "category": "nature",
+ "moji": "🐭",
+ "unicodeVersion": "6.0",
"digest": "007dd108507b45224f7a1fad3c1de6ecc75f38d71fc142744611eb13555f5eff"
},
- {
- "name": "mouse2",
- "unicode": "1F401",
+ "mouse2": {
+ "category": "nature",
+ "moji": "🐁",
+ "unicodeVersion": "6.0",
"digest": "f3ed37b639b7c16aae49502bd423f9fdeabaf15bc6f0f74063954b189e176b5d"
},
- {
- "name": "mouse_three_button",
- "unicode": "1F5B1",
+ "mouse_three_button": {
+ "category": "objects",
+ "moji": "🖱",
+ "unicodeVersion": "7.0",
"digest": "3724341ac5ad0d01027ef1575db64f1db7619f590ca6ada960d1f2c18dc7fc6a"
},
- {
- "name": "three_button_mouse",
- "unicode": "1F5B1",
- "digest": "3724341ac5ad0d01027ef1575db64f1db7619f590ca6ada960d1f2c18dc7fc6a"
- },
- {
- "name": "movie_camera",
- "unicode": "1F3A5",
+ "movie_camera": {
+ "category": "objects",
+ "moji": "🎥",
+ "unicodeVersion": "6.0",
"digest": "f7e285eda35b4431c07951e071643ddc34147cd76640e0d516fbfd11208346e9"
},
- {
- "name": "moyai",
- "unicode": "1F5FF",
+ "moyai": {
+ "category": "objects",
+ "moji": "🗿",
+ "unicodeVersion": "6.0",
"digest": "2c1d0662c95928936e6b9ab5a40c6110ff1cea5339f2803c7b63aabc76115afb"
},
- {
- "name": "mrs_claus",
- "unicode": "1F936",
- "digest": "1f72f586ca75bd7ebb4150cdcc8199a930c32fa4b81510cb8d200f1b3ddd4076"
- },
- {
- "name": "mother_christmas",
- "unicode": "1F936",
+ "mrs_claus": {
+ "category": "people",
+ "moji": "🤶",
+ "unicodeVersion": "9.0",
"digest": "1f72f586ca75bd7ebb4150cdcc8199a930c32fa4b81510cb8d200f1b3ddd4076"
},
- {
- "name": "mrs_claus_tone1",
- "unicode": "1F936-1F3FB",
+ "mrs_claus_tone1": {
+ "category": "people",
+ "moji": "🤶🏻",
+ "unicodeVersion": "9.0",
"digest": "244596919e0fed050203cf9e040899de323d7821235929f175852439927bd129"
},
- {
- "name": "mother_christmas_tone1",
- "unicode": "1F936-1F3FB",
- "digest": "244596919e0fed050203cf9e040899de323d7821235929f175852439927bd129"
- },
- {
- "name": "mrs_claus_tone2",
- "unicode": "1F936-1F3FC",
+ "mrs_claus_tone2": {
+ "category": "people",
+ "moji": "🤶🏼",
+ "unicodeVersion": "9.0",
"digest": "8cde96e8521f3a90262a7f5f8a2989a9590d9a02cda2c37e92335dc05975c18d"
},
- {
- "name": "mother_christmas_tone2",
- "unicode": "1F936-1F3FC",
- "digest": "8cde96e8521f3a90262a7f5f8a2989a9590d9a02cda2c37e92335dc05975c18d"
- },
- {
- "name": "mrs_claus_tone3",
- "unicode": "1F936-1F3FD",
+ "mrs_claus_tone3": {
+ "category": "people",
+ "moji": "🤶🏽",
+ "unicodeVersion": "9.0",
"digest": "c39cd4346d4581799dd0e9a6447c91a954a75747bf2682c8e4d79c3b0fcf7405"
},
- {
- "name": "mother_christmas_tone3",
- "unicode": "1F936-1F3FD",
- "digest": "c39cd4346d4581799dd0e9a6447c91a954a75747bf2682c8e4d79c3b0fcf7405"
- },
- {
- "name": "mrs_claus_tone4",
- "unicode": "1F936-1F3FE",
- "digest": "84c85cf54559ea2d78d196fee96149a249af4f959b78e223a0ec4fb72abdbcab"
- },
- {
- "name": "mother_christmas_tone4",
- "unicode": "1F936-1F3FE",
+ "mrs_claus_tone4": {
+ "category": "people",
+ "moji": "🤶🏾",
+ "unicodeVersion": "9.0",
"digest": "84c85cf54559ea2d78d196fee96149a249af4f959b78e223a0ec4fb72abdbcab"
},
- {
- "name": "mrs_claus_tone5",
- "unicode": "1F936-1F3FF",
+ "mrs_claus_tone5": {
+ "category": "people",
+ "moji": "🤶🏿",
+ "unicodeVersion": "9.0",
"digest": "ce26c0e0645713b17e7497d9f2d0484cc5477564dae99320cabf04d160d3b2ff"
},
- {
- "name": "mother_christmas_tone5",
- "unicode": "1F936-1F3FF",
- "digest": "ce26c0e0645713b17e7497d9f2d0484cc5477564dae99320cabf04d160d3b2ff"
- },
- {
- "name": "muscle",
- "unicode": "1F4AA",
+ "muscle": {
+ "category": "people",
+ "moji": "💪",
+ "unicodeVersion": "6.0",
"digest": "e4ce52757b2b7982e2516e0e8bf2e2253617cc9f3e6178f1887c61c9039461ba"
},
- {
- "name": "muscle_tone1",
- "unicode": "1F4AA-1F3FB",
+ "muscle_tone1": {
+ "category": "people",
+ "moji": "💪🏻",
+ "unicodeVersion": "8.0",
"digest": "4a2fa226a05bb847b62cdd163eb6c2d514d3c2330a727991cf550c0d32b0e818"
},
- {
- "name": "muscle_tone2",
- "unicode": "1F4AA-1F3FC",
+ "muscle_tone2": {
+ "category": "people",
+ "moji": "💪🏼",
+ "unicodeVersion": "8.0",
"digest": "a8d5ecce335c782ca5f5e55763c06cfefa1c16c24cd6602237cf125d4ff95e47"
},
- {
- "name": "muscle_tone3",
- "unicode": "1F4AA-1F3FD",
+ "muscle_tone3": {
+ "category": "people",
+ "moji": "💪🏽",
+ "unicodeVersion": "8.0",
"digest": "070354b443faec3969663b770545fc4cf5ec75148557b2b9d6fc82ab22b43bd1"
},
- {
- "name": "muscle_tone4",
- "unicode": "1F4AA-1F3FE",
+ "muscle_tone4": {
+ "category": "people",
+ "moji": "💪🏾",
+ "unicodeVersion": "8.0",
"digest": "8eafcdb6a607aeafa673c257df0d2a1b20f00fc0868d811babcbe784490a0dd3"
},
- {
- "name": "muscle_tone5",
- "unicode": "1F4AA-1F3FF",
+ "muscle_tone5": {
+ "category": "people",
+ "moji": "💪🏿",
+ "unicodeVersion": "8.0",
"digest": "85a1e2b5c89907694240e9c5b9d876a741fa7ba38918c5718273e289cbc40efe"
},
- {
- "name": "mushroom",
- "unicode": "1F344",
+ "mushroom": {
+ "category": "nature",
+ "moji": "🍄",
+ "unicodeVersion": "6.0",
"digest": "aaca8cf7c5cfa4487b5fef365a231f98be4bbf041197fc022161bcc8ce6f57c8"
},
- {
- "name": "musical_keyboard",
- "unicode": "1F3B9",
+ "musical_keyboard": {
+ "category": "activity",
+ "moji": "🎹",
+ "unicodeVersion": "6.0",
"digest": "fb0a726728900377d76d94aac9c94dce29107e8e3f1dcb0599d95bce7169b492"
},
- {
- "name": "musical_note",
- "unicode": "1F3B5",
+ "musical_note": {
+ "category": "symbols",
+ "moji": "🎵",
+ "unicodeVersion": "6.0",
"digest": "41288e79b4070bb980281d0e0d1c14d8b144b4aedb2eaadb9f2bebcb4ef892b4"
},
- {
- "name": "musical_score",
- "unicode": "1F3BC",
+ "musical_score": {
+ "category": "activity",
+ "moji": "🎼",
+ "unicodeVersion": "6.0",
"digest": "f0f91b9fa4a2bff7a5a1a11afa6f31cfe7e5fa8b0d6f3cce904b781a28ed0277"
},
- {
- "name": "mute",
- "unicode": "1F507",
+ "mute": {
+ "category": "symbols",
+ "moji": "🔇",
+ "unicodeVersion": "6.0",
"digest": "def277da49d744b55c7cdde269a15aa05315898f615e721ee7e9205d7b8030d6"
},
- {
- "name": "nail_care",
- "unicode": "1F485",
+ "nail_care": {
+ "category": "people",
+ "moji": "💅",
+ "unicodeVersion": "6.0",
"digest": "48b33b1dbbd25b4f34ab2ca07bb99ddaaaa741990142c5623310f76b78c076f9"
},
- {
- "name": "nail_care_tone1",
- "unicode": "1F485-1F3FB",
+ "nail_care_tone1": {
+ "category": "people",
+ "moji": "💅🏻",
+ "unicodeVersion": "8.0",
"digest": "a9ac92a34f407e7dd7c71377e6275e66657f7f42e4b911c540d1a66a02d92ac5"
},
- {
- "name": "nail_care_tone2",
- "unicode": "1F485-1F3FC",
+ "nail_care_tone2": {
+ "category": "people",
+ "moji": "💅🏼",
+ "unicodeVersion": "8.0",
"digest": "f295ec85980aaa75818fad619c3d25042146ecbbf361db9e9bb96e7bc202bc73"
},
- {
- "name": "nail_care_tone3",
- "unicode": "1F485-1F3FD",
+ "nail_care_tone3": {
+ "category": "people",
+ "moji": "💅🏽",
+ "unicodeVersion": "8.0",
"digest": "02ec373052a250977298bae85262177910126cc10de9480f1afa328ac2f65a95"
},
- {
- "name": "nail_care_tone4",
- "unicode": "1F485-1F3FE",
+ "nail_care_tone4": {
+ "category": "people",
+ "moji": "💅🏾",
+ "unicodeVersion": "8.0",
"digest": "f3d95390ab59caedfda66122bbd0acf3aabedc142fc48352d68900766a7e6f5c"
},
- {
- "name": "nail_care_tone5",
- "unicode": "1F485-1F3FF",
+ "nail_care_tone5": {
+ "category": "people",
+ "moji": "💅🏿",
+ "unicodeVersion": "8.0",
"digest": "009423c97f2aafd24fb8c7c485c58b30bbf9ae6797cc14b80d472b207327b518"
},
- {
- "name": "name_badge",
- "unicode": "1F4DB",
+ "name_badge": {
+ "category": "symbols",
+ "moji": "📛",
+ "unicodeVersion": "6.0",
"digest": "f9f6a4895ff0be8fb2ccc7ad195b94e9650f742f66ead999e90724cfb77af628"
},
- {
- "name": "nauseated_face",
- "unicode": "1F922",
- "digest": "f8471cf4720948d8246ec9d30e29783e819f90e3cfe8b1ba628671a1aad1a91c"
- },
- {
- "name": "sick",
- "unicode": "1F922",
+ "nauseated_face": {
+ "category": "people",
+ "moji": "🤢",
+ "unicodeVersion": "9.0",
"digest": "f8471cf4720948d8246ec9d30e29783e819f90e3cfe8b1ba628671a1aad1a91c"
},
- {
- "name": "necktie",
- "unicode": "1F454",
+ "necktie": {
+ "category": "people",
+ "moji": "👔",
+ "unicodeVersion": "6.0",
"digest": "01bb18dc8bfe787daa9613b5d09988cd5a065449ef906099ce3cb308c8a7da68"
},
- {
- "name": "negative_squared_cross_mark",
- "unicode": "274E",
+ "negative_squared_cross_mark": {
+ "category": "symbols",
+ "moji": "❎",
+ "unicodeVersion": "6.0",
"digest": "1cdaf4abc9adafa089c91c2e33a24e9e647aea0f857e767941a899a16ec53b74"
},
- {
- "name": "nerd",
- "unicode": "1F913",
- "digest": "9e5f3c93db25cf1d0f9d6e6bd2993161afec6c30573ba3fe85e13b8c84483d66"
- },
- {
- "name": "nerd_face",
- "unicode": "1F913",
+ "nerd": {
+ "category": "people",
+ "moji": "🤓",
+ "unicodeVersion": "8.0",
"digest": "9e5f3c93db25cf1d0f9d6e6bd2993161afec6c30573ba3fe85e13b8c84483d66"
},
- {
- "name": "neutral_face",
- "unicode": "1F610",
+ "neutral_face": {
+ "category": "people",
+ "moji": "😐",
+ "unicodeVersion": "6.0",
"digest": "7449430a60619956573e9dc80834045296f2b99853737b6c7794c785ff53d64e"
},
- {
- "name": "new",
- "unicode": "1F195",
+ "new": {
+ "category": "symbols",
+ "moji": "🆕",
+ "unicodeVersion": "6.0",
"digest": "e20bc3e9f40726afd0cfb7268d02f1e1a07343364fd08b252d59f38de067bf06"
},
- {
- "name": "new_moon",
- "unicode": "1F311",
+ "new_moon": {
+ "category": "nature",
+ "moji": "🌑",
+ "unicodeVersion": "6.0",
"digest": "dbfc5dcae34b45f15ff767e297cba3a12cb83f3b542db8cfc8dbd9669e0df46c"
},
- {
- "name": "new_moon_with_face",
- "unicode": "1F31A",
+ "new_moon_with_face": {
+ "category": "nature",
+ "moji": "🌚",
+ "unicodeVersion": "6.0",
"digest": "c66d347d2222ac8d77d323a07699aff6b168328648db4f885b1ed0e2831fd59b"
},
- {
- "name": "newspaper",
- "unicode": "1F4F0",
+ "newspaper": {
+ "category": "objects",
+ "moji": "📰",
+ "unicodeVersion": "6.0",
"digest": "c05e986d9cdac11afa30c6a21a72572ddf50fc64e87ae0c4e0ad57ffe70acc5c"
},
- {
- "name": "newspaper2",
- "unicode": "1F5DE",
- "digest": "63db7bcf51effc73e5124392740736383774a4bcfbc1156cf55599504760883d"
- },
- {
- "name": "rolled_up_newspaper",
- "unicode": "1F5DE",
+ "newspaper2": {
+ "category": "objects",
+ "moji": "🗞",
+ "unicodeVersion": "7.0",
"digest": "63db7bcf51effc73e5124392740736383774a4bcfbc1156cf55599504760883d"
},
- {
- "name": "ng",
- "unicode": "1F196",
+ "ng": {
+ "category": "symbols",
+ "moji": "🆖",
+ "unicodeVersion": "6.0",
"digest": "34d5a11c70f48ea719e602908534f446b192622e775d4160f0e1ec52c342a35c"
},
- {
- "name": "night_with_stars",
- "unicode": "1F303",
+ "night_with_stars": {
+ "category": "travel",
+ "moji": "🌃",
+ "unicodeVersion": "6.0",
"digest": "39d9c079be80ee6ce1667531be528a2aa7f8bd46c7b6c2a6ee279d9a207c84a4"
},
- {
- "name": "nine",
- "unicode": "0039-20E3",
+ "nine": {
+ "category": "symbols",
+ "moji": "9️⃣",
+ "unicodeVersion": "3.0",
"digest": "8bb40750eda8506ef877c9a3b8e2039d26f20eef345742f635740574a7e8daa6"
},
- {
- "name": "no_bell",
- "unicode": "1F515",
+ "no_bell": {
+ "category": "symbols",
+ "moji": "🔕",
+ "unicodeVersion": "6.0",
"digest": "6542a9a5656c79c153f8c37f12d48f677c89b02ed0989ae37fa5e51ce6895422"
},
- {
- "name": "no_bicycles",
- "unicode": "1F6B3",
+ "no_bicycles": {
+ "category": "symbols",
+ "moji": "🚳",
+ "unicodeVersion": "6.0",
"digest": "af71c183545da2ff4c05609f9d572edb64b63ccba7c6a4b208d271558aa92b0a"
},
- {
- "name": "no_entry",
- "unicode": "26D4",
+ "no_entry": {
+ "category": "symbols",
+ "moji": "⛔",
+ "unicodeVersion": "5.2",
"digest": "dc0bac1ed9ab8e9af143f0fce5043fe68f7f46bd80856cdec95d20c3999b637d"
},
- {
- "name": "no_entry_sign",
- "unicode": "1F6AB",
+ "no_entry_sign": {
+ "category": "symbols",
+ "moji": "🚫",
+ "unicodeVersion": "6.0",
"digest": "2c1fceef23b62effca68e0e087b8f020125d25b98d61492b1540055d1914fdc3"
},
- {
- "name": "no_good",
- "unicode": "1F645",
+ "no_good": {
+ "category": "people",
+ "moji": "🙅",
+ "unicodeVersion": "6.0",
"digest": "6eb970b104389be5d18657d7c04be5149958c26855c52ea68574af852c5f85c4"
},
- {
- "name": "no_good_tone1",
- "unicode": "1F645-1F3FB",
+ "no_good_tone1": {
+ "category": "people",
+ "moji": "🙅🏻",
+ "unicodeVersion": "8.0",
"digest": "c20a24a1e536240b4dcf90ecb530796de621d7ba1fb9e3fa0f849d048c509c03"
},
- {
- "name": "no_good_tone2",
- "unicode": "1F645-1F3FC",
+ "no_good_tone2": {
+ "category": "people",
+ "moji": "🙅🏼",
+ "unicodeVersion": "8.0",
"digest": "f31a4628c1f2e6a39288fda8eb19c9ec89983e3726e17a09384d9ecc13ef0b4c"
},
- {
- "name": "no_good_tone3",
- "unicode": "1F645-1F3FD",
+ "no_good_tone3": {
+ "category": "people",
+ "moji": "🙅🏽",
+ "unicodeVersion": "8.0",
"digest": "959dec1bfdaf37b20a86ab2bcbdbacd3179c87b163042377d966eab47564c0fb"
},
- {
- "name": "no_good_tone4",
- "unicode": "1F645-1F3FE",
+ "no_good_tone4": {
+ "category": "people",
+ "moji": "🙅🏾",
+ "unicodeVersion": "8.0",
"digest": "efd931f0080adf2e04129c83a8b24fda0ae7a9fa7c4b463686c0b99023620db8"
},
- {
- "name": "no_good_tone5",
- "unicode": "1F645-1F3FF",
+ "no_good_tone5": {
+ "category": "people",
+ "moji": "🙅🏿",
+ "unicodeVersion": "8.0",
"digest": "f35df2b26af9baef47c1f8cc97a1b28a58aa7fcb2a13fdac7b2d9189f1e40105"
},
- {
- "name": "no_mobile_phones",
- "unicode": "1F4F5",
+ "no_mobile_phones": {
+ "category": "symbols",
+ "moji": "📵",
+ "unicodeVersion": "6.0",
"digest": "a472decd6ac7f9777961c09e00458746b2c04965585e3bee4556be3968e55bcd"
},
- {
- "name": "no_mouth",
- "unicode": "1F636",
+ "no_mouth": {
+ "category": "people",
+ "moji": "😶",
+ "unicodeVersion": "6.0",
"digest": "72dda8b1e3ad4b05d9b095f9bd05e95d7ba013906c68914976a4554e8edf5866"
},
- {
- "name": "no_pedestrians",
- "unicode": "1F6B7",
+ "no_pedestrians": {
+ "category": "symbols",
+ "moji": "🚷",
+ "unicodeVersion": "6.0",
"digest": "062b4a71b338fe09775e465bfba8ac04efbb3640330e8cabe88f3af62b0f4225"
},
- {
- "name": "no_smoking",
- "unicode": "1F6AD",
+ "no_smoking": {
+ "category": "symbols",
+ "moji": "🚭",
+ "unicodeVersion": "6.0",
"digest": "ae2ebb331f79f6074091c0ee9cd69fce16d5e12a131d18973fc05520097e14ee"
},
- {
- "name": "non-potable_water",
- "unicode": "1F6B1",
+ "non-potable_water": {
+ "category": "symbols",
+ "moji": "🚱",
+ "unicodeVersion": "6.0",
"digest": "32eba0a99b498133c2e4450036f768d3dccaaf5b50adc9ad988757adc777a6a1"
},
- {
- "name": "nose",
- "unicode": "1F443",
+ "nose": {
+ "category": "people",
+ "moji": "👃",
+ "unicodeVersion": "6.0",
"digest": "9f800e24658ea3cebe1144d5d808cf13a88261f1a7f1f81a10d03b3d9d00e541"
},
- {
- "name": "nose_tone1",
- "unicode": "1F443-1F3FB",
+ "nose_tone1": {
+ "category": "people",
+ "moji": "👃🏻",
+ "unicodeVersion": "8.0",
"digest": "a2d0af22284b1d264eb780943b8360f463996a5c9c9584b8473edf8d442d9173"
},
- {
- "name": "nose_tone2",
- "unicode": "1F443-1F3FC",
+ "nose_tone2": {
+ "category": "people",
+ "moji": "👃🏼",
+ "unicodeVersion": "8.0",
"digest": "244dcaa8540024cf521f29f36bd48f933bf82f4833e35e6fa0abf113022038f3"
},
- {
- "name": "nose_tone3",
- "unicode": "1F443-1F3FD",
+ "nose_tone3": {
+ "category": "people",
+ "moji": "👃🏽",
+ "unicodeVersion": "8.0",
"digest": "c935b64866f0d49da52035aa09f36ff56d238eb7f5b92205386451056e8ea74f"
},
- {
- "name": "nose_tone4",
- "unicode": "1F443-1F3FE",
+ "nose_tone4": {
+ "category": "people",
+ "moji": "👃🏾",
+ "unicodeVersion": "8.0",
"digest": "a87e95fd9319c49e66b6dea0e57319d0ed9921b8d94df037767bf3d5dc7c94f3"
},
- {
- "name": "nose_tone5",
- "unicode": "1F443-1F3FF",
+ "nose_tone5": {
+ "category": "people",
+ "moji": "👃🏿",
+ "unicodeVersion": "8.0",
"digest": "1e0f9842e0f8ad5805eabd3f35a6038b7a2e49d566a1f5c17271f9cdf467ca60"
},
- {
- "name": "notebook",
- "unicode": "1F4D3",
+ "notebook": {
+ "category": "objects",
+ "moji": "📓",
+ "unicodeVersion": "6.0",
"digest": "fc679d3728f86073d1607a926885dd8b0261132f5c4a0322f1e46ea9f95c8cb8"
},
- {
- "name": "notebook_with_decorative_cover",
- "unicode": "1F4D4",
+ "notebook_with_decorative_cover": {
+ "category": "objects",
+ "moji": "📔",
+ "unicodeVersion": "6.0",
"digest": "d822eda4b49cbfa399b36f134c1a0b8dcfdd27ed89f12c50bc18f6f0a9aa56ef"
},
- {
- "name": "notepad_spiral",
- "unicode": "1F5D2",
+ "notepad_spiral": {
+ "category": "objects",
+ "moji": "🗒",
+ "unicodeVersion": "7.0",
"digest": "c6a8e16aa62474cef13e5659fddb4afc57e3f79635e32e6020edbee2b5b50f18"
},
- {
- "name": "spiral_note_pad",
- "unicode": "1F5D2",
- "digest": "c6a8e16aa62474cef13e5659fddb4afc57e3f79635e32e6020edbee2b5b50f18"
- },
- {
- "name": "notes",
- "unicode": "1F3B6",
+ "notes": {
+ "category": "symbols",
+ "moji": "🎶",
+ "unicodeVersion": "6.0",
"digest": "98467e0adc134d45676ef1c6c459e5853a9db50c8a6e91b6aec7d449aa737f48"
},
- {
- "name": "nut_and_bolt",
- "unicode": "1F529",
+ "nut_and_bolt": {
+ "category": "objects",
+ "moji": "🔩",
+ "unicodeVersion": "6.0",
"digest": "a77bd72f29a7302195dcec240174b15586de79e3204258e3fb401a6ea90563b3"
},
- {
- "name": "o",
- "unicode": "2B55",
+ "o": {
+ "category": "symbols",
+ "moji": "⭕",
+ "unicodeVersion": "5.2",
"digest": "2387e5fd9ae4c2972d40298d32319b8fa55c50dbfc1c04c5c36088213e6951dd"
},
- {
- "name": "o2",
- "unicode": "1F17E",
+ "o2": {
+ "category": "symbols",
+ "moji": "🅾",
+ "unicodeVersion": "6.0",
"digest": "6a9ccb0bf394e4d05ffda19327cee18f7b9ed80367fc7f41c93da9bb7efab0bf"
},
- {
- "name": "ocean",
- "unicode": "1F30A",
+ "ocean": {
+ "category": "nature",
+ "moji": "🌊",
+ "unicodeVersion": "6.0",
"digest": "1a9ca9848d4fb75852addfc10bf84eccf7caa5339714b90e3de4cb6f2518465e"
},
- {
- "name": "octagonal_sign",
- "unicode": "1F6D1",
- "digest": "9f6927048e1f9da57f89d1ae1eb86fa4ab7abdbabca756a738a799e948d0b3f9"
- },
- {
- "name": "stop_sign",
- "unicode": "1F6D1",
+ "octagonal_sign": {
+ "category": "symbols",
+ "moji": "🛑",
+ "unicodeVersion": "9.0",
"digest": "9f6927048e1f9da57f89d1ae1eb86fa4ab7abdbabca756a738a799e948d0b3f9"
},
- {
- "name": "octopus",
- "unicode": "1F419",
+ "octopus": {
+ "category": "nature",
+ "moji": "🐙",
+ "unicodeVersion": "6.0",
"digest": "0fcc65c12f4b29ea75a8c4823d20838a7e6db6978fdcb536943072aa1460bc59"
},
- {
- "name": "oden",
- "unicode": "1F362",
+ "oden": {
+ "category": "food",
+ "moji": "🍢",
+ "unicodeVersion": "6.0",
"digest": "089974cb13a0bef6a245fc73029c5ed5153fd4caae0177b835f779e32200b8aa"
},
- {
- "name": "office",
- "unicode": "1F3E2",
+ "office": {
+ "category": "travel",
+ "moji": "🏢",
+ "unicodeVersion": "6.0",
"digest": "3633a2e91036362e273eef4e0cfbdbbb4cb1208afe2cfa110ebef7b78109a66f"
},
- {
- "name": "oil",
- "unicode": "1F6E2",
- "digest": "00b94d33bcc9b9e8a5d4bd6e7f7e2fced9497ce05919edd5e58eafbc011c2caa"
- },
- {
- "name": "oil_drum",
- "unicode": "1F6E2",
+ "oil": {
+ "category": "objects",
+ "moji": "🛢",
+ "unicodeVersion": "7.0",
"digest": "00b94d33bcc9b9e8a5d4bd6e7f7e2fced9497ce05919edd5e58eafbc011c2caa"
},
- {
- "name": "ok",
- "unicode": "1F197",
+ "ok": {
+ "category": "symbols",
+ "moji": "🆗",
+ "unicodeVersion": "6.0",
"digest": "5f320f9b96e98a2f17ebe240daff9b9fd2ae0727cd6c8e4633b1744356e89365"
},
- {
- "name": "ok_hand",
- "unicode": "1F44C",
+ "ok_hand": {
+ "category": "people",
+ "moji": "👌",
+ "unicodeVersion": "6.0",
"digest": "d63002dce3cc3655b67b8765b7c28d370edba0e3758b2329b60e0e61c4d8e78d"
},
- {
- "name": "ok_hand_tone1",
- "unicode": "1F44C-1F3FB",
+ "ok_hand_tone1": {
+ "category": "people",
+ "moji": "👌🏻",
+ "unicodeVersion": "8.0",
"digest": "ef1508efcf483b09807554fe0e451c2948224f9deb85463e8e0dad6875b54012"
},
- {
- "name": "ok_hand_tone2",
- "unicode": "1F44C-1F3FC",
+ "ok_hand_tone2": {
+ "category": "people",
+ "moji": "👌🏼",
+ "unicodeVersion": "8.0",
"digest": "1215a101a082fd8e04c5d2f7e3c59d0f480cb0bedd79aeab5d36676bfe760088"
},
- {
- "name": "ok_hand_tone3",
- "unicode": "1F44C-1F3FD",
+ "ok_hand_tone3": {
+ "category": "people",
+ "moji": "👌🏽",
+ "unicodeVersion": "8.0",
"digest": "6fe0ed9fb42e86bb2bed4cb37b2acacacda1471fb1ee845ad55e54fb0897fbf4"
},
- {
- "name": "ok_hand_tone4",
- "unicode": "1F44C-1F3FE",
+ "ok_hand_tone4": {
+ "category": "people",
+ "moji": "👌🏾",
+ "unicodeVersion": "8.0",
"digest": "bfb9041c49d95e901a667264abaf9b398f6c4aa8b52bf5191c122db20c13c020"
},
- {
- "name": "ok_hand_tone5",
- "unicode": "1F44C-1F3FF",
+ "ok_hand_tone5": {
+ "category": "people",
+ "moji": "👌🏿",
+ "unicodeVersion": "8.0",
"digest": "1c218dc04d698da2cbdd7bea1ca3f845f9b386e967b7247c52f4b0f6ec8f5320"
},
- {
- "name": "ok_woman",
- "unicode": "1F646",
+ "ok_woman": {
+ "category": "people",
+ "moji": "🙆",
+ "unicodeVersion": "6.0",
"digest": "3f8bd4ce2c4497155d697e5a71ebdc9339f65633d07fa9a7903e1bd76cfa4ba1"
},
- {
- "name": "ok_woman_tone1",
- "unicode": "1F646-1F3FB",
+ "ok_woman_tone1": {
+ "category": "people",
+ "moji": "🙆🏻",
+ "unicodeVersion": "8.0",
"digest": "1660cd904ccd2ecdc6f4ba00527f7d4ec8c33f3c6183344616f97badae4c3730"
},
- {
- "name": "ok_woman_tone2",
- "unicode": "1F646-1F3FC",
+ "ok_woman_tone2": {
+ "category": "people",
+ "moji": "🙆🏼",
+ "unicodeVersion": "8.0",
"digest": "7ba5fddd1e141424fac6778894dfc5af28e125839c58937c69496f99cd2c4002"
},
- {
- "name": "ok_woman_tone3",
- "unicode": "1F646-1F3FD",
+ "ok_woman_tone3": {
+ "category": "people",
+ "moji": "🙆🏽",
+ "unicodeVersion": "8.0",
"digest": "1d972b8377c52f598406f59ab1e5be41aaf8f027e1fefba3deda66312ccd6a9b"
},
- {
- "name": "ok_woman_tone4",
- "unicode": "1F646-1F3FE",
+ "ok_woman_tone4": {
+ "category": "people",
+ "moji": "🙆🏾",
+ "unicodeVersion": "8.0",
"digest": "a176328d8f53503aa743448968afd21d72ffd3510555526a3fb38d6b30ee7c15"
},
- {
- "name": "ok_woman_tone5",
- "unicode": "1F646-1F3FF",
+ "ok_woman_tone5": {
+ "category": "people",
+ "moji": "🙆🏿",
+ "unicodeVersion": "8.0",
"digest": "13cfc1b589c57e81f768ee07a14b737cafc71407a7eb0956728b2ec4b1df14c4"
},
- {
- "name": "older_man",
- "unicode": "1F474",
+ "older_man": {
+ "category": "people",
+ "moji": "👴",
+ "unicodeVersion": "6.0",
"digest": "4c0462b199bf26181c9e4d2d4cb878a32b0294566941212efc67362d0645f948"
},
- {
- "name": "older_man_tone1",
- "unicode": "1F474-1F3FB",
+ "older_man_tone1": {
+ "category": "people",
+ "moji": "👴🏻",
+ "unicodeVersion": "8.0",
"digest": "99baa083f78cb01166d0a928d0b53682be14be04c29fc17bef14aac1a73a61e6"
},
- {
- "name": "older_man_tone2",
- "unicode": "1F474-1F3FC",
+ "older_man_tone2": {
+ "category": "people",
+ "moji": "👴🏼",
+ "unicodeVersion": "8.0",
"digest": "5b4ce713e8820ba517fe92c25f3b93e6a6bf3704d1f982c461d5f31fc02b9d3d"
},
- {
- "name": "older_man_tone3",
- "unicode": "1F474-1F3FD",
+ "older_man_tone3": {
+ "category": "people",
+ "moji": "👴🏽",
+ "unicodeVersion": "8.0",
"digest": "0eff72b3226c3a703c635798ee84129a695c896fa011fe1adbc105312eecc083"
},
- {
- "name": "older_man_tone4",
- "unicode": "1F474-1F3FE",
+ "older_man_tone4": {
+ "category": "people",
+ "moji": "👴🏾",
+ "unicodeVersion": "8.0",
"digest": "ad9ba82b0c5d3b171b0639ee4265370dbddff5e0eeb70729db122659bb8c8f84"
},
- {
- "name": "older_man_tone5",
- "unicode": "1F474-1F3FF",
+ "older_man_tone5": {
+ "category": "people",
+ "moji": "👴🏿",
+ "unicodeVersion": "8.0",
"digest": "5eb0a7467cc40e75752e11fd5126b275863dc037557a0d0d3b24b681e00c2386"
},
- {
- "name": "older_woman",
- "unicode": "1F475",
- "digest": "c261fdf3b01e0c7d949e177144531add5895197fbadf1acbba8eb17d18766bf6"
- },
- {
- "name": "grandma",
- "unicode": "1F475",
+ "older_woman": {
+ "category": "people",
+ "moji": "👵",
+ "unicodeVersion": "6.0",
"digest": "c261fdf3b01e0c7d949e177144531add5895197fbadf1acbba8eb17d18766bf6"
},
- {
- "name": "older_woman_tone1",
- "unicode": "1F475-1F3FB",
- "digest": "1f2bb9e42270a58194498254da27ac2b7a50edaa771b90ee194ccd6d24660c62"
- },
- {
- "name": "grandma_tone1",
- "unicode": "1F475-1F3FB",
+ "older_woman_tone1": {
+ "category": "people",
+ "moji": "👵🏻",
+ "unicodeVersion": "8.0",
"digest": "1f2bb9e42270a58194498254da27ac2b7a50edaa771b90ee194ccd6d24660c62"
},
- {
- "name": "older_woman_tone2",
- "unicode": "1F475-1F3FC",
- "digest": "2e28198e9b7ac08c55980677ed66655fd899e157f14184958bebd87fcd714940"
- },
- {
- "name": "grandma_tone2",
- "unicode": "1F475-1F3FC",
+ "older_woman_tone2": {
+ "category": "people",
+ "moji": "👵🏼",
+ "unicodeVersion": "8.0",
"digest": "2e28198e9b7ac08c55980677ed66655fd899e157f14184958bebd87fcd714940"
},
- {
- "name": "older_woman_tone3",
- "unicode": "1F475-1F3FD",
- "digest": "c968be0170f7e0c65d4f796337034cfb1daba897884da6fad85635ab5b6edf67"
- },
- {
- "name": "grandma_tone3",
- "unicode": "1F475-1F3FD",
+ "older_woman_tone3": {
+ "category": "people",
+ "moji": "👵🏽",
+ "unicodeVersion": "8.0",
"digest": "c968be0170f7e0c65d4f796337034cfb1daba897884da6fad85635ab5b6edf67"
},
- {
- "name": "older_woman_tone4",
- "unicode": "1F475-1F3FE",
- "digest": "3596a6fa9a643bf79255afcd29657b03850df8499db9669b92ce013af908af44"
- },
- {
- "name": "grandma_tone4",
- "unicode": "1F475-1F3FE",
+ "older_woman_tone4": {
+ "category": "people",
+ "moji": "👵🏾",
+ "unicodeVersion": "8.0",
"digest": "3596a6fa9a643bf79255afcd29657b03850df8499db9669b92ce013af908af44"
},
- {
- "name": "older_woman_tone5",
- "unicode": "1F475-1F3FF",
+ "older_woman_tone5": {
+ "category": "people",
+ "moji": "👵🏿",
+ "unicodeVersion": "8.0",
"digest": "c8998cb3dbd15e22bd1d6dad613d109ce371d9ffca3657e1a8afe5aeb30c1275"
},
- {
- "name": "grandma_tone5",
- "unicode": "1F475-1F3FF",
- "digest": "c8998cb3dbd15e22bd1d6dad613d109ce371d9ffca3657e1a8afe5aeb30c1275"
- },
- {
- "name": "om_symbol",
- "unicode": "1F549",
+ "om_symbol": {
+ "category": "symbols",
+ "moji": "🕉",
+ "unicodeVersion": "7.0",
"digest": "5ead73bea546ba9ba6da522f7280cc289c75ff5467742bdba31f92d0e1b3f4e6"
},
- {
- "name": "on",
- "unicode": "1F51B",
+ "on": {
+ "category": "symbols",
+ "moji": "🔛",
+ "unicodeVersion": "6.0",
"digest": "9cc61a6b31a30c32dab594191bf23f91e341c4105384ab22158a6d43e6364631"
},
- {
- "name": "oncoming_automobile",
- "unicode": "1F698",
+ "oncoming_automobile": {
+ "category": "travel",
+ "moji": "🚘",
+ "unicodeVersion": "6.0",
"digest": "557c9cacdc3f95215d4f7a6f097a2baa7c007cb9c519492a6717077af4ca6b56"
},
- {
- "name": "oncoming_bus",
- "unicode": "1F68D",
+ "oncoming_bus": {
+ "category": "travel",
+ "moji": "🚍",
+ "unicodeVersion": "6.0",
"digest": "059f28ce6bfb337e107db5982cbd2004844450ef20b4a54b9ca3cb738360ab05"
},
- {
- "name": "oncoming_police_car",
- "unicode": "1F694",
+ "oncoming_police_car": {
+ "category": "travel",
+ "moji": "🚔",
+ "unicodeVersion": "6.0",
"digest": "aee79306a0d129cfc1980f58db80391eb46d2d7d5f814bf431414dc7680cab72"
},
- {
- "name": "oncoming_taxi",
- "unicode": "1F696",
+ "oncoming_taxi": {
+ "category": "travel",
+ "moji": "🚖",
+ "unicodeVersion": "6.0",
"digest": "84351489fc86d980b8d3eb9ec4e81120fe700b3ac01346daebe2b7aeb9607a55"
},
- {
- "name": "one",
- "unicode": "0031-20E3",
+ "one": {
+ "category": "symbols",
+ "moji": "1️⃣",
+ "unicodeVersion": "3.0",
"digest": "d5d3fff04e68a114ff6464ee06fc831f3f381713045165f62a88d5e8215c195b"
},
- {
- "name": "open_file_folder",
- "unicode": "1F4C2",
+ "open_file_folder": {
+ "category": "objects",
+ "moji": "📂",
+ "unicodeVersion": "6.0",
"digest": "96cfc322ee4903ae8cec07604811742245fd7d14f00bb70276d39d29c48bed28"
},
- {
- "name": "open_hands",
- "unicode": "1F450",
+ "open_hands": {
+ "category": "people",
+ "moji": "👐",
+ "unicodeVersion": "6.0",
"digest": "a6c131da2040b48103cea14f280e728675da50fa448d2b3f3438fcbb5bf5596a"
},
- {
- "name": "open_hands_tone1",
- "unicode": "1F450-1F3FB",
+ "open_hands_tone1": {
+ "category": "people",
+ "moji": "👐🏻",
+ "unicodeVersion": "8.0",
"digest": "867128dff2fa9b860c10c6b792f989f0c057928783696062378f834c0ef89d85"
},
- {
- "name": "open_hands_tone2",
- "unicode": "1F450-1F3FC",
+ "open_hands_tone2": {
+ "category": "people",
+ "moji": "👐🏼",
+ "unicodeVersion": "8.0",
"digest": "487ff2745b03d49bb3b1d0acd86ba530fd8cc3f467ca3fa504f88f0ef1cbbc01"
},
- {
- "name": "open_hands_tone3",
- "unicode": "1F450-1F3FD",
+ "open_hands_tone3": {
+ "category": "people",
+ "moji": "👐🏽",
+ "unicodeVersion": "8.0",
"digest": "cb8cddc8b8661f874ac9478289d16cc41406b947bb87f3363df518a588a53e16"
},
- {
- "name": "open_hands_tone4",
- "unicode": "1F450-1F3FE",
+ "open_hands_tone4": {
+ "category": "people",
+ "moji": "👐🏾",
+ "unicodeVersion": "8.0",
"digest": "17dcc2c07230846a769f3c79ce618a757c88b9b58c95c6c5b2d7f968814d447d"
},
- {
- "name": "open_hands_tone5",
- "unicode": "1F450-1F3FF",
+ "open_hands_tone5": {
+ "category": "people",
+ "moji": "👐🏿",
+ "unicodeVersion": "8.0",
"digest": "36b2493d67c84cea4f3f85a3088c6abcfd35cf99f7aeaeedfafa420ee878e3d2"
},
- {
- "name": "open_mouth",
- "unicode": "1F62E",
+ "open_mouth": {
+ "category": "people",
+ "moji": "😮",
+ "unicodeVersion": "6.1",
"digest": "1906c5100ae0c8326ca5c4f9422976958a38dadd8d77724d68538a25d9623035"
},
- {
- "name": "ophiuchus",
- "unicode": "26CE",
+ "ophiuchus": {
+ "category": "symbols",
+ "moji": "⛎",
+ "unicodeVersion": "6.0",
"digest": "6112e2a1656b1cb8bd9a8b0dfa6cbf66d30cae671710a9ef75c821de344aab2b"
},
- {
- "name": "orange_book",
- "unicode": "1F4D9",
+ "orange_book": {
+ "category": "objects",
+ "moji": "📙",
+ "unicodeVersion": "6.0",
"digest": "41141b08d2beceded21a94795431603c47fd7d42a3a472a2aa8b2bb25fa87ebf"
},
- {
- "name": "orthodox_cross",
- "unicode": "2626",
+ "orthodox_cross": {
+ "category": "symbols",
+ "moji": "☦",
+ "unicodeVersion": "1.1",
"digest": "c16372102f0169dd6d32eb2b27a633aaee74e4e0fddcf723c15ad97f9dc6075c"
},
- {
- "name": "outbox_tray",
- "unicode": "1F4E4",
+ "outbox_tray": {
+ "category": "objects",
+ "moji": "📤",
+ "unicodeVersion": "6.0",
"digest": "e47cb481a0ffcb39996f32fd313e19b362a91d8dda15ffca48ac23a3b5bb5baf"
},
- {
- "name": "owl",
- "unicode": "1F989",
+ "owl": {
+ "category": "nature",
+ "moji": "🦉",
+ "unicodeVersion": "9.0",
"digest": "f62ec1ad23ad9038966eea8d8b79660ac212f291af2e89bcdb0fdc683caf41e5"
},
- {
- "name": "ox",
- "unicode": "1F402",
+ "ox": {
+ "category": "nature",
+ "moji": "🐂",
+ "unicodeVersion": "6.0",
"digest": "d13bc60552190bb9936bf32d681bdc742439b702a09cfc62137ea09a98624aed"
},
- {
- "name": "package",
- "unicode": "1F4E6",
+ "package": {
+ "category": "objects",
+ "moji": "📦",
+ "unicodeVersion": "6.0",
"digest": "e82bf5accebb65136e897c15607eef635fb79fd7b2d8c8e19a9eb00b6786918c"
},
- {
- "name": "page_facing_up",
- "unicode": "1F4C4",
+ "page_facing_up": {
+ "category": "objects",
+ "moji": "📄",
+ "unicodeVersion": "6.0",
"digest": "3884868bdcb2f29615b09a13a30385cbc5269379094a54b5a7e8a5f4e8ce905a"
},
- {
- "name": "page_with_curl",
- "unicode": "1F4C3",
+ "page_with_curl": {
+ "category": "objects",
+ "moji": "📃",
+ "unicodeVersion": "6.0",
"digest": "3d6257670189f841ad1fa45415c34feb2433b2cb35bb435c4ee122ce89b39669"
},
- {
- "name": "pager",
- "unicode": "1F4DF",
+ "pager": {
+ "category": "objects",
+ "moji": "📟",
+ "unicodeVersion": "6.0",
"digest": "e21c756cc1c58ebc1b37ebcd38e22a25b31e2e81306c6f18285d6a7671f9eb12"
},
- {
- "name": "paintbrush",
- "unicode": "1F58C",
- "digest": "fc0da7a25b726b8be9dd6467953e27293d2313a21eeff21424c2a19be614fff2"
- },
- {
- "name": "lower_left_paintbrush",
- "unicode": "1F58C",
+ "paintbrush": {
+ "category": "objects",
+ "moji": "🖌",
+ "unicodeVersion": "7.0",
"digest": "fc0da7a25b726b8be9dd6467953e27293d2313a21eeff21424c2a19be614fff2"
},
- {
- "name": "palm_tree",
- "unicode": "1F334",
+ "palm_tree": {
+ "category": "nature",
+ "moji": "🌴",
+ "unicodeVersion": "6.0",
"digest": "90fedafd62fe0abf51325174d0f293ebb9a4794913b9ba93b12f2d0119056df1"
},
- {
- "name": "pancakes",
- "unicode": "1F95E",
+ "pancakes": {
+ "category": "food",
+ "moji": "🥞",
+ "unicodeVersion": "9.0",
"digest": "5256b4832431e8a88555796b1a9726f12d909a26fb2bdc3a0abff76412c45903"
},
- {
- "name": "panda_face",
- "unicode": "1F43C",
+ "panda_face": {
+ "category": "nature",
+ "moji": "🐼",
+ "unicodeVersion": "6.0",
"digest": "56a4b84abe983bd6569be1b81ac5e43071015fd308389a16b92231310ae56a5b"
},
- {
- "name": "paperclip",
- "unicode": "1F4CE",
+ "paperclip": {
+ "category": "objects",
+ "moji": "📎",
+ "unicodeVersion": "6.0",
"digest": "d1e2ce94a12b7e8b7a9bba49e47ddc7432ec0288545d3b6817c7a499e806e3f0"
},
- {
- "name": "paperclips",
- "unicode": "1F587",
- "digest": "70cefa0d0777f070e393e9f95c24146fe2dd627f30fa3845baa19310d9291fe2"
- },
- {
- "name": "linked_paperclips",
- "unicode": "1F587",
+ "paperclips": {
+ "category": "objects",
+ "moji": "🖇",
+ "unicodeVersion": "7.0",
"digest": "70cefa0d0777f070e393e9f95c24146fe2dd627f30fa3845baa19310d9291fe2"
},
- {
- "name": "park",
- "unicode": "1F3DE",
+ "park": {
+ "category": "travel",
+ "moji": "🏞",
+ "unicodeVersion": "7.0",
"digest": "444dce8014e0817ddd756c36a38adfbbf7ae4c6aa509e4cae291828f0716d5e7"
},
- {
- "name": "national_park",
- "unicode": "1F3DE",
- "digest": "444dce8014e0817ddd756c36a38adfbbf7ae4c6aa509e4cae291828f0716d5e7"
- },
- {
- "name": "parking",
- "unicode": "1F17F",
+ "parking": {
+ "category": "symbols",
+ "moji": "🅿",
+ "unicodeVersion": "5.2",
"digest": "9f1da460a7dd58b26beab8cf701be2691fb812208fbc941c71daa35be1507c2f"
},
- {
- "name": "part_alternation_mark",
- "unicode": "303D",
+ "part_alternation_mark": {
+ "category": "symbols",
+ "moji": "〽",
+ "unicodeVersion": "3.2",
"digest": "956da19353bb38fd4dfe0ab5360679a9035d566858fb5de62887b85c75fb8eef"
},
- {
- "name": "partly_sunny",
- "unicode": "26C5",
+ "partly_sunny": {
+ "category": "nature",
+ "moji": "⛅",
+ "unicodeVersion": "5.2",
"digest": "8fb9a6d2caf9e0cce58447762f0dfd6aa0b581b2e83fea6411348e0cbc8cf3c4"
},
- {
- "name": "passport_control",
- "unicode": "1F6C2",
+ "passport_control": {
+ "category": "symbols",
+ "moji": "🛂",
+ "unicodeVersion": "6.0",
"digest": "d9be6eed2c90e1c89171c42d70a06485fdf86a4c68833371832cc1f6897fadd0"
},
- {
- "name": "pause_button",
- "unicode": "23F8",
- "digest": "143221d99e82399ed7824b6c5e185700896492058b65c04e4c668291de78b203"
- },
- {
- "name": "double_vertical_bar",
- "unicode": "23F8",
+ "pause_button": {
+ "category": "symbols",
+ "moji": "⏸",
+ "unicodeVersion": "7.0",
"digest": "143221d99e82399ed7824b6c5e185700896492058b65c04e4c668291de78b203"
},
- {
- "name": "peace",
- "unicode": "262E",
+ "peace": {
+ "category": "symbols",
+ "moji": "☮",
+ "unicodeVersion": "1.1",
"digest": "65181429e373c1f0507bbd98425c1bec0c042d648fb285a392460cbce60f44d4"
},
- {
- "name": "peace_symbol",
- "unicode": "262E",
- "digest": "65181429e373c1f0507bbd98425c1bec0c042d648fb285a392460cbce60f44d4"
- },
- {
- "name": "peach",
- "unicode": "1F351",
+ "peach": {
+ "category": "food",
+ "moji": "🍑",
+ "unicodeVersion": "6.0",
"digest": "768d1f4f29e1e06aff5abb29043be83087ded16427ce6a2d0f682814e665e311"
},
- {
- "name": "peanuts",
- "unicode": "1F95C",
- "digest": "e2384846b6e4a6c3a56e991ebb749cb68b330ac00a9e9d888b2c39105ff7ff5d"
- },
- {
- "name": "shelled_peanut",
- "unicode": "1F95C",
+ "peanuts": {
+ "category": "food",
+ "moji": "🥜",
+ "unicodeVersion": "9.0",
"digest": "e2384846b6e4a6c3a56e991ebb749cb68b330ac00a9e9d888b2c39105ff7ff5d"
},
- {
- "name": "pear",
- "unicode": "1F350",
+ "pear": {
+ "category": "food",
+ "moji": "🍐",
+ "unicodeVersion": "6.0",
"digest": "b7c9cf90bb979649b863d2f4132f1b51f6f8107d42e08fb8b4033fea32844948"
},
- {
- "name": "pen_ballpoint",
- "unicode": "1F58A",
+ "pen_ballpoint": {
+ "category": "objects",
+ "moji": "🖊",
+ "unicodeVersion": "7.0",
"digest": "aacb20b220f26704e10303deeea33be0eec2d3811dcba7795902ca44b6ae9876"
},
- {
- "name": "lower_left_ballpoint_pen",
- "unicode": "1F58A",
- "digest": "aacb20b220f26704e10303deeea33be0eec2d3811dcba7795902ca44b6ae9876"
- },
- {
- "name": "pen_fountain",
- "unicode": "1F58B",
- "digest": "3619913eab2b6291f518b40481bb3eca0820d68b0a1b3c11fb6a69c62b75a626"
- },
- {
- "name": "lower_left_fountain_pen",
- "unicode": "1F58B",
+ "pen_fountain": {
+ "category": "objects",
+ "moji": "🖋",
+ "unicodeVersion": "7.0",
"digest": "3619913eab2b6291f518b40481bb3eca0820d68b0a1b3c11fb6a69c62b75a626"
},
- {
- "name": "pencil",
- "unicode": "1F4DD",
+ "pencil": {
+ "category": "objects",
+ "moji": "📝",
+ "unicodeVersion": "6.0",
"digest": "accbc3f1439b7faa4411e502385f78a16c8e71851f71fc13582753291ffb507c"
},
- {
- "name": "memo",
- "unicode": "1F4DD",
- "digest": "accbc3f1439b7faa4411e502385f78a16c8e71851f71fc13582753291ffb507c"
- },
- {
- "name": "pencil2",
- "unicode": "270F",
+ "pencil2": {
+ "category": "objects",
+ "moji": "✏",
+ "unicodeVersion": "1.1",
"digest": "9ca1b56b5726f472b1f1b23050ed163e213916dac379d22e38e4c8358fe871e0"
},
- {
- "name": "penguin",
- "unicode": "1F427",
+ "penguin": {
+ "category": "nature",
+ "moji": "🐧",
+ "unicodeVersion": "6.0",
"digest": "a1800ab931d6dc84a9c89bfab2c815198025c276d952509c55b18dd20bd9d316"
},
- {
- "name": "pensive",
- "unicode": "1F614",
+ "pensive": {
+ "category": "people",
+ "moji": "😔",
+ "unicodeVersion": "6.0",
"digest": "d237deff9f5ead8a0b281b7e5c6f4b82e98cc30c80c86c22c3fdc6160090b2f2"
},
- {
- "name": "performing_arts",
- "unicode": "1F3AD",
+ "performing_arts": {
+ "category": "activity",
+ "moji": "🎭",
+ "unicodeVersion": "6.0",
"digest": "d7c7bc9213e308ca26286cbbd8012e656b0f9b00293758faf1bfccc4c5ceabed"
},
- {
- "name": "persevere",
- "unicode": "1F623",
+ "persevere": {
+ "category": "people",
+ "moji": "😣",
+ "unicodeVersion": "6.0",
"digest": "c361509c9b8663af19a02a1ffff61b1b0d0b4bd75d693ce3d406b0ca1bde1ca0"
},
- {
- "name": "person_frowning",
- "unicode": "1F64D",
+ "person_frowning": {
+ "category": "people",
+ "moji": "🙍",
+ "unicodeVersion": "6.0",
"digest": "b37be8bd95f21a6860ad3f171b8086125ab37331b382d87bcdb4cd684800546b"
},
- {
- "name": "person_frowning_tone1",
- "unicode": "1F64D-1F3FB",
+ "person_frowning_tone1": {
+ "category": "people",
+ "moji": "🙍🏻",
+ "unicodeVersion": "8.0",
"digest": "3d5e78a367f9673baed2a86bc11cf04fd44394aadb65291fa51ade8dca318427"
},
- {
- "name": "person_frowning_tone2",
- "unicode": "1F64D-1F3FC",
+ "person_frowning_tone2": {
+ "category": "people",
+ "moji": "🙍🏼",
+ "unicodeVersion": "8.0",
"digest": "7456c414c65ad6b6f11855f68a2eedc18113526f86862c4373202397cb1bed2c"
},
- {
- "name": "person_frowning_tone3",
- "unicode": "1F64D-1F3FD",
+ "person_frowning_tone3": {
+ "category": "people",
+ "moji": "🙍🏽",
+ "unicodeVersion": "8.0",
"digest": "c86cf2d6951f1e6a7c786a74caaf68a777cf00e88023e23849d4383f864ae437"
},
- {
- "name": "person_frowning_tone4",
- "unicode": "1F64D-1F3FE",
+ "person_frowning_tone4": {
+ "category": "people",
+ "moji": "🙍🏾",
+ "unicodeVersion": "8.0",
"digest": "944e96ced645ced8db6bb50120c7e37ed46b6960d595cbfe964c81803efa83aa"
},
- {
- "name": "person_frowning_tone5",
- "unicode": "1F64D-1F3FF",
+ "person_frowning_tone5": {
+ "category": "people",
+ "moji": "🙍🏿",
+ "unicodeVersion": "8.0",
"digest": "4bd0ea571be6ef9f0493784ef0d12d5e47bc2d6ac610fb42c450bf3d87fb2948"
},
- {
- "name": "person_with_blond_hair",
- "unicode": "1F471",
+ "person_with_blond_hair": {
+ "category": "people",
+ "moji": "👱",
+ "unicodeVersion": "6.0",
"digest": "a7f94ede2e43308108c2260d83fc10121dda09a67f94a0a840e6d7bba7fd5616"
},
- {
- "name": "person_with_blond_hair_tone1",
- "unicode": "1F471-1F3FB",
+ "person_with_blond_hair_tone1": {
+ "category": "people",
+ "moji": "👱🏻",
+ "unicodeVersion": "8.0",
"digest": "00a116357a7878554c83e5bade4bddfa9cfabf76a229efa19cbb58e0d216219c"
},
- {
- "name": "person_with_blond_hair_tone2",
- "unicode": "1F471-1F3FC",
+ "person_with_blond_hair_tone2": {
+ "category": "people",
+ "moji": "👱🏼",
+ "unicodeVersion": "8.0",
"digest": "df509ebe92ed3138b9d5bd4645eff4b13f77f714cf62bb949c59eff1adc00019"
},
- {
- "name": "person_with_blond_hair_tone3",
- "unicode": "1F471-1F3FD",
+ "person_with_blond_hair_tone3": {
+ "category": "people",
+ "moji": "👱🏽",
+ "unicodeVersion": "8.0",
"digest": "6f328513f440a0c8cd1dc44596a5028fd8f306bdaf57c1e6f3aa94a3aa262b3c"
},
- {
- "name": "person_with_blond_hair_tone4",
- "unicode": "1F471-1F3FE",
+ "person_with_blond_hair_tone4": {
+ "category": "people",
+ "moji": "👱🏾",
+ "unicodeVersion": "8.0",
"digest": "32df1a577815b009696643ad80d063cc97b35d54add6d4e5517fc936f6da9ee8"
},
- {
- "name": "person_with_blond_hair_tone5",
- "unicode": "1F471-1F3FF",
+ "person_with_blond_hair_tone5": {
+ "category": "people",
+ "moji": "👱🏿",
+ "unicodeVersion": "8.0",
"digest": "2e270bb39187d8e36a33f4aa4d6045308189595fafc157cf7993e82d7ce93442"
},
- {
- "name": "person_with_pouting_face",
- "unicode": "1F64E",
+ "person_with_pouting_face": {
+ "category": "people",
+ "moji": "🙎",
+ "unicodeVersion": "6.0",
"digest": "57e9a6e5f82121516dc189173f2a63b218f726cd51014e24a18c2bdfeeec3a0b"
},
- {
- "name": "person_with_pouting_face_tone1",
- "unicode": "1F64E-1F3FB",
+ "person_with_pouting_face_tone1": {
+ "category": "people",
+ "moji": "🙎🏻",
+ "unicodeVersion": "8.0",
"digest": "d10dadb1ac03fc2e221eff77b4c47935dc0b4fe897af3de30461e7226c3b4bbc"
},
- {
- "name": "person_with_pouting_face_tone2",
- "unicode": "1F64E-1F3FC",
+ "person_with_pouting_face_tone2": {
+ "category": "people",
+ "moji": "🙎🏼",
+ "unicodeVersion": "8.0",
"digest": "efface531537ab934b3b96985210a2dac88de812e82e804d6ec12174e536d1cc"
},
- {
- "name": "person_with_pouting_face_tone3",
- "unicode": "1F64E-1F3FD",
+ "person_with_pouting_face_tone3": {
+ "category": "people",
+ "moji": "🙎🏽",
+ "unicodeVersion": "8.0",
"digest": "7ff26ece237216b949bfa96d16bd12cfd248c6fd3e4ed89aa6c735c09eafaeff"
},
- {
- "name": "person_with_pouting_face_tone4",
- "unicode": "1F64E-1F3FE",
+ "person_with_pouting_face_tone4": {
+ "category": "people",
+ "moji": "🙎🏾",
+ "unicodeVersion": "8.0",
"digest": "045c04105df41d94ff4942133c7394e42ff35ef76c4ccb711497ab77ae6219f2"
},
- {
- "name": "person_with_pouting_face_tone5",
- "unicode": "1F64E-1F3FF",
+ "person_with_pouting_face_tone5": {
+ "category": "people",
+ "moji": "🙎🏿",
+ "unicodeVersion": "8.0",
"digest": "783ee37f146fcf61d38af5009f5823cf6526fe99ed891979f454016bce9dd4ba"
},
- {
- "name": "pick",
- "unicode": "26CF",
+ "pick": {
+ "category": "objects",
+ "moji": "⛏",
+ "unicodeVersion": "5.2",
"digest": "7f0ec5445b4d5c66cf46e2a7332946cce34bd70e9929ac7a119251a7f57f555d"
},
- {
- "name": "pig",
- "unicode": "1F437",
+ "pig": {
+ "category": "nature",
+ "moji": "🐷",
+ "unicodeVersion": "6.0",
"digest": "51362570ab36805c8f67622ee4543e38811f8abb20f732a1af2ffbff2d63d042"
},
- {
- "name": "pig2",
- "unicode": "1F416",
+ "pig2": {
+ "category": "nature",
+ "moji": "🐖",
+ "unicodeVersion": "6.0",
"digest": "67010e255f28061b9d9210bcdab6edc072642ad134122a1d0c7e3a6b1795a45b"
},
- {
- "name": "pig_nose",
- "unicode": "1F43D",
+ "pig_nose": {
+ "category": "nature",
+ "moji": "🐽",
+ "unicodeVersion": "6.0",
"digest": "0b21cac238bf4910939fbea9bed35552378c1b605a3867d7b85c1556dbda22a9"
},
- {
- "name": "pill",
- "unicode": "1F48A",
+ "pill": {
+ "category": "objects",
+ "moji": "💊",
+ "unicodeVersion": "6.0",
"digest": "cb00be361aaba6dbcf8da58bd20b76221dd75031362ecae99496b088ed413a7f"
},
- {
- "name": "pineapple",
- "unicode": "1F34D",
+ "pineapple": {
+ "category": "food",
+ "moji": "🍍",
+ "unicodeVersion": "6.0",
"digest": "621d4d4c52b59e566c2e29ed7845c8bd2d1da0946577527342097808d170dd70"
},
- {
- "name": "ping_pong",
- "unicode": "1F3D3",
- "digest": "943a858bd054c81a08a08951f8351c27c8009b85a9359729c7362868298b58e1"
- },
- {
- "name": "table_tennis",
- "unicode": "1F3D3",
+ "ping_pong": {
+ "category": "activity",
+ "moji": "🏓",
+ "unicodeVersion": "8.0",
"digest": "943a858bd054c81a08a08951f8351c27c8009b85a9359729c7362868298b58e1"
},
- {
- "name": "pisces",
- "unicode": "2653",
+ "pisces": {
+ "category": "symbols",
+ "moji": "♓",
+ "unicodeVersion": "1.1",
"digest": "453c3915122a4b6b32867056d2447be48675a84469145c88d52f8007fcb0861a"
},
- {
- "name": "pizza",
- "unicode": "1F355",
+ "pizza": {
+ "category": "food",
+ "moji": "🍕",
+ "unicodeVersion": "6.0",
"digest": "169bc6c1e1d7fdab1b8bf2eab0eeec4f9a7ae08b7b9b38f33b0b0c642e72053a"
},
- {
- "name": "place_of_worship",
- "unicode": "1F6D0",
+ "place_of_worship": {
+ "category": "symbols",
+ "moji": "🛐",
+ "unicodeVersion": "8.0",
"digest": "daf271d36a38ee8c0f8b9de84c128ab8b25a5b7df8f107308d0353c961f2c644"
},
- {
- "name": "worship_symbol",
- "unicode": "1F6D0",
- "digest": "daf271d36a38ee8c0f8b9de84c128ab8b25a5b7df8f107308d0353c961f2c644"
- },
- {
- "name": "play_pause",
- "unicode": "23EF",
+ "play_pause": {
+ "category": "symbols",
+ "moji": "⏯",
+ "unicodeVersion": "6.0",
"digest": "af1498f34a3d6e0da8bbd26ebaa447e697e2df08c8eb255437cf7905c93f8c42"
},
- {
- "name": "point_down",
- "unicode": "1F447",
+ "point_down": {
+ "category": "people",
+ "moji": "👇",
+ "unicodeVersion": "6.0",
"digest": "4ecdb3f31c16dc38113b8854ec1a7884613b688a185ebdf967eab9a81018f76d"
},
- {
- "name": "point_down_tone1",
- "unicode": "1F447-1F3FB",
+ "point_down_tone1": {
+ "category": "people",
+ "moji": "👇🏻",
+ "unicodeVersion": "8.0",
"digest": "c74a7c94367cddbfa840542dc0924adeb0d108be0c7fde8c25fb95d69115d283"
},
- {
- "name": "point_down_tone2",
- "unicode": "1F447-1F3FC",
+ "point_down_tone2": {
+ "category": "people",
+ "moji": "👇🏼",
+ "unicodeVersion": "8.0",
"digest": "dc4bda0726d85418b974addb42738f437fbb9cf16e5815cdbab3859c4ada6cae"
},
- {
- "name": "point_down_tone3",
- "unicode": "1F447-1F3FD",
+ "point_down_tone3": {
+ "category": "people",
+ "moji": "👇🏽",
+ "unicodeVersion": "8.0",
"digest": "e460f81a501376d2f0ed1d45e358c5ed03ba049e8f466e4298afb4f3ca6d24dc"
},
- {
- "name": "point_down_tone4",
- "unicode": "1F447-1F3FE",
+ "point_down_tone4": {
+ "category": "people",
+ "moji": "👇🏾",
+ "unicodeVersion": "8.0",
"digest": "4bc91cd771f24e0f897a9d8b18f323fec9a82da0fc2429c4a7e4e6a9d885a0a3"
},
- {
- "name": "point_down_tone5",
- "unicode": "1F447-1F3FF",
+ "point_down_tone5": {
+ "category": "people",
+ "moji": "👇🏿",
+ "unicodeVersion": "8.0",
"digest": "7e47c6bc73250f36dc7ae1c1c09e7b41f30647b9d0ff703a53a75cc046b5057d"
},
- {
- "name": "point_left",
- "unicode": "1F448",
+ "point_left": {
+ "category": "people",
+ "moji": "👈",
+ "unicodeVersion": "6.0",
"digest": "b5a7e864a0016afbadb3bec41f51ecf8c4af73cc20462e1a08b357f90bca6879"
},
- {
- "name": "point_left_tone1",
- "unicode": "1F448-1F3FB",
+ "point_left_tone1": {
+ "category": "people",
+ "moji": "👈🏻",
+ "unicodeVersion": "8.0",
"digest": "9f1868272a10a2b738c065be5d30241643324550cfd47baf01c7a09060e66d31"
},
- {
- "name": "point_left_tone2",
- "unicode": "1F448-1F3FC",
+ "point_left_tone2": {
+ "category": "people",
+ "moji": "👈🏼",
+ "unicodeVersion": "8.0",
"digest": "bf0d58c68178a2c2c01d4a6235a1a66b90073cea170f9f6fe2668b6dd68424f7"
},
- {
- "name": "point_left_tone3",
- "unicode": "1F448-1F3FD",
+ "point_left_tone3": {
+ "category": "people",
+ "moji": "👈🏽",
+ "unicodeVersion": "8.0",
"digest": "34d28c97bc8f9d111d14e328153c4298fc32cf18e39e20aacaec17846645ed90"
},
- {
- "name": "point_left_tone4",
- "unicode": "1F448-1F3FE",
+ "point_left_tone4": {
+ "category": "people",
+ "moji": "👈🏾",
+ "unicodeVersion": "8.0",
"digest": "c40c8436316915d516c53bb1c98a469528cefd98baa719be7e748c4608cbbcc9"
},
- {
- "name": "point_left_tone5",
- "unicode": "1F448-1F3FF",
+ "point_left_tone5": {
+ "category": "people",
+ "moji": "👈🏿",
+ "unicodeVersion": "8.0",
"digest": "c410fe32e4ce0ded74845a54b86090e59e5820d457837b16e175b36cc71ecb46"
},
- {
- "name": "point_right",
- "unicode": "1F449",
+ "point_right": {
+ "category": "people",
+ "moji": "👉",
+ "unicodeVersion": "6.0",
"digest": "44d9251ab41f2f48c2250c44a47f92b3476a71f13fbbbfb637547db837fd5a49"
},
- {
- "name": "point_right_tone1",
- "unicode": "1F449-1F3FB",
+ "point_right_tone1": {
+ "category": "people",
+ "moji": "👉🏻",
+ "unicodeVersion": "8.0",
"digest": "9fcce259eb81c0b52ec7796b98a1653194e3a9021a1d338df1dbbab7522fc406"
},
- {
- "name": "point_right_tone2",
- "unicode": "1F449-1F3FC",
+ "point_right_tone2": {
+ "category": "people",
+ "moji": "👉🏼",
+ "unicodeVersion": "8.0",
"digest": "9d00a0b1cfc435674dc56065b3d28d28839196977504cf20581205351d8708f2"
},
- {
- "name": "point_right_tone3",
- "unicode": "1F449-1F3FD",
+ "point_right_tone3": {
+ "category": "people",
+ "moji": "👉🏽",
+ "unicodeVersion": "8.0",
"digest": "e3026a70630ba73d76892a055a80cac2f78d509faddce737f802d2abefa074ba"
},
- {
- "name": "point_right_tone4",
- "unicode": "1F449-1F3FE",
+ "point_right_tone4": {
+ "category": "people",
+ "moji": "👉🏾",
+ "unicodeVersion": "8.0",
"digest": "ea508fde90561460361773b4e1b8e80874667b19ac115926206e7c592587cb76"
},
- {
- "name": "point_right_tone5",
- "unicode": "1F449-1F3FF",
+ "point_right_tone5": {
+ "category": "people",
+ "moji": "👉🏿",
+ "unicodeVersion": "8.0",
"digest": "d59cdb2864eb2929941ecd233f8b8afcddc30fbd4594e5f9acf6386ae06ac12c"
},
- {
- "name": "point_up",
- "unicode": "261D",
+ "point_up": {
+ "category": "people",
+ "moji": "☝",
+ "unicodeVersion": "1.1",
"digest": "b69ff4f650989709f2185822d278c7773672bd9eb4a625da80f3038a2b9ce42b"
},
- {
- "name": "point_up_2",
- "unicode": "1F446",
+ "point_up_2": {
+ "category": "people",
+ "moji": "👆",
+ "unicodeVersion": "6.0",
"digest": "e83cd9eff2af5125a25f5a306c3ee3cfea240add683b5c36a86a994a8d8c805c"
},
- {
- "name": "point_up_2_tone1",
- "unicode": "1F446-1F3FB",
+ "point_up_2_tone1": {
+ "category": "people",
+ "moji": "👆🏻",
+ "unicodeVersion": "8.0",
"digest": "b02ec3e7e04a83bfb769cffb951cbf32aa78e56fa5a51c097f9326df9e08ed33"
},
- {
- "name": "point_up_2_tone2",
- "unicode": "1F446-1F3FC",
+ "point_up_2_tone2": {
+ "category": "people",
+ "moji": "👆🏼",
+ "unicodeVersion": "8.0",
"digest": "32994b85c8b4a1383ca985ebc3382be88866cea1ff1315adfb71fb05e992a232"
},
- {
- "name": "point_up_2_tone3",
- "unicode": "1F446-1F3FD",
+ "point_up_2_tone3": {
+ "category": "people",
+ "moji": "👆🏽",
+ "unicodeVersion": "8.0",
"digest": "9e263bcfb82ada34ff85291f36e64e66b86760fb11a4e0c554e801644d417d6d"
},
- {
- "name": "point_up_2_tone4",
- "unicode": "1F446-1F3FE",
+ "point_up_2_tone4": {
+ "category": "people",
+ "moji": "👆🏾",
+ "unicodeVersion": "8.0",
"digest": "3edc92130a0851ac7b5236772ce7918d088689221df287098688e1ed5b3ff181"
},
- {
- "name": "point_up_2_tone5",
- "unicode": "1F446-1F3FF",
+ "point_up_2_tone5": {
+ "category": "people",
+ "moji": "👆🏿",
+ "unicodeVersion": "8.0",
"digest": "cabb3b7da9290840ef59d0c8b22625bdb2e94842f01b0a575ccbc348f3069d77"
},
- {
- "name": "point_up_tone1",
- "unicode": "261D-1F3FB",
+ "point_up_tone1": {
+ "category": "people",
+ "moji": "☝🏻",
+ "unicodeVersion": "8.0",
"digest": "e496fda349072f8b321ceb7a251175f7244c3076661f5ede48ea75ba1acf8339"
},
- {
- "name": "point_up_tone2",
- "unicode": "261D-1F3FC",
+ "point_up_tone2": {
+ "category": "people",
+ "moji": "☝🏼",
+ "unicodeVersion": "8.0",
"digest": "5a8081323f3baa67e6431e21e16a36559b339f5175d586644e34947f738dd07a"
},
- {
- "name": "point_up_tone3",
- "unicode": "261D-1F3FD",
+ "point_up_tone3": {
+ "category": "people",
+ "moji": "☝🏽",
+ "unicodeVersion": "8.0",
"digest": "07bf0cea812eb226b443334e026e13d1ec23e013478f4af862a3919703107842"
},
- {
- "name": "point_up_tone4",
- "unicode": "261D-1F3FE",
+ "point_up_tone4": {
+ "category": "people",
+ "moji": "☝🏾",
+ "unicodeVersion": "8.0",
"digest": "1fbbd71433108143ee157d0fdadd183f7f013bafa96f0dd93b181e1fd5fd4af2"
},
- {
- "name": "point_up_tone5",
- "unicode": "261D-1F3FF",
+ "point_up_tone5": {
+ "category": "people",
+ "moji": "☝🏿",
+ "unicodeVersion": "8.0",
"digest": "ad068ef32df32f8297955490a9a90590a0f93ed5702a052cd0d8f6484c6cc679"
},
- {
- "name": "police_car",
- "unicode": "1F693",
+ "police_car": {
+ "category": "travel",
+ "moji": "🚓",
+ "unicodeVersion": "6.0",
"digest": "0909be1bd615ae331a7cce71e16dee3ca663c721d5170072c593cb7c76f9f661"
},
- {
- "name": "poodle",
- "unicode": "1F429",
+ "poodle": {
+ "category": "nature",
+ "moji": "🐩",
+ "unicodeVersion": "6.0",
"digest": "f1742fdf3fd26a8a5cfeaba57026518dacaad364cbd03344c4000a35af13e47a"
},
- {
- "name": "poop",
- "unicode": "1F4A9",
- "digest": "857a61c872138d359a7fe8257bb26118afa49d75186eca2addb415d07c92b3ec"
- },
- {
- "name": "shit",
- "unicode": "1F4A9",
+ "poop": {
+ "category": "people",
+ "moji": "💩",
+ "unicodeVersion": "6.0",
"digest": "857a61c872138d359a7fe8257bb26118afa49d75186eca2addb415d07c92b3ec"
},
- {
- "name": "hankey",
- "unicode": "1F4A9",
- "digest": "857a61c872138d359a7fe8257bb26118afa49d75186eca2addb415d07c92b3ec"
- },
- {
- "name": "poo",
- "unicode": "1F4A9",
- "digest": "857a61c872138d359a7fe8257bb26118afa49d75186eca2addb415d07c92b3ec"
- },
- {
- "name": "popcorn",
- "unicode": "1F37F",
+ "popcorn": {
+ "category": "food",
+ "moji": "🍿",
+ "unicodeVersion": "8.0",
"digest": "684f1b7ef34ea7ca933aed41569bc6595a19ef0d546a1b7b9e69f8335540b323"
},
- {
- "name": "post_office",
- "unicode": "1F3E3",
+ "post_office": {
+ "category": "travel",
+ "moji": "🏣",
+ "unicodeVersion": "6.0",
"digest": "54398ee396c1314a7993b1cb1cba264946b5c9d5a7dbb43fd67286854d1d1a0f"
},
- {
- "name": "postal_horn",
- "unicode": "1F4EF",
+ "postal_horn": {
+ "category": "objects",
+ "moji": "📯",
+ "unicodeVersion": "6.0",
"digest": "0ea12f44f3bae9a14bde3b37361b48bd738d2f613bb1b53a9204959b70e643f8"
},
- {
- "name": "postbox",
- "unicode": "1F4EE",
+ "postbox": {
+ "category": "objects",
+ "moji": "📮",
+ "unicodeVersion": "6.0",
"digest": "bbc424ae8d46de380d7023a43ea064002fd614657d00330d3503275827ac87e2"
},
- {
- "name": "potable_water",
- "unicode": "1F6B0",
+ "potable_water": {
+ "category": "symbols",
+ "moji": "🚰",
+ "unicodeVersion": "6.0",
"digest": "dbe80d9637837377cc2a290da2e895f81a3108cc18b049e3d87212402c1c2098"
},
- {
- "name": "potato",
- "unicode": "1F954",
+ "potato": {
+ "category": "food",
+ "moji": "🥔",
+ "unicodeVersion": "9.0",
"digest": "a56a69f36f3a0793f278726d92c0cea2960554f3062ef1a0904526a04511d8e1"
},
- {
- "name": "pouch",
- "unicode": "1F45D",
+ "pouch": {
+ "category": "people",
+ "moji": "👝",
+ "unicodeVersion": "6.0",
"digest": "9f012b90310b4a072b6a8fa2c64def087b5f7ffffaafc36e1856ba943a170351"
},
- {
- "name": "poultry_leg",
- "unicode": "1F357",
+ "poultry_leg": {
+ "category": "food",
+ "moji": "🍗",
+ "unicodeVersion": "6.0",
"digest": "1445ec4f5e68a19e5a84e5537dca8190d62409070c954d112e6097f1a6b7f054"
},
- {
- "name": "pound",
- "unicode": "1F4B7",
+ "pound": {
+ "category": "objects",
+ "moji": "💷",
+ "unicodeVersion": "6.0",
"digest": "eb11b83eb52adb0a15e69a3bc15788a2dc7825dedee81ac3af84963c9dd517b5"
},
- {
- "name": "pouting_cat",
- "unicode": "1F63E",
+ "pouting_cat": {
+ "category": "people",
+ "moji": "😾",
+ "unicodeVersion": "6.0",
"digest": "8822abedf3499cf98278d7eeea0764d1100ec25cad71b4b2e877f9346f8c8138"
},
- {
- "name": "pray",
- "unicode": "1F64F",
+ "pray": {
+ "category": "people",
+ "moji": "🙏",
+ "unicodeVersion": "6.0",
"digest": "735b79dab34ac2cf81fd42fdcd7eb1f13c24655e5e343816d5764896c03edeea"
},
- {
- "name": "pray_tone1",
- "unicode": "1F64F-1F3FB",
+ "pray_tone1": {
+ "category": "people",
+ "moji": "🙏🏻",
+ "unicodeVersion": "8.0",
"digest": "e8b6103450215e8566797f150978355e297deade4eb47a6371f7a7bc558fed9d"
},
- {
- "name": "pray_tone2",
- "unicode": "1F64F-1F3FC",
+ "pray_tone2": {
+ "category": "people",
+ "moji": "🙏🏼",
+ "unicodeVersion": "8.0",
"digest": "ee8baacd95d7e8dbad8a1f2d9a12e36c98f3d518db5d3b117d0a18290815e62b"
},
- {
- "name": "pray_tone3",
- "unicode": "1F64F-1F3FD",
+ "pray_tone3": {
+ "category": "people",
+ "moji": "🙏🏽",
+ "unicodeVersion": "8.0",
"digest": "ae8c0caa9aca0a6c44069e76a7535c961d0284cd701812f76bbd2bd79ce2bd53"
},
- {
- "name": "pray_tone4",
- "unicode": "1F64F-1F3FE",
+ "pray_tone4": {
+ "category": "people",
+ "moji": "🙏🏾",
+ "unicodeVersion": "8.0",
"digest": "64f7b3178b8cd6f6a877ed583539eefe068fa87a0dd658fdcd58c8bc809f7e17"
},
- {
- "name": "pray_tone5",
- "unicode": "1F64F-1F3FF",
+ "pray_tone5": {
+ "category": "people",
+ "moji": "🙏🏿",
+ "unicodeVersion": "8.0",
"digest": "5bc8cdce937ac06779c87021423efcec4f602aa4a39dba90b00de81033005332"
},
- {
- "name": "prayer_beads",
- "unicode": "1F4FF",
+ "prayer_beads": {
+ "category": "objects",
+ "moji": "📿",
+ "unicodeVersion": "8.0",
"digest": "80177091264430cbcf7c994fbe5ee17319d1a58d933636cc752a54dafcf98a05"
},
- {
- "name": "pregnant_woman",
- "unicode": "1F930",
+ "pregnant_woman": {
+ "category": "people",
+ "moji": "🤰",
+ "unicodeVersion": "9.0",
"digest": "49abb86409103338bdb6ae43c13a78ca2dc9cd158a26df35eadd0da3c84a4352"
},
- {
- "name": "expecting_woman",
- "unicode": "1F930",
- "digest": "49abb86409103338bdb6ae43c13a78ca2dc9cd158a26df35eadd0da3c84a4352"
- },
- {
- "name": "pregnant_woman_tone1",
- "unicode": "1F930-1F3FB",
- "digest": "5a9f8ed2b631ecf8af111803a5c11f4c156435a5293cb50329c7b98697c8da25"
- },
- {
- "name": "expecting_woman_tone1",
- "unicode": "1F930-1F3FB",
+ "pregnant_woman_tone1": {
+ "category": "people",
+ "moji": "🤰🏻",
+ "unicodeVersion": "9.0",
"digest": "5a9f8ed2b631ecf8af111803a5c11f4c156435a5293cb50329c7b98697c8da25"
},
- {
- "name": "pregnant_woman_tone2",
- "unicode": "1F930-1F3FC",
+ "pregnant_woman_tone2": {
+ "category": "people",
+ "moji": "🤰🏼",
+ "unicodeVersion": "9.0",
"digest": "279a2eafff603b11629c955b05f5bd3d7da9a271d4fb3f02e9ccd457b8d2d815"
},
- {
- "name": "expecting_woman_tone2",
- "unicode": "1F930-1F3FC",
- "digest": "279a2eafff603b11629c955b05f5bd3d7da9a271d4fb3f02e9ccd457b8d2d815"
- },
- {
- "name": "pregnant_woman_tone3",
- "unicode": "1F930-1F3FD",
+ "pregnant_woman_tone3": {
+ "category": "people",
+ "moji": "🤰🏽",
+ "unicodeVersion": "9.0",
"digest": "93bb63ec2312db315e3f0065520b715cc413ac0fd65538ec9b5cd97df2a42b20"
},
- {
- "name": "expecting_woman_tone3",
- "unicode": "1F930-1F3FD",
- "digest": "93bb63ec2312db315e3f0065520b715cc413ac0fd65538ec9b5cd97df2a42b20"
- },
- {
- "name": "pregnant_woman_tone4",
- "unicode": "1F930-1F3FE",
+ "pregnant_woman_tone4": {
+ "category": "people",
+ "moji": "🤰🏾",
+ "unicodeVersion": "9.0",
"digest": "b8dc3dcec894bfd832a249459b10850f8786b6778d8887a677d1291865623da2"
},
- {
- "name": "expecting_woman_tone4",
- "unicode": "1F930-1F3FE",
- "digest": "b8dc3dcec894bfd832a249459b10850f8786b6778d8887a677d1291865623da2"
- },
- {
- "name": "pregnant_woman_tone5",
- "unicode": "1F930-1F3FF",
- "digest": "73ee432752f81980f353a7f9b9f7a5ece62512dca08e15c1876b89227face21c"
- },
- {
- "name": "expecting_woman_tone5",
- "unicode": "1F930-1F3FF",
+ "pregnant_woman_tone5": {
+ "category": "people",
+ "moji": "🤰🏿",
+ "unicodeVersion": "9.0",
"digest": "73ee432752f81980f353a7f9b9f7a5ece62512dca08e15c1876b89227face21c"
},
- {
- "name": "prince",
- "unicode": "1F934",
+ "prince": {
+ "category": "people",
+ "moji": "🤴",
+ "unicodeVersion": "9.0",
"digest": "34a0e0625f0a9825d3674192d6233b6cae4d8130451293df09f91a6a4165869c"
},
- {
- "name": "prince_tone1",
- "unicode": "1F934-1F3FB",
+ "prince_tone1": {
+ "category": "people",
+ "moji": "🤴🏻",
+ "unicodeVersion": "9.0",
"digest": "ccecdfeccb2ab1fceceae14f3fba875c8c7099785a4c40131c08a697b5b675fc"
},
- {
- "name": "prince_tone2",
- "unicode": "1F934-1F3FC",
+ "prince_tone2": {
+ "category": "people",
+ "moji": "🤴🏼",
+ "unicodeVersion": "9.0",
"digest": "c373fd3e0c1798415e3d8d88fab6c98c1bbdedcbe6f52f3a3899f6e2124a768d"
},
- {
- "name": "prince_tone3",
- "unicode": "1F934-1F3FD",
+ "prince_tone3": {
+ "category": "people",
+ "moji": "🤴🏽",
+ "unicodeVersion": "9.0",
"digest": "71d15695ca954d55aa69d3c753c7d31a8ba5329713a8ddbc90dafc11e524c4ef"
},
- {
- "name": "prince_tone4",
- "unicode": "1F934-1F3FE",
+ "prince_tone4": {
+ "category": "people",
+ "moji": "🤴🏾",
+ "unicodeVersion": "9.0",
"digest": "08f6cb32424f15cc3aaf83c31a5dac7c01a6be2f37ea8f13aed579ce6fb4db19"
},
- {
- "name": "prince_tone5",
- "unicode": "1F934-1F3FF",
+ "prince_tone5": {
+ "category": "people",
+ "moji": "🤴🏿",
+ "unicodeVersion": "9.0",
"digest": "77d521148efa33fa4d3409693d050fecfd948411e807327484f174e289834649"
},
- {
- "name": "princess",
- "unicode": "1F478",
+ "princess": {
+ "category": "people",
+ "moji": "👸",
+ "unicodeVersion": "6.0",
"digest": "efabd28480a843c735f0868734da2f9ce28133933b02ab07b645498f494f3f80"
},
- {
- "name": "princess_tone1",
- "unicode": "1F478-1F3FB",
+ "princess_tone1": {
+ "category": "people",
+ "moji": "👸🏻",
+ "unicodeVersion": "8.0",
"digest": "52b88b99ba64f82e8f36e2a1827c85145e4fcd6863478c2345fe9fa9e8901cdf"
},
- {
- "name": "princess_tone2",
- "unicode": "1F478-1F3FC",
+ "princess_tone2": {
+ "category": "people",
+ "moji": "👸🏼",
+ "unicodeVersion": "8.0",
"digest": "7e44289404693668f20e681fcdc2e516512d54a69c627eedae958f69dfe6eea9"
},
- {
- "name": "princess_tone3",
- "unicode": "1F478-1F3FD",
+ "princess_tone3": {
+ "category": "people",
+ "moji": "👸🏽",
+ "unicodeVersion": "8.0",
"digest": "96c9a9857348d7a1a8be899c50d55b352b9a9fd5c65e4777bfa199fe7929d41c"
},
- {
- "name": "princess_tone4",
- "unicode": "1F478-1F3FE",
+ "princess_tone4": {
+ "category": "people",
+ "moji": "👸🏾",
+ "unicodeVersion": "8.0",
"digest": "67696f96be60f2a36598072172d2db197d007e6c1ac3acef526a5ce6d59bf3f7"
},
- {
- "name": "princess_tone5",
- "unicode": "1F478-1F3FF",
+ "princess_tone5": {
+ "category": "people",
+ "moji": "👸🏿",
+ "unicodeVersion": "8.0",
"digest": "007f624e2fad91bb57ce32ecd35213a796d71807f3b12f3f1575bf50e6a50eeb"
},
- {
- "name": "printer",
- "unicode": "1F5A8",
+ "printer": {
+ "category": "objects",
+ "moji": "🖨",
+ "unicodeVersion": "7.0",
"digest": "5e5307e3dc7ec4e16c9978fb00934c99c4adefca7d32732a244d1f2de71ce6f8"
},
- {
- "name": "projector",
- "unicode": "1F4FD",
+ "projector": {
+ "category": "objects",
+ "moji": "📽",
+ "unicodeVersion": "7.0",
"digest": "7f8e1fdb89584849a56ee34c62cab808af48b7bd4823467d090af4657a2e0420"
},
- {
- "name": "film_projector",
- "unicode": "1F4FD",
- "digest": "7f8e1fdb89584849a56ee34c62cab808af48b7bd4823467d090af4657a2e0420"
- },
- {
- "name": "punch",
- "unicode": "1F44A",
+ "punch": {
+ "category": "people",
+ "moji": "👊",
+ "unicodeVersion": "6.0",
"digest": "c7e7edf6d64f755db3f02874354f08337b3971aff329476d19ac946e0b421329"
},
- {
- "name": "punch_tone1",
- "unicode": "1F44A-1F3FB",
+ "punch_tone1": {
+ "category": "people",
+ "moji": "👊🏻",
+ "unicodeVersion": "8.0",
"digest": "c9ba508b0c36041047473782acfedab5af40dd7946b33daf4d8d54c726e06a11"
},
- {
- "name": "punch_tone2",
- "unicode": "1F44A-1F3FC",
+ "punch_tone2": {
+ "category": "people",
+ "moji": "👊🏼",
+ "unicodeVersion": "8.0",
"digest": "d53011cd2f3334c7b3fffdfe1e2b8cc1c832c74306e1ac6d03f954a1309d7d0b"
},
- {
- "name": "punch_tone3",
- "unicode": "1F44A-1F3FD",
+ "punch_tone3": {
+ "category": "people",
+ "moji": "👊🏽",
+ "unicodeVersion": "8.0",
"digest": "f7522347094e0130ed8e304678106574dbd7dd2b6b3aeb4d8a7a0fef880920b2"
},
- {
- "name": "punch_tone4",
- "unicode": "1F44A-1F3FE",
+ "punch_tone4": {
+ "category": "people",
+ "moji": "👊🏾",
+ "unicodeVersion": "8.0",
"digest": "3e62bdd426f3e6ff175ce3b8dd6f6d3998d9c1506128defa96b528b455295b47"
},
- {
- "name": "punch_tone5",
- "unicode": "1F44A-1F3FF",
+ "punch_tone5": {
+ "category": "people",
+ "moji": "👊🏿",
+ "unicodeVersion": "8.0",
"digest": "7d9bff777dc4ec41ac132b1252fa08cf92a398c8dc146c4a5327b45d568982d8"
},
- {
- "name": "purple_heart",
- "unicode": "1F49C",
+ "purple_heart": {
+ "category": "symbols",
+ "moji": "💜",
+ "unicodeVersion": "6.0",
"digest": "a6bf01de806525942be480e45a4b2879f91df8129b78a1b8734d4f917bcab773"
},
- {
- "name": "purse",
- "unicode": "1F45B",
+ "purse": {
+ "category": "people",
+ "moji": "👛",
+ "unicodeVersion": "6.0",
"digest": "2b785f36e01875d66cfda2192c8c53606e7224a7c869a4826b62cb61613d60c8"
},
- {
- "name": "pushpin",
- "unicode": "1F4CC",
+ "pushpin": {
+ "category": "objects",
+ "moji": "📌",
+ "unicodeVersion": "6.0",
"digest": "c3f7d7008be6bab8dc02284d4d759abf7aafbb3dbbe3a53f0f5b2ff685af88f8"
},
- {
- "name": "put_litter_in_its_place",
- "unicode": "1F6AE",
+ "put_litter_in_its_place": {
+ "category": "symbols",
+ "moji": "🚮",
+ "unicodeVersion": "6.0",
"digest": "f52a57d6f1bada7b6e6b9a6458597d70cb701c01e1120d8cb1d7ff65e01d405c"
},
- {
- "name": "question",
- "unicode": "2753",
+ "question": {
+ "category": "symbols",
+ "moji": "❓",
+ "unicodeVersion": "6.0",
"digest": "40050a1fd29bed321fd601d13dc33de5d6084121f1d873b29bde9dc3d823a310"
},
- {
- "name": "rabbit",
- "unicode": "1F430",
+ "rabbit": {
+ "category": "nature",
+ "moji": "🐰",
+ "unicodeVersion": "6.0",
"digest": "678ad953a7ab8f618c59051449a67c965d1f04f42dd6f6669adaf3fadebd080c"
},
- {
- "name": "rabbit2",
- "unicode": "1F407",
+ "rabbit2": {
+ "category": "nature",
+ "moji": "🐇",
+ "unicodeVersion": "6.0",
"digest": "19b1f5108292472434cc7a49efac4ea9275779735c7aeb0f15c36021d5998ca0"
},
- {
- "name": "race_car",
- "unicode": "1F3CE",
- "digest": "46f4814259d3d17ff35c04110e73e5327aee99f4711cd459ca1ee951508da3a6"
- },
- {
- "name": "racing_car",
- "unicode": "1F3CE",
+ "race_car": {
+ "category": "travel",
+ "moji": "🏎",
+ "unicodeVersion": "7.0",
"digest": "46f4814259d3d17ff35c04110e73e5327aee99f4711cd459ca1ee951508da3a6"
},
- {
- "name": "racehorse",
- "unicode": "1F40E",
+ "racehorse": {
+ "category": "nature",
+ "moji": "🐎",
+ "unicodeVersion": "6.0",
"digest": "a57b7aca35347ada8225eeee06b70cfd040484104963b4df56ea8fec690576b0"
},
- {
- "name": "radio",
- "unicode": "1F4FB",
+ "radio": {
+ "category": "objects",
+ "moji": "📻",
+ "unicodeVersion": "6.0",
"digest": "9245951dd779cdd141089891b15a90d3999a6358acf1fc296aa505100f812108"
},
- {
- "name": "radio_button",
- "unicode": "1F518",
+ "radio_button": {
+ "category": "symbols",
+ "moji": "🔘",
+ "unicodeVersion": "6.0",
"digest": "565bec59198df2592e96564c6e314d3cde33c47b453db1bec6c5d027b5cb4fd9"
},
- {
- "name": "radioactive",
- "unicode": "2622",
- "digest": "0ed6634057824e0cfd10b2533753e3632b0624341a7eac8d9835706480335581"
- },
- {
- "name": "radioactive_sign",
- "unicode": "2622",
+ "radioactive": {
+ "category": "symbols",
+ "moji": "☢",
+ "unicodeVersion": "1.1",
"digest": "0ed6634057824e0cfd10b2533753e3632b0624341a7eac8d9835706480335581"
},
- {
- "name": "rage",
- "unicode": "1F621",
+ "rage": {
+ "category": "people",
+ "moji": "😡",
+ "unicodeVersion": "6.0",
"digest": "d97ba6bd08eec46dbc7199f530c945b73a87a878e35397b0a3e4f2b45039e89e"
},
- {
- "name": "railway_car",
- "unicode": "1F683",
+ "railway_car": {
+ "category": "travel",
+ "moji": "🚃",
+ "unicodeVersion": "6.0",
"digest": "2cddc08d555e7fc24e312c3d255ed013fbf9cd2974a6918369c32554049ba2be"
},
- {
- "name": "railway_track",
- "unicode": "1F6E4",
- "digest": "0da351b6d4e75c6beeaef1225e151d9580d4b5c41dfa1cf192715bf3cec981d7"
- },
- {
- "name": "railroad_track",
- "unicode": "1F6E4",
+ "railway_track": {
+ "category": "travel",
+ "moji": "🛤",
+ "unicodeVersion": "7.0",
"digest": "0da351b6d4e75c6beeaef1225e151d9580d4b5c41dfa1cf192715bf3cec981d7"
},
- {
- "name": "rainbow",
- "unicode": "1F308",
+ "rainbow": {
+ "category": "travel",
+ "moji": "🌈",
+ "unicodeVersion": "6.0",
"digest": "a93aceb54e965f35e397e8c8716b1831614933308d026012d5464ee42783ed4d"
},
- {
- "name": "raised_back_of_hand",
- "unicode": "1F91A",
+ "raised_back_of_hand": {
+ "category": "people",
+ "moji": "🤚",
+ "unicodeVersion": "9.0",
"digest": "20973a697e826625deba5ee3c4f25eb5e1737f2e860ac6fe4ee4d0e0c84b5e12"
},
- {
- "name": "back_of_hand",
- "unicode": "1F91A",
- "digest": "20973a697e826625deba5ee3c4f25eb5e1737f2e860ac6fe4ee4d0e0c84b5e12"
- },
- {
- "name": "raised_back_of_hand_tone1",
- "unicode": "1F91A-1F3FB",
- "digest": "06af5941255ca69d10d99d0a512bbda6141a296453835dbccf259ce0afe1dd3d"
- },
- {
- "name": "back_of_hand_tone1",
- "unicode": "1F91A-1F3FB",
+ "raised_back_of_hand_tone1": {
+ "category": "people",
+ "moji": "🤚🏻",
+ "unicodeVersion": "9.0",
"digest": "06af5941255ca69d10d99d0a512bbda6141a296453835dbccf259ce0afe1dd3d"
},
- {
- "name": "raised_back_of_hand_tone2",
- "unicode": "1F91A-1F3FC",
- "digest": "429ed19555c9e5197b729b3e7bd8013346551051cb0b3fbc8a4372717c9a027d"
- },
- {
- "name": "back_of_hand_tone2",
- "unicode": "1F91A-1F3FC",
+ "raised_back_of_hand_tone2": {
+ "category": "people",
+ "moji": "🤚🏼",
+ "unicodeVersion": "9.0",
"digest": "429ed19555c9e5197b729b3e7bd8013346551051cb0b3fbc8a4372717c9a027d"
},
- {
- "name": "raised_back_of_hand_tone3",
- "unicode": "1F91A-1F3FD",
- "digest": "487a1c3f19e77c99b520ec073de2acc4a9e585b739a84b3989f7de85d2c2045c"
- },
- {
- "name": "back_of_hand_tone3",
- "unicode": "1F91A-1F3FD",
+ "raised_back_of_hand_tone3": {
+ "category": "people",
+ "moji": "🤚🏽",
+ "unicodeVersion": "9.0",
"digest": "487a1c3f19e77c99b520ec073de2acc4a9e585b739a84b3989f7de85d2c2045c"
},
- {
- "name": "raised_back_of_hand_tone4",
- "unicode": "1F91A-1F3FE",
- "digest": "154254d8500c55ec3de698be4a352f9bcf06e2950cabc4eabaccad0f39a1e1e9"
- },
- {
- "name": "back_of_hand_tone4",
- "unicode": "1F91A-1F3FE",
+ "raised_back_of_hand_tone4": {
+ "category": "people",
+ "moji": "🤚🏾",
+ "unicodeVersion": "9.0",
"digest": "154254d8500c55ec3de698be4a352f9bcf06e2950cabc4eabaccad0f39a1e1e9"
},
- {
- "name": "raised_back_of_hand_tone5",
- "unicode": "1F91A-1F3FF",
- "digest": "6e9c0855ecd5f14adca5e5862427c3d39ffcf86f7ddd3aaa1fefc3cefc7483c8"
- },
- {
- "name": "back_of_hand_tone5",
- "unicode": "1F91A-1F3FF",
+ "raised_back_of_hand_tone5": {
+ "category": "people",
+ "moji": "🤚🏿",
+ "unicodeVersion": "9.0",
"digest": "6e9c0855ecd5f14adca5e5862427c3d39ffcf86f7ddd3aaa1fefc3cefc7483c8"
},
- {
- "name": "raised_hand",
- "unicode": "270B",
+ "raised_hand": {
+ "category": "people",
+ "moji": "✋",
+ "unicodeVersion": "6.0",
"digest": "5cf11be683aea985d5ba51fbd44722c2327311bfe26b61c3d441c90f5d5a195a"
},
- {
- "name": "raised_hand_tone1",
- "unicode": "270B-1F3FB",
+ "raised_hand_tone1": {
+ "category": "people",
+ "moji": "✋🏻",
+ "unicodeVersion": "8.0",
"digest": "865afca29b57577fed8fe8c2be57b74254a008c8cf34194680be2759239b5f5d"
},
- {
- "name": "raised_hand_tone2",
- "unicode": "270B-1F3FC",
+ "raised_hand_tone2": {
+ "category": "people",
+ "moji": "✋🏼",
+ "unicodeVersion": "8.0",
"digest": "832169a0b626a682a58a3b998f68413657b4962c1fab05f1fdc2668e82727210"
},
- {
- "name": "raised_hand_tone3",
- "unicode": "270B-1F3FD",
+ "raised_hand_tone3": {
+ "category": "people",
+ "moji": "✋🏽",
+ "unicodeVersion": "8.0",
"digest": "3959a873ad7671de82c615c4ed840b011e67baafb2bab7dd16859608d3e83cb1"
},
- {
- "name": "raised_hand_tone4",
- "unicode": "270B-1F3FE",
+ "raised_hand_tone4": {
+ "category": "people",
+ "moji": "✋🏾",
+ "unicodeVersion": "8.0",
"digest": "db542f65d076ccf3dbfca27cb7c2f135a8bf7a487a81a04873e70172bdfcd579"
},
- {
- "name": "raised_hand_tone5",
- "unicode": "270B-1F3FF",
+ "raised_hand_tone5": {
+ "category": "people",
+ "moji": "✋🏿",
+ "unicodeVersion": "8.0",
"digest": "88ca884d14baaae48df21d75c22d82fb15bdc395e42026f5ca34cd65e5ae8674"
},
- {
- "name": "raised_hands",
- "unicode": "1F64C",
+ "raised_hands": {
+ "category": "people",
+ "moji": "🙌",
+ "unicodeVersion": "6.0",
"digest": "2ee73466a3f5079e542857fe6f5497e9f87753a81854985ce3356a8d3da1d8b8"
},
- {
- "name": "raised_hands_tone1",
- "unicode": "1F64C-1F3FB",
+ "raised_hands_tone1": {
+ "category": "people",
+ "moji": "🙌🏻",
+ "unicodeVersion": "8.0",
"digest": "43e73c60f040a66374b8ec98f3629a90d13ae9f472446ed7676cd5573e824f4b"
},
- {
- "name": "raised_hands_tone2",
- "unicode": "1F64C-1F3FC",
+ "raised_hands_tone2": {
+ "category": "people",
+ "moji": "🙌🏼",
+ "unicodeVersion": "8.0",
"digest": "fcc5255bb2b06dc82d6878e74cf34e8ce118c70004a06d39a980683772b98c52"
},
- {
- "name": "raised_hands_tone3",
- "unicode": "1F64C-1F3FD",
+ "raised_hands_tone3": {
+ "category": "people",
+ "moji": "🙌🏽",
+ "unicodeVersion": "8.0",
"digest": "3ee3e0aafef486e766a166935e8147fb75a7329cfebc96dec876cc45e83a8754"
},
- {
- "name": "raised_hands_tone4",
- "unicode": "1F64C-1F3FE",
+ "raised_hands_tone4": {
+ "category": "people",
+ "moji": "🙌🏾",
+ "unicodeVersion": "8.0",
"digest": "78a8cbf6b2b85be4d6b18f0ff6a77f197963117955725fb7e57e0441effb928f"
},
- {
- "name": "raised_hands_tone5",
- "unicode": "1F64C-1F3FF",
+ "raised_hands_tone5": {
+ "category": "people",
+ "moji": "🙌🏿",
+ "unicodeVersion": "8.0",
"digest": "2a5ed7334a17172db0cd820a559e7f75df40ec44de6c25d194c76e1b58c634cb"
},
- {
- "name": "raising_hand",
- "unicode": "1F64B",
+ "raising_hand": {
+ "category": "people",
+ "moji": "🙋",
+ "unicodeVersion": "6.0",
"digest": "512750b00704f1ccefd3c757743540b785ad7670dbbe4a2c4dca8d93e6701920"
},
- {
- "name": "raising_hand_tone1",
- "unicode": "1F64B-1F3FB",
+ "raising_hand_tone1": {
+ "category": "people",
+ "moji": "🙋🏻",
+ "unicodeVersion": "8.0",
"digest": "2897722f091c273dd3714cff7423c2475bc3070416c28014ca03322b9ece48bc"
},
- {
- "name": "raising_hand_tone2",
- "unicode": "1F64B-1F3FC",
+ "raising_hand_tone2": {
+ "category": "people",
+ "moji": "🙋🏼",
+ "unicodeVersion": "8.0",
"digest": "59199b334b3845911382c1f29bd7c0d5ef9d2486417345e265b166ead7d3e1c1"
},
- {
- "name": "raising_hand_tone3",
- "unicode": "1F64B-1F3FD",
+ "raising_hand_tone3": {
+ "category": "people",
+ "moji": "🙋🏽",
+ "unicodeVersion": "8.0",
"digest": "f95b338d5efcf14ef12f415a2c1bba93df48628ddc94f34f70c31e1b3c2e1d28"
},
- {
- "name": "raising_hand_tone4",
- "unicode": "1F64B-1F3FE",
+ "raising_hand_tone4": {
+ "category": "people",
+ "moji": "🙋🏾",
+ "unicodeVersion": "8.0",
"digest": "951ddbfdb57d5a60551b59b3d0f7ca00a64912f4a101a73afaebd68445cd6cec"
},
- {
- "name": "raising_hand_tone5",
- "unicode": "1F64B-1F3FF",
+ "raising_hand_tone5": {
+ "category": "people",
+ "moji": "🙋🏿",
+ "unicodeVersion": "8.0",
"digest": "9370f93704d8f89ca6dc946715eab5e7dba82bf04dd68c00f5c0abb8bc16371e"
},
- {
- "name": "ram",
- "unicode": "1F40F",
+ "ram": {
+ "category": "nature",
+ "moji": "🐏",
+ "unicodeVersion": "6.0",
"digest": "2875ab28e1018b39062aeb0c5ce488c48a98f13e9f2364470a0a700b126604f2"
},
- {
- "name": "ramen",
- "unicode": "1F35C",
+ "ramen": {
+ "category": "food",
+ "moji": "🍜",
+ "unicodeVersion": "6.0",
"digest": "425662a49c4c13577c0de8d45d004e5ba204aaadbaabae62a5c283ecd7a9a2c5"
},
- {
- "name": "rat",
- "unicode": "1F400",
+ "rat": {
+ "category": "nature",
+ "moji": "🐀",
+ "unicodeVersion": "6.0",
"digest": "14380d65498c6ce037c02a93bca2b24f25a368d85278d6015b8c9f7cd261f8e2"
},
- {
- "name": "record_button",
- "unicode": "23FA",
+ "record_button": {
+ "category": "symbols",
+ "moji": "⏺",
+ "unicodeVersion": "7.0",
"digest": "92be12161ba206bb2e06a39131711c7b17368d55b4aae0b48f0ac5b6b1cde76b"
},
- {
- "name": "recycle",
- "unicode": "267B",
+ "recycle": {
+ "category": "symbols",
+ "moji": "♻",
+ "unicodeVersion": "3.2",
"digest": "c377e8537367b05b5de9be860a0fcabd7aed2bf4ba146eefc423671a21530369"
},
- {
- "name": "red_car",
- "unicode": "1F697",
+ "red_car": {
+ "category": "travel",
+ "moji": "🚗",
+ "unicodeVersion": "6.0",
"digest": "8a99832a195263c0e922af53d52dea37aa3e07032b3c2a1977f8527b4a144b9c"
},
- {
- "name": "red_circle",
- "unicode": "1F534",
+ "red_circle": {
+ "category": "symbols",
+ "moji": "🔴",
+ "unicodeVersion": "6.0",
"digest": "9dcf0132f6f2cc81702f0e3b15b37984e8439796705bf98f68ba449b3dfa5307"
},
- {
- "name": "registered",
- "unicode": "00AE",
+ "registered": {
+ "category": "symbols",
+ "moji": "®",
+ "unicodeVersion": "1.1",
"digest": "9661b1df529ecb752d130820c55c403e5de263748eb02f7fea327818bc282d94"
},
- {
- "name": "relaxed",
- "unicode": "263A",
+ "relaxed": {
+ "category": "people",
+ "moji": "☺",
+ "unicodeVersion": "1.1",
"digest": "2d5aed4fb8504c6d6660ef8d3bfe0cc053dcd6099c2f53748c202dc970c639bc"
},
- {
- "name": "relieved",
- "unicode": "1F60C",
+ "relieved": {
+ "category": "people",
+ "moji": "😌",
+ "unicodeVersion": "6.0",
"digest": "b4ce2ba6c220d887fe5e333c05ed773df9b6df0ac456879fd8f5103ff68604a5"
},
- {
- "name": "reminder_ribbon",
- "unicode": "1F397",
+ "reminder_ribbon": {
+ "category": "activity",
+ "moji": "🎗",
+ "unicodeVersion": "7.0",
"digest": "c3de2a7c9350b77a0b86c0dcce9dcd9953ea8a97aa1e7aed149755924742f54d"
},
- {
- "name": "repeat",
- "unicode": "1F501",
+ "repeat": {
+ "category": "symbols",
+ "moji": "🔁",
+ "unicodeVersion": "6.0",
"digest": "b9512d508613ed0eb3181eb1030f7f6fd6b994476ecdfa308733c6df975fb99e"
},
- {
- "name": "repeat_one",
- "unicode": "1F502",
+ "repeat_one": {
+ "category": "symbols",
+ "moji": "🔂",
+ "unicodeVersion": "6.0",
"digest": "53409cf24dd4bb0d7b50ae359f15d06b87b7f4a292ed5c3a09652fa421a90bf2"
},
- {
- "name": "restroom",
- "unicode": "1F6BB",
+ "restroom": {
+ "category": "symbols",
+ "moji": "🚻",
+ "unicodeVersion": "6.0",
"digest": "2e7a1bfc9a9d49b0272230a91db7369e24d54bf1de8e683d36b85f1d8c037f77"
},
- {
- "name": "revolving_hearts",
- "unicode": "1F49E",
+ "revolving_hearts": {
+ "category": "symbols",
+ "moji": "💞",
+ "unicodeVersion": "6.0",
"digest": "c43d3197cb4cf06659f643638f6c4e91a2889e0f6531b7d81ea826c2a8b784fc"
},
- {
- "name": "rewind",
- "unicode": "23EA",
+ "rewind": {
+ "category": "symbols",
+ "moji": "⏪",
+ "unicodeVersion": "6.0",
"digest": "d20c918c1e528ff0947312738501ca9a6fb6ff4016aad07db7a8125d00fd65cd"
},
- {
- "name": "rhino",
- "unicode": "1F98F",
- "digest": "163fa3acd78eead72c431a1f48b8465a6d45272a9169560e456d30b4df93dc6b"
- },
- {
- "name": "rhinoceros",
- "unicode": "1F98F",
+ "rhino": {
+ "category": "nature",
+ "moji": "🦏",
+ "unicodeVersion": "9.0",
"digest": "163fa3acd78eead72c431a1f48b8465a6d45272a9169560e456d30b4df93dc6b"
},
- {
- "name": "ribbon",
- "unicode": "1F380",
+ "ribbon": {
+ "category": "objects",
+ "moji": "🎀",
+ "unicodeVersion": "6.0",
"digest": "74315fe907f9f0203afe139cd4552aa442eecfa2a64fac12db3e1292fc5a8828"
},
- {
- "name": "rice",
- "unicode": "1F35A",
+ "rice": {
+ "category": "food",
+ "moji": "🍚",
+ "unicodeVersion": "6.0",
"digest": "f544f12606de59d28739798003f14ebd8869856add8e24496ec5dda3e131daf4"
},
- {
- "name": "rice_ball",
- "unicode": "1F359",
+ "rice_ball": {
+ "category": "food",
+ "moji": "🍙",
+ "unicodeVersion": "6.0",
"digest": "2cba6f5364cd366859bc8948897b65fc97b225ea7973d9be3b24aba388fed8e8"
},
- {
- "name": "rice_cracker",
- "unicode": "1F358",
+ "rice_cracker": {
+ "category": "food",
+ "moji": "🍘",
+ "unicodeVersion": "6.0",
"digest": "ac0f805d41d4f322154c1968bd3ce3e9aabcd39d908182e52fd7d28458dbef92"
},
- {
- "name": "rice_scene",
- "unicode": "1F391",
+ "rice_scene": {
+ "category": "travel",
+ "moji": "🎑",
+ "unicodeVersion": "6.0",
"digest": "b942a06d3da0570aca59bab0af57cd8c16863934f12a38f70339fd0a36f675f5"
},
- {
- "name": "right_facing_fist",
- "unicode": "1F91C",
- "digest": "f815d1cc0c0345ddcc8886ae9c133582d7dc779732ac9b93dde1ab4fdd3b251d"
- },
- {
- "name": "right_fist",
- "unicode": "1F91C",
+ "right_facing_fist": {
+ "category": "people",
+ "moji": "🤜",
+ "unicodeVersion": "9.0",
"digest": "f815d1cc0c0345ddcc8886ae9c133582d7dc779732ac9b93dde1ab4fdd3b251d"
},
- {
- "name": "right_facing_fist_tone1",
- "unicode": "1F91C-1F3FB",
- "digest": "0f9269b70cf68071d97389e059a2bdacffd73f2afd2ce6cfd7447bb1a4e9abbb"
- },
- {
- "name": "right_fist_tone1",
- "unicode": "1F91C-1F3FB",
+ "right_facing_fist_tone1": {
+ "category": "people",
+ "moji": "🤜🏻",
+ "unicodeVersion": "9.0",
"digest": "0f9269b70cf68071d97389e059a2bdacffd73f2afd2ce6cfd7447bb1a4e9abbb"
},
- {
- "name": "right_facing_fist_tone2",
- "unicode": "1F91C-1F3FC",
- "digest": "32a9833db853972e49e65aa227fb0512c57362da190aa1cc40e1d64f238e837e"
- },
- {
- "name": "right_fist_tone2",
- "unicode": "1F91C-1F3FC",
+ "right_facing_fist_tone2": {
+ "category": "people",
+ "moji": "🤜🏼",
+ "unicodeVersion": "9.0",
"digest": "32a9833db853972e49e65aa227fb0512c57362da190aa1cc40e1d64f238e837e"
},
- {
- "name": "right_facing_fist_tone3",
- "unicode": "1F91C-1F3FD",
- "digest": "be4706f8bb088411f5cbbf9065a0ae5b773c97456bd975c2b6789765657847b9"
- },
- {
- "name": "right_fist_tone3",
- "unicode": "1F91C-1F3FD",
+ "right_facing_fist_tone3": {
+ "category": "people",
+ "moji": "🤜🏽",
+ "unicodeVersion": "9.0",
"digest": "be4706f8bb088411f5cbbf9065a0ae5b773c97456bd975c2b6789765657847b9"
},
- {
- "name": "right_facing_fist_tone4",
- "unicode": "1F91C-1F3FE",
+ "right_facing_fist_tone4": {
+ "category": "people",
+ "moji": "🤜🏾",
+ "unicodeVersion": "9.0",
"digest": "1680862891a9d85c4b6f76232a80e2ef7428bcec93087c86eae2efaba9c6a3f7"
},
- {
- "name": "right_fist_tone4",
- "unicode": "1F91C-1F3FE",
- "digest": "1680862891a9d85c4b6f76232a80e2ef7428bcec93087c86eae2efaba9c6a3f7"
- },
- {
- "name": "right_facing_fist_tone5",
- "unicode": "1F91C-1F3FF",
- "digest": "388715a4bc2178c52bbb3bc2729f57be50acab5d751784c9f3220e86c6b1fbcc"
- },
- {
- "name": "right_fist_tone5",
- "unicode": "1F91C-1F3FF",
+ "right_facing_fist_tone5": {
+ "category": "people",
+ "moji": "🤜🏿",
+ "unicodeVersion": "9.0",
"digest": "388715a4bc2178c52bbb3bc2729f57be50acab5d751784c9f3220e86c6b1fbcc"
},
- {
- "name": "ring",
- "unicode": "1F48D",
+ "ring": {
+ "category": "people",
+ "moji": "💍",
+ "unicodeVersion": "6.0",
"digest": "b5322907222797b5e1786209cda88513e76cd397a40f0a7da24847245c95ef9d"
},
- {
- "name": "robot",
- "unicode": "1F916",
+ "robot": {
+ "category": "people",
+ "moji": "🤖",
+ "unicodeVersion": "8.0",
"digest": "4d788e6ec89279588b036fca6b17f5a981291681df8f90306ecf5c039de40848"
},
- {
- "name": "robot_face",
- "unicode": "1F916",
- "digest": "4d788e6ec89279588b036fca6b17f5a981291681df8f90306ecf5c039de40848"
- },
- {
- "name": "rocket",
- "unicode": "1F680",
+ "rocket": {
+ "category": "travel",
+ "moji": "🚀",
+ "unicodeVersion": "6.0",
"digest": "b82e68a95aa89a6de344d6e256fef86a848ebc91de560b043b3e1f7fd072d57d"
},
- {
- "name": "rofl",
- "unicode": "1F923",
- "digest": "f4f99ba2ac67b97338f904f9384ff03fb832a2e427bf6e74611bf5fee45f1f48"
- },
- {
- "name": "rolling_on_the_floor_laughing",
- "unicode": "1F923",
+ "rofl": {
+ "category": "people",
+ "moji": "🤣",
+ "unicodeVersion": "9.0",
"digest": "f4f99ba2ac67b97338f904f9384ff03fb832a2e427bf6e74611bf5fee45f1f48"
},
- {
- "name": "roller_coaster",
- "unicode": "1F3A2",
+ "roller_coaster": {
+ "category": "travel",
+ "moji": "🎢",
+ "unicodeVersion": "6.0",
"digest": "a65e9ace1d7900499777af1225995f17af90a398bb414764c20b6e09a8c23a2c"
},
- {
- "name": "rolling_eyes",
- "unicode": "1F644",
+ "rolling_eyes": {
+ "category": "people",
+ "moji": "🙄",
+ "unicodeVersion": "8.0",
"digest": "23dea8100da488a05721a4e82823eb438393b0ea762211c9ecef011d127aa1b7"
},
- {
- "name": "face_with_rolling_eyes",
- "unicode": "1F644",
- "digest": "23dea8100da488a05721a4e82823eb438393b0ea762211c9ecef011d127aa1b7"
- },
- {
- "name": "rooster",
- "unicode": "1F413",
+ "rooster": {
+ "category": "nature",
+ "moji": "🐓",
+ "unicodeVersion": "6.0",
"digest": "2b90c5cf6fa46da13eb77285443d600afcea0c48bd1d215d60167e7dc510da5d"
},
- {
- "name": "rose",
- "unicode": "1F339",
+ "rose": {
+ "category": "nature",
+ "moji": "🌹",
+ "unicodeVersion": "6.0",
"digest": "73799e459dba188de4de704605d824242feeb65d587c5bf9109acf528d037146"
},
- {
- "name": "rosette",
- "unicode": "1F3F5",
+ "rosette": {
+ "category": "activity",
+ "moji": "🏵",
+ "unicodeVersion": "7.0",
"digest": "2537def4deef422d4e669b28b1a0675259306ab38601019df3ec3482b14e52d5"
},
- {
- "name": "rotating_light",
- "unicode": "1F6A8",
+ "rotating_light": {
+ "category": "travel",
+ "moji": "🚨",
+ "unicodeVersion": "6.0",
"digest": "91fcdb85a752ae904d335a978c7e7936aed4c75d414b35219b5a74430e51555f"
},
- {
- "name": "round_pushpin",
- "unicode": "1F4CD",
+ "round_pushpin": {
+ "category": "objects",
+ "moji": "📍",
+ "unicodeVersion": "6.0",
"digest": "8ffca77bbdc6f1f726daf3abd6eff338a5ad1aa9b09dbbd8782c1e7ef5452f30"
},
- {
- "name": "rowboat",
- "unicode": "1F6A3",
+ "rowboat": {
+ "category": "activity",
+ "moji": "🚣",
+ "unicodeVersion": "6.0",
"digest": "83715d83a061926d4ad3bb569b21f5d337e3ebd4c9bcdfe493e661c12adc0a16"
},
- {
- "name": "rowboat_tone1",
- "unicode": "1F6A3-1F3FB",
+ "rowboat_tone1": {
+ "category": "activity",
+ "moji": "🚣🏻",
+ "unicodeVersion": "8.0",
"digest": "e279ac816442c0876fba1f42c700b80f2fb6de671e1a8a9e9d11b71eed5c58e8"
},
- {
- "name": "rowboat_tone2",
- "unicode": "1F6A3-1F3FC",
+ "rowboat_tone2": {
+ "category": "activity",
+ "moji": "🚣🏼",
+ "unicodeVersion": "8.0",
"digest": "6a48eba352ed4971d26498b6c622e5772389c89c5205ed02acde8e995dddcc3b"
},
- {
- "name": "rowboat_tone3",
- "unicode": "1F6A3-1F3FD",
+ "rowboat_tone3": {
+ "category": "activity",
+ "moji": "🚣🏽",
+ "unicodeVersion": "8.0",
"digest": "875948f6d8354ebd95ce9a66fde30f06a8366dcd89d5ca3e660845f8801e9305"
},
- {
- "name": "rowboat_tone4",
- "unicode": "1F6A3-1F3FE",
+ "rowboat_tone4": {
+ "category": "activity",
+ "moji": "🚣🏾",
+ "unicodeVersion": "8.0",
"digest": "8c7ac7346b0020d0ff5e2f4a1efb1b7785eac637f17556663ec33e2335083f0a"
},
- {
- "name": "rowboat_tone5",
- "unicode": "1F6A3-1F3FF",
+ "rowboat_tone5": {
+ "category": "activity",
+ "moji": "🚣🏿",
+ "unicodeVersion": "8.0",
"digest": "a399dbb647892b22323e0bf17bc36a9b5f1708ebedf9ba525233ee7b9d48339a"
},
- {
- "name": "rugby_football",
- "unicode": "1F3C9",
+ "rugby_football": {
+ "category": "activity",
+ "moji": "🏉",
+ "unicodeVersion": "6.0",
"digest": "cc6f00ade3e0bbb7899e7bfb138b57216dd66de26d7967d5ffa501f382ed09f4"
},
- {
- "name": "runner",
- "unicode": "1F3C3",
+ "runner": {
+ "category": "people",
+ "moji": "🏃",
+ "unicodeVersion": "6.0",
"digest": "e9af7b591be60ade2049dbada0f062ba2d3e17f02bec76cbd34ce68854a2a10c"
},
- {
- "name": "runner_tone1",
- "unicode": "1F3C3-1F3FB",
+ "runner_tone1": {
+ "category": "people",
+ "moji": "🏃🏻",
+ "unicodeVersion": "8.0",
"digest": "21091cbb09c558712ecf63548bf28b7995df42bdb85235088799a517800e52f5"
},
- {
- "name": "runner_tone2",
- "unicode": "1F3C3-1F3FC",
+ "runner_tone2": {
+ "category": "people",
+ "moji": "🏃🏼",
+ "unicodeVersion": "8.0",
"digest": "1fe3d194f675a46fe67799394192e66c407dd81163363692c5e7da32ddb9af2b"
},
- {
- "name": "runner_tone3",
- "unicode": "1F3C3-1F3FD",
+ "runner_tone3": {
+ "category": "people",
+ "moji": "🏃🏽",
+ "unicodeVersion": "8.0",
"digest": "8cea1bf4ef3be71f42dc5bae978d5b7a197a3851543225349ef0dda29a370537"
},
- {
- "name": "runner_tone4",
- "unicode": "1F3C3-1F3FE",
+ "runner_tone4": {
+ "category": "people",
+ "moji": "🏃🏾",
+ "unicodeVersion": "8.0",
"digest": "c33f0b8b5a71d295fb6ba322e79446964a8eca9e4573efd591e4273808b088a0"
},
- {
- "name": "runner_tone5",
- "unicode": "1F3C3-1F3FF",
+ "runner_tone5": {
+ "category": "people",
+ "moji": "🏃🏿",
+ "unicodeVersion": "8.0",
"digest": "9f59e6dd0fdf2f17bceb41f5c355b4e6f3c8bb8cbd8af0992f0b5630ff8892e8"
},
- {
- "name": "running_shirt_with_sash",
- "unicode": "1F3BD",
+ "running_shirt_with_sash": {
+ "category": "activity",
+ "moji": "🎽",
+ "unicodeVersion": "6.0",
"digest": "7542307d3595aca45e8ccae66b6e58b6e92870144b738263d5379ec6dc992b76"
},
- {
- "name": "sa",
- "unicode": "1F202",
+ "sa": {
+ "category": "symbols",
+ "moji": "🈂",
+ "unicodeVersion": "6.0",
"digest": "6042bcabd1516ef3847d695aba22851c49421244432d256e24eba04e8a223dab"
},
- {
- "name": "sagittarius",
- "unicode": "2650",
+ "sagittarius": {
+ "category": "symbols",
+ "moji": "♐",
+ "unicodeVersion": "1.1",
"digest": "a02593e025023f2e82a01c587a8c0bbb1eff88cbcabf535a1558413eb32ed1d5"
},
- {
- "name": "sailboat",
- "unicode": "26F5",
+ "sailboat": {
+ "category": "travel",
+ "moji": "⛵",
+ "unicodeVersion": "5.2",
"digest": "c95ef4dc939cbdcb757ef3cd90331310e8c0a426add8cc800bae2540148a3195"
},
- {
- "name": "sake",
- "unicode": "1F376",
+ "sake": {
+ "category": "food",
+ "moji": "🍶",
+ "unicodeVersion": "6.0",
"digest": "0a786075f3d9da48ae91afccf6ae0d097888da9509d354ee1d3cb99afcc88fe4"
},
- {
- "name": "salad",
- "unicode": "1F957",
- "digest": "fe321487ab847abe670e68a83f1d9e096129741c689c769ee7de4a65aeac29f8"
- },
- {
- "name": "green_salad",
- "unicode": "1F957",
+ "salad": {
+ "category": "food",
+ "moji": "🥗",
+ "unicodeVersion": "9.0",
"digest": "fe321487ab847abe670e68a83f1d9e096129741c689c769ee7de4a65aeac29f8"
},
- {
- "name": "sandal",
- "unicode": "1F461",
+ "sandal": {
+ "category": "people",
+ "moji": "👡",
+ "unicodeVersion": "6.0",
"digest": "03c3077cb4bd900934f9bdf921165b465e5cc9a6bee53e45a091411bceb8892d"
},
- {
- "name": "santa",
- "unicode": "1F385",
+ "santa": {
+ "category": "people",
+ "moji": "🎅",
+ "unicodeVersion": "6.0",
"digest": "178513e3d815917e59958870f5885b3414b43a16b8056980c863a468dfe00179"
},
- {
- "name": "santa_tone1",
- "unicode": "1F385-1F3FB",
+ "santa_tone1": {
+ "category": "people",
+ "moji": "🎅🏻",
+ "unicodeVersion": "8.0",
"digest": "bf900bbc19bbd329229add9326e28e8197b69d6ddceb69f42162b0200fde5d16"
},
- {
- "name": "santa_tone2",
- "unicode": "1F385-1F3FC",
+ "santa_tone2": {
+ "category": "people",
+ "moji": "🎅🏼",
+ "unicodeVersion": "8.0",
"digest": "7340f2171adab97198e3eecac8b0d84c4c2a41f84606301a0d10e9fe655c93d1"
},
- {
- "name": "santa_tone3",
- "unicode": "1F385-1F3FD",
+ "santa_tone3": {
+ "category": "people",
+ "moji": "🎅🏽",
+ "unicodeVersion": "8.0",
"digest": "7368ab75454ec28d8f7d6baef6ad69b5278445a9f50753f6624731bffde32054"
},
- {
- "name": "santa_tone4",
- "unicode": "1F385-1F3FE",
+ "santa_tone4": {
+ "category": "people",
+ "moji": "🎅🏾",
+ "unicodeVersion": "8.0",
"digest": "0ee60188353e0ee7772079c192bebbc6d49e74e63906f840c66da4eb35f4f245"
},
- {
- "name": "santa_tone5",
- "unicode": "1F385-1F3FF",
+ "santa_tone5": {
+ "category": "people",
+ "moji": "🎅🏿",
+ "unicodeVersion": "8.0",
"digest": "e4378a0cc5d21e9b9fe6e35c32d1ebc6fb8c2e1c09554cd096aeaefd3a6eb511"
},
- {
- "name": "satellite",
- "unicode": "1F4E1",
+ "satellite": {
+ "category": "objects",
+ "moji": "📡",
+ "unicodeVersion": "6.0",
"digest": "c9d63118dcb445856917bb080460ab695cc78e715dcbba30ba18dffa9e906b27"
},
- {
- "name": "satellite_orbital",
- "unicode": "1F6F0",
+ "satellite_orbital": {
+ "category": "travel",
+ "moji": "🛰",
+ "unicodeVersion": "7.0",
"digest": "beb2f50e7f2b010e76bed9daa95d7329a93c783d3ebc4f0b797dd721c5e3d32d"
},
- {
- "name": "saxophone",
- "unicode": "1F3B7",
+ "saxophone": {
+ "category": "activity",
+ "moji": "🎷",
+ "unicodeVersion": "6.0",
"digest": "dfd138634f6702a3b89b5a2a50016720eef3f800b0d1d8c9fe097808c9491e96"
},
- {
- "name": "scales",
- "unicode": "2696",
+ "scales": {
+ "category": "objects",
+ "moji": "⚖",
+ "unicodeVersion": "4.1",
"digest": "2280c026f16c6b92e0daa00bc14e718770f8d231c571ab439bde84d837cf31cc"
},
- {
- "name": "school",
- "unicode": "1F3EB",
+ "school": {
+ "category": "travel",
+ "moji": "🏫",
+ "unicodeVersion": "6.0",
"digest": "af198b068a86ccad3daec4c6873e6b4735086c1ecbb3848182e70bae9aa3ee24"
},
- {
- "name": "school_satchel",
- "unicode": "1F392",
+ "school_satchel": {
+ "category": "people",
+ "moji": "🎒",
+ "unicodeVersion": "6.0",
"digest": "f670ae8aea67eb9d8aaa0bf2748c1cc3e503dcc1dbe999133afcdf21af046b24"
},
- {
- "name": "scissors",
- "unicode": "2702",
+ "scissors": {
+ "category": "objects",
+ "moji": "✂",
+ "unicodeVersion": "1.1",
"digest": "95225be28f05d8b5a6b6e6bf58d973f61f183ad4fef55a558dc1b810796b85c8"
},
- {
- "name": "scooter",
- "unicode": "1F6F4",
+ "scooter": {
+ "category": "travel",
+ "moji": "🛴",
+ "unicodeVersion": "9.0",
"digest": "4a7db148880398db75e059711cb53edefb6b8fa9d442009f52856b887ab1dde4"
},
- {
- "name": "scorpion",
- "unicode": "1F982",
+ "scorpion": {
+ "category": "nature",
+ "moji": "🦂",
+ "unicodeVersion": "8.0",
"digest": "d41119d1ea5daf727c17dbea7dadec1718c72fc9f98ae88252161df5fde0938a"
},
- {
- "name": "scorpius",
- "unicode": "264F",
+ "scorpius": {
+ "category": "symbols",
+ "moji": "♏",
+ "unicodeVersion": "1.1",
"digest": "a36404b408814c2ecb8fa8b61f5c5432dfcf54cae8c09cc67b8d0fadf7cbdc03"
},
- {
- "name": "scream",
- "unicode": "1F631",
+ "scream": {
+ "category": "people",
+ "moji": "😱",
+ "unicodeVersion": "6.0",
"digest": "916e4903a4b694da4b00f190f872a4e100e7736b7a2e6171fa1636f46bf646e6"
},
- {
- "name": "scream_cat",
- "unicode": "1F640",
+ "scream_cat": {
+ "category": "people",
+ "moji": "🙀",
+ "unicodeVersion": "6.0",
"digest": "f1d3a6ff538064e7d5e0321bbc33aba44e8da703dc1894ef1403c0cd6d63d781"
},
- {
- "name": "scroll",
- "unicode": "1F4DC",
+ "scroll": {
+ "category": "objects",
+ "moji": "📜",
+ "unicodeVersion": "6.0",
"digest": "9b2cb00860bcc2d20017cafb2ed9681b6232dc07273d489d75d53ce29e4ba3ab"
},
- {
- "name": "seat",
- "unicode": "1F4BA",
+ "seat": {
+ "category": "travel",
+ "moji": "💺",
+ "unicodeVersion": "6.0",
"digest": "ae68d86fc2a07cae332451b23bd1ceba3f6526a6c56d8c1089777fa4632850e1"
},
- {
- "name": "second_place",
- "unicode": "1F948",
+ "second_place": {
+ "category": "activity",
+ "moji": "🥈",
+ "unicodeVersion": "9.0",
"digest": "9e2336fc16e532829b55380252f94655b58817d47c909fc2570002c5b06b9c40"
},
- {
- "name": "second_place_medal",
- "unicode": "1F948",
- "digest": "9e2336fc16e532829b55380252f94655b58817d47c909fc2570002c5b06b9c40"
- },
- {
- "name": "secret",
- "unicode": "3299",
+ "secret": {
+ "category": "symbols",
+ "moji": "㊙",
+ "unicodeVersion": "1.1",
"digest": "1d0b9adde2657f41421b135962de20820cf4b4eb0204044f9859522ab9d211b0"
},
- {
- "name": "see_no_evil",
- "unicode": "1F648",
+ "see_no_evil": {
+ "category": "nature",
+ "moji": "🙈",
+ "unicodeVersion": "6.0",
"digest": "3ff66d2e84b36d071d0a34f8e41cfd620a56b83131474ea50ed7803b635551ed"
},
- {
- "name": "seedling",
- "unicode": "1F331",
+ "seedling": {
+ "category": "nature",
+ "moji": "🌱",
+ "unicodeVersion": "6.0",
"digest": "c0ec5e6d20e1afdc4e78eeddb1301c8b708ad6278e7287a4e4e825417c858e75"
},
- {
- "name": "selfie",
- "unicode": "1F933",
+ "selfie": {
+ "category": "people",
+ "moji": "🤳",
+ "unicodeVersion": "9.0",
"digest": "2a1bc9f18ad4d6fb893d91c88ef1b2d9bd063dc2bb1a4b08c248c30f52545d4e"
},
- {
- "name": "selfie_tone1",
- "unicode": "1F933-1F3FB",
+ "selfie_tone1": {
+ "category": "people",
+ "moji": "🤳🏻",
+ "unicodeVersion": "9.0",
"digest": "26dc212ffed30c276bd6a66a72bc4513e68098a2205fb4ca5b51ccfa1de5b544"
},
- {
- "name": "selfie_tone2",
- "unicode": "1F933-1F3FC",
+ "selfie_tone2": {
+ "category": "people",
+ "moji": "🤳🏼",
+ "unicodeVersion": "9.0",
"digest": "71eceaefda46e3521f374f76693e7fa8f215067498067900080e2925ca94d7de"
},
- {
- "name": "selfie_tone3",
- "unicode": "1F933-1F3FD",
+ "selfie_tone3": {
+ "category": "people",
+ "moji": "🤳🏽",
+ "unicodeVersion": "9.0",
"digest": "53eabbd4f6b8ebbd2f7af7bf5cd64309c4039ac1c5b2180290a547deaafcebdf"
},
- {
- "name": "selfie_tone4",
- "unicode": "1F933-1F3FE",
+ "selfie_tone4": {
+ "category": "people",
+ "moji": "🤳🏾",
+ "unicodeVersion": "9.0",
"digest": "0baad378b09652b99c5d458db2e03b4db14a1557db4ea0969806a0ca1d33d40c"
},
- {
- "name": "selfie_tone5",
- "unicode": "1F933-1F3FF",
+ "selfie_tone5": {
+ "category": "people",
+ "moji": "🤳🏿",
+ "unicodeVersion": "9.0",
"digest": "9a07608f34ec4dad48764a855f83f3965709d7b2fd2342e6dc9ed61f23f4adfd"
},
- {
- "name": "seven",
- "unicode": "0037-20E3",
+ "seven": {
+ "category": "symbols",
+ "moji": "7️⃣",
+ "unicodeVersion": "3.0",
"digest": "ae85172d2c76c44afb4e3b45d277d400abb2dc895244b9abfbd1dac1cd7c53c2"
},
- {
- "name": "shallow_pan_of_food",
- "unicode": "1F958",
+ "shallow_pan_of_food": {
+ "category": "food",
+ "moji": "🥘",
+ "unicodeVersion": "9.0",
"digest": "7c7ad9d5d3f7226427d310b5853e8257fad899febe58dcbc5adb4677964f5c6d"
},
- {
- "name": "paella",
- "unicode": "1F958",
- "digest": "7c7ad9d5d3f7226427d310b5853e8257fad899febe58dcbc5adb4677964f5c6d"
- },
- {
- "name": "shamrock",
- "unicode": "2618",
+ "shamrock": {
+ "category": "nature",
+ "moji": "☘",
+ "unicodeVersion": "4.1",
"digest": "68ed70c26e04a818439a1742d2da6bc169edd02db86b6e6f8014b651f3235488"
},
- {
- "name": "shark",
- "unicode": "1F988",
+ "shark": {
+ "category": "nature",
+ "moji": "🦈",
+ "unicodeVersion": "9.0",
"digest": "23a2364b6356e7bbb84c138e9cf58e2c68cd8caabb337a0c4d365ce87bf5d2da"
},
- {
- "name": "shaved_ice",
- "unicode": "1F367",
+ "shaved_ice": {
+ "category": "food",
+ "moji": "🍧",
+ "unicodeVersion": "6.0",
"digest": "54048e77268b7548d03088517bf8558d11324db901ca57f9bec93f1873663a74"
},
- {
- "name": "sheep",
- "unicode": "1F411",
+ "sheep": {
+ "category": "nature",
+ "moji": "🐑",
+ "unicodeVersion": "6.0",
"digest": "c867c8e6e51768f1f51f4fe5abd3fbd5c1d69b01a3cb48b5fb94b6e2338a271c"
},
- {
- "name": "shell",
- "unicode": "1F41A",
+ "shell": {
+ "category": "nature",
+ "moji": "🐚",
+ "unicodeVersion": "6.0",
"digest": "8983652d33ad6ab91195518cecb5a268a1c0ae603d271f0ddd756ff50058ddb3"
},
- {
- "name": "shield",
- "unicode": "1F6E1",
+ "shield": {
+ "category": "objects",
+ "moji": "🛡",
+ "unicodeVersion": "7.0",
"digest": "763d0a56a62c51c730ccb0fbea38ab597cbf41a85ab968198e6ec35630d50aa5"
},
- {
- "name": "shinto_shrine",
- "unicode": "26E9",
+ "shinto_shrine": {
+ "category": "travel",
+ "moji": "⛩",
+ "unicodeVersion": "5.2",
"digest": "38a6d756c5aa9703510afa5076d75192f7814bbb6632394d4b8253d9ceda7f8c"
},
- {
- "name": "ship",
- "unicode": "1F6A2",
+ "ship": {
+ "category": "travel",
+ "moji": "🚢",
+ "unicodeVersion": "6.0",
"digest": "79c680845892a3e81ec6af2160ee07c29147155943e5daba6c76d04252014c20"
},
- {
- "name": "shirt",
- "unicode": "1F455",
+ "shirt": {
+ "category": "people",
+ "moji": "👕",
+ "unicodeVersion": "6.0",
"digest": "46c7253e15d7cac03699ddb1550fbb7565bbe487310f7e218c0583aa69f9d3c5"
},
- {
- "name": "shopping_bags",
- "unicode": "1F6CD",
+ "shopping_bags": {
+ "category": "objects",
+ "moji": "🛍",
+ "unicodeVersion": "7.0",
"digest": "95a3f03c675207bb1354270d02a630c204455c47b3edca23c48523a40cf3ea3b"
},
- {
- "name": "shopping_cart",
- "unicode": "1F6D2",
+ "shopping_cart": {
+ "category": "objects",
+ "moji": "🛒",
+ "unicodeVersion": "9.0",
"digest": "4599b63f6861cdb4d8272cac84435c24c1d4d6a73c66d51e04a1cd14a1d333e6"
},
- {
- "name": "shopping_trolley",
- "unicode": "1F6D2",
- "digest": "4599b63f6861cdb4d8272cac84435c24c1d4d6a73c66d51e04a1cd14a1d333e6"
- },
- {
- "name": "shower",
- "unicode": "1F6BF",
+ "shower": {
+ "category": "objects",
+ "moji": "🚿",
+ "unicodeVersion": "6.0",
"digest": "6b3c767c0eb472d4861c6c3cc2735a5e2c09681872ef42a11dc89f3c80b9da01"
},
- {
- "name": "shrimp",
- "unicode": "1F990",
+ "shrimp": {
+ "category": "nature",
+ "moji": "🦐",
+ "unicodeVersion": "9.0",
"digest": "b3651f3be3767125076a013fe903854f5b456a8afae865cb219cf528e0f44caa"
},
- {
- "name": "shrug",
- "unicode": "1F937",
+ "shrug": {
+ "category": "people",
+ "moji": "🤷",
+ "unicodeVersion": "9.0",
"digest": "6e264243cc3b6e396069dea4357a958bdcd4081cb1af0ed6aa47235bef88cf27"
},
- {
- "name": "shrug_tone1",
- "unicode": "1F937-1F3FB",
+ "shrug_tone1": {
+ "category": "people",
+ "moji": "🤷🏻",
+ "unicodeVersion": "9.0",
"digest": "0567b9fd95c8a857914003a5465a500ca79c8111811d45b865021b1b1d92d0b1"
},
- {
- "name": "shrug_tone2",
- "unicode": "1F937-1F3FC",
+ "shrug_tone2": {
+ "category": "people",
+ "moji": "🤷🏼",
+ "unicodeVersion": "9.0",
"digest": "1557c2f5e3d4599c806d74c0b78afcca940678787534b6862bb89a20601bac8a"
},
- {
- "name": "shrug_tone3",
- "unicode": "1F937-1F3FD",
+ "shrug_tone3": {
+ "category": "people",
+ "moji": "🤷🏽",
+ "unicodeVersion": "9.0",
"digest": "f02754541a7bf74ba7eebe6c27daf1e3e1dac25172c35b8ba45641e278dfda3d"
},
- {
- "name": "shrug_tone4",
- "unicode": "1F937-1F3FE",
+ "shrug_tone4": {
+ "category": "people",
+ "moji": "🤷🏾",
+ "unicodeVersion": "9.0",
"digest": "2b5121164cb5f4e253d8fb31f6445cf8afaf30dba41732edc511440cdb78d15c"
},
- {
- "name": "shrug_tone5",
- "unicode": "1F937-1F3FF",
+ "shrug_tone5": {
+ "category": "people",
+ "moji": "🤷🏿",
+ "unicodeVersion": "9.0",
"digest": "62d99a26bbad479f574f66208c41b9960cd41fb9d79d3a13fbdaa44682077115"
},
- {
- "name": "signal_strength",
- "unicode": "1F4F6",
+ "signal_strength": {
+ "category": "symbols",
+ "moji": "📶",
+ "unicodeVersion": "6.0",
"digest": "2c6f04ba4ecd2d2d423e19eb52cfbfd253f4db6e0908d91c1af4ea6192597447"
},
- {
- "name": "six",
- "unicode": "0036-20E3",
+ "six": {
+ "category": "symbols",
+ "moji": "6️⃣",
+ "unicodeVersion": "3.0",
"digest": "cede9324261208d0fd5d00fcdfc0df0331944bd9cff4f40b30a582a641526c1c"
},
- {
- "name": "six_pointed_star",
- "unicode": "1F52F",
+ "six_pointed_star": {
+ "category": "symbols",
+ "moji": "🔯",
+ "unicodeVersion": "6.0",
"digest": "9203e3b4f08af439ae0bfb6a7b29a02dceb027b6c2dc5463b524dfd314cbff4e"
},
- {
- "name": "ski",
- "unicode": "1F3BF",
+ "ski": {
+ "category": "activity",
+ "moji": "🎿",
+ "unicodeVersion": "6.0",
"digest": "80f0ca8660ba373fef823af9e98e148c4ddb1e217eb6d0a0ea2bae2288b57570"
},
- {
- "name": "skier",
- "unicode": "26F7",
+ "skier": {
+ "category": "activity",
+ "moji": "⛷",
+ "unicodeVersion": "5.2",
"digest": "4fff0aa155367f551a59aed9657b8afa159173882b25db9cd8434293d1eed76d"
},
- {
- "name": "skull",
- "unicode": "1F480",
- "digest": "cdd2031164281bf2b0083df4479651d96bc16d11e44bac4deaf402a9c0d6f40a"
- },
- {
- "name": "skeleton",
- "unicode": "1F480",
+ "skull": {
+ "category": "people",
+ "moji": "💀",
+ "unicodeVersion": "6.0",
"digest": "cdd2031164281bf2b0083df4479651d96bc16d11e44bac4deaf402a9c0d6f40a"
},
- {
- "name": "skull_crossbones",
- "unicode": "2620",
+ "skull_crossbones": {
+ "category": "objects",
+ "moji": "☠",
+ "unicodeVersion": "1.1",
"digest": "ae764ba21a1fcc4409f4cc9e75a261d70b87548f64158dbd3451374ad5724123"
},
- {
- "name": "skull_and_crossbones",
- "unicode": "2620",
- "digest": "ae764ba21a1fcc4409f4cc9e75a261d70b87548f64158dbd3451374ad5724123"
- },
- {
- "name": "sleeping",
- "unicode": "1F634",
+ "sleeping": {
+ "category": "people",
+ "moji": "😴",
+ "unicodeVersion": "6.1",
"digest": "1050a011509b56735c9f30a6fccc876256e2a4546dc6052e518151c8aca4b526"
},
- {
- "name": "sleeping_accommodation",
- "unicode": "1F6CC",
+ "sleeping_accommodation": {
+ "category": "objects",
+ "moji": "🛌",
+ "unicodeVersion": "7.0",
"digest": "2ce42c027d1d0947abc403c359fd668a7bc44f5ead2582e97f3db7dd4e22e5d5"
},
- {
- "name": "sleepy",
- "unicode": "1F62A",
+ "sleepy": {
+ "category": "people",
+ "moji": "😪",
+ "unicodeVersion": "6.0",
"digest": "2ee9bb1f72ef99e0e33095ec2bbf7a58ffea0ff7d40b840f4cdba57be9de74b0"
},
- {
- "name": "slight_frown",
- "unicode": "1F641",
- "digest": "d71d564a6c2d366a8e28a78ef4e07d387a77037fe8c99aa0ea1571299dc490c9"
- },
- {
- "name": "slightly_frowning_face",
- "unicode": "1F641",
+ "slight_frown": {
+ "category": "people",
+ "moji": "🙁",
+ "unicodeVersion": "7.0",
"digest": "d71d564a6c2d366a8e28a78ef4e07d387a77037fe8c99aa0ea1571299dc490c9"
},
- {
- "name": "slight_smile",
- "unicode": "1F642",
- "digest": "10f4b66a755f5c78762a330f20d1866e4a22f3f1d495161d758d3bab8d2f36fe"
- },
- {
- "name": "slightly_smiling_face",
- "unicode": "1F642",
+ "slight_smile": {
+ "category": "people",
+ "moji": "🙂",
+ "unicodeVersion": "7.0",
"digest": "10f4b66a755f5c78762a330f20d1866e4a22f3f1d495161d758d3bab8d2f36fe"
},
- {
- "name": "slot_machine",
- "unicode": "1F3B0",
+ "slot_machine": {
+ "category": "activity",
+ "moji": "🎰",
+ "unicodeVersion": "6.0",
"digest": "914184788f8cd865cd074dca25c22acee31f5498117bd9a6e78cae67e6601652"
},
- {
- "name": "small_blue_diamond",
- "unicode": "1F539",
+ "small_blue_diamond": {
+ "category": "symbols",
+ "moji": "🔹",
+ "unicodeVersion": "6.0",
"digest": "0b56d8e6b5ddf1f49fcc76e45e5fb2ee9f99ae6ffe682c26eaea4d9b7faac36c"
},
- {
- "name": "small_orange_diamond",
- "unicode": "1F538",
+ "small_orange_diamond": {
+ "category": "symbols",
+ "moji": "🔸",
+ "unicodeVersion": "6.0",
"digest": "a2235830550e289c1608f2dcf5ede48f5c1a0eff45570699c39708c9677ab950"
},
- {
- "name": "small_red_triangle",
- "unicode": "1F53A",
+ "small_red_triangle": {
+ "category": "symbols",
+ "moji": "🔺",
+ "unicodeVersion": "6.0",
"digest": "8c2985c4e9ce42d2f3b35539b879bc36206c5ef749f39fbd1eac51bd2676e1e5"
},
- {
- "name": "small_red_triangle_down",
- "unicode": "1F53B",
+ "small_red_triangle_down": {
+ "category": "symbols",
+ "moji": "🔻",
+ "unicodeVersion": "6.0",
"digest": "46bd328df2fbf5d0597596bbf00d2d5f6e0c65bcb8f3fb325df8ba0c25e445b5"
},
- {
- "name": "smile",
- "unicode": "1F604",
+ "smile": {
+ "category": "people",
+ "moji": "😄",
+ "unicodeVersion": "6.0",
"digest": "14905c372d5bf7719bd727c9efae31a03291acec79801652a23710c6848c5d14"
},
- {
- "name": "smile_cat",
- "unicode": "1F638",
+ "smile_cat": {
+ "category": "people",
+ "moji": "😸",
+ "unicodeVersion": "6.0",
"digest": "c35b76d6df100edb4022d762f47abfeb9f5e70886960c1d25908bd5d57ccb47e"
},
- {
- "name": "smiley",
- "unicode": "1F603",
+ "smiley": {
+ "category": "people",
+ "moji": "😃",
+ "unicodeVersion": "6.0",
"digest": "a89f31eb9d814636852517a7f4eadec59195e2ac2cc9f8d124f1a1cc0f775b4a"
},
- {
- "name": "smiley_cat",
- "unicode": "1F63A",
+ "smiley_cat": {
+ "category": "people",
+ "moji": "😺",
+ "unicodeVersion": "6.0",
"digest": "3e66a113c5e3e73fb94be29084cb27986b6bdb0e78ab44785bf2a35a550e71bf"
},
- {
- "name": "smiling_imp",
- "unicode": "1F608",
+ "smiling_imp": {
+ "category": "people",
+ "moji": "😈",
+ "unicodeVersion": "6.0",
"digest": "3e02131d16525938f6facc7e097365dec7e13c8a0049a3be35fc29c80cc291b3"
},
- {
- "name": "smirk",
- "unicode": "1F60F",
+ "smirk": {
+ "category": "people",
+ "moji": "😏",
+ "unicodeVersion": "6.0",
"digest": "3c180d46f5574d6fca3bb68eb02517da60b7008843cb3e90f2f9620d0c8ee943"
},
- {
- "name": "smirk_cat",
- "unicode": "1F63C",
+ "smirk_cat": {
+ "category": "people",
+ "moji": "😼",
+ "unicodeVersion": "6.0",
"digest": "0683c7f73e1f65984e91313607d7cca21d99acd4b2e9932f00e0fffd0ce90742"
},
- {
- "name": "smoking",
- "unicode": "1F6AC",
+ "smoking": {
+ "category": "objects",
+ "moji": "🚬",
+ "unicodeVersion": "6.0",
"digest": "baa9cb444bf0fe5c74358f981b19bc9e5c0415ced7f042baf93642282476ea61"
},
- {
- "name": "snail",
- "unicode": "1F40C",
+ "snail": {
+ "category": "nature",
+ "moji": "🐌",
+ "unicodeVersion": "6.0",
"digest": "5733bf3672ae4b2b3e090fa670aeac70dcbcc04ca5b13abc8c8e53b8b3d4ff33"
},
- {
- "name": "snake",
- "unicode": "1F40D",
+ "snake": {
+ "category": "nature",
+ "moji": "🐍",
+ "unicodeVersion": "6.0",
"digest": "18da2d97c771149ef5454dd23470e900903a62ab93f9e2ce301aad5a8181d773"
},
- {
- "name": "sneezing_face",
- "unicode": "1F927",
- "digest": "c20ef571dc7e35572fe3c18b7845aefc89af083ea925c48a29de3b7387af6e17"
- },
- {
- "name": "sneeze",
- "unicode": "1F927",
+ "sneezing_face": {
+ "category": "people",
+ "moji": "🤧",
+ "unicodeVersion": "9.0",
"digest": "c20ef571dc7e35572fe3c18b7845aefc89af083ea925c48a29de3b7387af6e17"
},
- {
- "name": "snowboarder",
- "unicode": "1F3C2",
+ "snowboarder": {
+ "category": "activity",
+ "moji": "🏂",
+ "unicodeVersion": "6.0",
"digest": "c6e074139b851aa53b1ba6464d84da14b3da7412fc44c6c196a8469d76915c19"
},
- {
- "name": "snowflake",
- "unicode": "2744",
+ "snowflake": {
+ "category": "nature",
+ "moji": "❄",
+ "unicodeVersion": "1.1",
"digest": "6556c918e181df01ba849e76c43972d5310439971e5d8fc2409d112c05bf0028"
},
- {
- "name": "snowman",
- "unicode": "26C4",
+ "snowman": {
+ "category": "nature",
+ "moji": "⛄",
+ "unicodeVersion": "5.2",
"digest": "6137456b2335e88e09c1859615eb22bb636355ef438f7a3949ad2f3d54478dd3"
},
- {
- "name": "snowman2",
- "unicode": "2603",
+ "snowman2": {
+ "category": "nature",
+ "moji": "☃",
+ "unicodeVersion": "1.1",
"digest": "33ec75c22a13c81fa3c6b24a77ac1a08dc0dbe70b3716cf17b6702014d8a63fe"
},
- {
- "name": "sob",
- "unicode": "1F62D",
+ "sob": {
+ "category": "people",
+ "moji": "😭",
+ "unicodeVersion": "6.0",
"digest": "d1ed4b31861f9f9fd4e9c95a9c17530e2320a1b4cad6ececb1545ce25d65e4ce"
},
- {
- "name": "soccer",
- "unicode": "26BD",
+ "soccer": {
+ "category": "activity",
+ "moji": "⚽",
+ "unicodeVersion": "5.2",
"digest": "6a3f2e6a9a0b64c3fbf8705995792091daf386a4112dba75507a1f556f662f84"
},
- {
- "name": "soon",
- "unicode": "1F51C",
+ "soon": {
+ "category": "symbols",
+ "moji": "🔜",
+ "unicodeVersion": "6.0",
"digest": "a49d1bcfbac3e6ccc05b9a9863eff74b0eb8b4d4b22b8b0f7b2787fcba1c73cc"
},
- {
- "name": "sos",
- "unicode": "1F198",
+ "sos": {
+ "category": "symbols",
+ "moji": "🆘",
+ "unicodeVersion": "6.0",
"digest": "2fa7e0274383aeed6019eb9177e778d7aab8b88575b078b0ffeb77cd18df14b3"
},
- {
- "name": "sound",
- "unicode": "1F509",
+ "sound": {
+ "category": "symbols",
+ "moji": "🔉",
+ "unicodeVersion": "6.0",
"digest": "faaca7b315b2495cbc381468580d25f1d11362441c35bb43d8a914f2ec8202d2"
},
- {
- "name": "space_invader",
- "unicode": "1F47E",
+ "space_invader": {
+ "category": "activity",
+ "moji": "👾",
+ "unicodeVersion": "6.0",
"digest": "e75379cb5063f9a8861d762ad1886097c1697fbb61f2e4e8f531047955a4a2dd"
},
- {
- "name": "spades",
- "unicode": "2660",
+ "spades": {
+ "category": "symbols",
+ "moji": "♠",
+ "unicodeVersion": "1.1",
"digest": "2c4d20f6a4893cfc62498d3f1f8f67577f39ed09f3e6682d8cb9cd8f365d30da"
},
- {
- "name": "spaghetti",
- "unicode": "1F35D",
+ "spaghetti": {
+ "category": "food",
+ "moji": "🍝",
+ "unicodeVersion": "6.0",
"digest": "6d3451dc0faa1913539edb99261448f51735f269b61193c53dfe63466c0191e8"
},
- {
- "name": "sparkle",
- "unicode": "2747",
+ "sparkle": {
+ "category": "symbols",
+ "moji": "❇",
+ "unicodeVersion": "1.1",
"digest": "7131163cd6c2f879110c86e9f068c33cf580f7c4b619449c41851fe6083402ee"
},
- {
- "name": "sparkler",
- "unicode": "1F387",
+ "sparkler": {
+ "category": "travel",
+ "moji": "🎇",
+ "unicodeVersion": "6.0",
"digest": "88539ed8a13bd66e0c265c0913bd3ec2ddc4d95484323595713beb102221a1f6"
},
- {
- "name": "sparkles",
- "unicode": "2728",
+ "sparkles": {
+ "category": "nature",
+ "moji": "✨",
+ "unicodeVersion": "6.0",
"digest": "cf84d16b1c0a381d5a7ae79031872747c9a6887eab6e92cc4a10a4b8600ef506"
},
- {
- "name": "sparkling_heart",
- "unicode": "1F496",
+ "sparkling_heart": {
+ "category": "symbols",
+ "moji": "💖",
+ "unicodeVersion": "6.0",
"digest": "b80b1ddef83b6528b309a194f6f2faf5acab603daeb9254523efc2b941bcb6d2"
},
- {
- "name": "speak_no_evil",
- "unicode": "1F64A",
+ "speak_no_evil": {
+ "category": "nature",
+ "moji": "🙊",
+ "unicodeVersion": "6.0",
"digest": "d2d7cfb4d471928a496bdc146890adc8422a68500b68115630b24c125d18e81f"
},
- {
- "name": "speaker",
- "unicode": "1F508",
+ "speaker": {
+ "category": "symbols",
+ "moji": "🔈",
+ "unicodeVersion": "6.0",
"digest": "dbca5f7181728d2ad67ff76fd566ffbdf53e333e7eeed341f54668bd47969413"
},
- {
- "name": "speaking_head",
- "unicode": "1F5E3",
+ "speaking_head": {
+ "category": "people",
+ "moji": "🗣",
+ "unicodeVersion": "7.0",
"digest": "4be1af79b4506c00af4df64663413bcbae195dab0bc63c5011feb8f9663ed544"
},
- {
- "name": "speaking_head_in_silhouette",
- "unicode": "1F5E3",
- "digest": "4be1af79b4506c00af4df64663413bcbae195dab0bc63c5011feb8f9663ed544"
- },
- {
- "name": "speech_balloon",
- "unicode": "1F4AC",
+ "speech_balloon": {
+ "category": "symbols",
+ "moji": "💬",
+ "unicodeVersion": "6.0",
"digest": "817100d9979456e7d2f253ac22e13b7a2302dc1590566214915b003e403c53ca"
},
- {
- "name": "speedboat",
- "unicode": "1F6A4",
+ "speedboat": {
+ "category": "travel",
+ "moji": "🚤",
+ "unicodeVersion": "6.0",
"digest": "a523b2320f0b24be1e9fdbc1ff828e28d8fd9a64d51e5888ab453ef0bc9f0576"
},
- {
- "name": "spider",
- "unicode": "1F577",
+ "spider": {
+ "category": "nature",
+ "moji": "🕷",
+ "unicodeVersion": "7.0",
"digest": "8411eac0c1b80926fd93cc1d6423e00b05d04c485b79ee232da8f1714e899a37"
},
- {
- "name": "spider_web",
- "unicode": "1F578",
+ "spider_web": {
+ "category": "nature",
+ "moji": "🕸",
+ "unicodeVersion": "7.0",
"digest": "2434bdfbe56dcc4a43699dd59b638af431486b52fb1d6d685451f3b231b2be23"
},
- {
- "name": "spoon",
- "unicode": "1F944",
+ "spoon": {
+ "category": "food",
+ "moji": "🥄",
+ "unicodeVersion": "9.0",
"digest": "4fa31d59e5bffd2c45a8e01fcd5652e78a5691cbfa744e69882bc67173ddea05"
},
- {
- "name": "spy",
- "unicode": "1F575",
- "digest": "99fe3cdeff934726ee5855b0e401bf32570084aaad4eb10df837fd410ca742aa"
- },
- {
- "name": "sleuth_or_spy",
- "unicode": "1F575",
+ "spy": {
+ "category": "people",
+ "moji": "🕵",
+ "unicodeVersion": "7.0",
"digest": "99fe3cdeff934726ee5855b0e401bf32570084aaad4eb10df837fd410ca742aa"
},
- {
- "name": "spy_tone1",
- "unicode": "1F575-1F3FB",
- "digest": "1720a99064061c43c7647b6bd517efa2ee2621b355a644adfb347d62849366a2"
- },
- {
- "name": "sleuth_or_spy_tone1",
- "unicode": "1F575-1F3FB",
+ "spy_tone1": {
+ "category": "people",
+ "moji": "🕵🏻",
+ "unicodeVersion": "8.0",
"digest": "1720a99064061c43c7647b6bd517efa2ee2621b355a644adfb347d62849366a2"
},
- {
- "name": "spy_tone2",
- "unicode": "1F575-1F3FC",
+ "spy_tone2": {
+ "category": "people",
+ "moji": "🕵🏼",
+ "unicodeVersion": "8.0",
"digest": "23ff0026723f2b5a46fbfb55e24c4a4a33af2bd96808b3ea3af76aae99965d68"
},
- {
- "name": "sleuth_or_spy_tone2",
- "unicode": "1F575-1F3FC",
- "digest": "23ff0026723f2b5a46fbfb55e24c4a4a33af2bd96808b3ea3af76aae99965d68"
- },
- {
- "name": "spy_tone3",
- "unicode": "1F575-1F3FD",
- "digest": "1d0cb3d54fb61e4763a4f0642ef32094bdd40832be0d42799ce9ba69773616df"
- },
- {
- "name": "sleuth_or_spy_tone3",
- "unicode": "1F575-1F3FD",
+ "spy_tone3": {
+ "category": "people",
+ "moji": "🕵🏽",
+ "unicodeVersion": "8.0",
"digest": "1d0cb3d54fb61e4763a4f0642ef32094bdd40832be0d42799ce9ba69773616df"
},
- {
- "name": "spy_tone4",
- "unicode": "1F575-1F3FE",
+ "spy_tone4": {
+ "category": "people",
+ "moji": "🕵🏾",
+ "unicodeVersion": "8.0",
"digest": "e36a4b52df6cb954fab9d9128111f1301c6d46bdeacf51993ffb5bb354cd0ad3"
},
- {
- "name": "sleuth_or_spy_tone4",
- "unicode": "1F575-1F3FE",
- "digest": "e36a4b52df6cb954fab9d9128111f1301c6d46bdeacf51993ffb5bb354cd0ad3"
- },
- {
- "name": "spy_tone5",
- "unicode": "1F575-1F3FF",
- "digest": "ffc6fefd9a537124ebf0a9ddf387414dce1291335026064644f6cf9315591129"
- },
- {
- "name": "sleuth_or_spy_tone5",
- "unicode": "1F575-1F3FF",
+ "spy_tone5": {
+ "category": "people",
+ "moji": "🕵🏿",
+ "unicodeVersion": "8.0",
"digest": "ffc6fefd9a537124ebf0a9ddf387414dce1291335026064644f6cf9315591129"
},
- {
- "name": "squid",
- "unicode": "1F991",
+ "squid": {
+ "category": "nature",
+ "moji": "🦑",
+ "unicodeVersion": "9.0",
"digest": "65a1b318c2c506b9d26cfd8282a5cf9922109595c8d12e92c3f7481ac7c08c49"
},
- {
- "name": "stadium",
- "unicode": "1F3DF",
+ "stadium": {
+ "category": "travel",
+ "moji": "🏟",
+ "unicodeVersion": "7.0",
"digest": "73bf955e767ba1518c9c92b2ba59a2aa1ec4b018652dffd97bcd74832a33789f"
},
- {
- "name": "star",
- "unicode": "2B50",
+ "star": {
+ "category": "nature",
+ "moji": "⭐",
+ "unicodeVersion": "5.1",
"digest": "d78e5c1b78caed103e100150c10b08a9ca3ee30c243943d6fc3cc08f422122e9"
},
- {
- "name": "star2",
- "unicode": "1F31F",
+ "star2": {
+ "category": "nature",
+ "moji": "🌟",
+ "unicodeVersion": "6.0",
"digest": "f91ac4afe3f5d4a52847ae8b4a9704b591e00399aebba553d150d7e34ee939fa"
},
- {
- "name": "star_and_crescent",
- "unicode": "262A",
+ "star_and_crescent": {
+ "category": "symbols",
+ "moji": "☪",
+ "unicodeVersion": "1.1",
"digest": "1bf3d29e50034f5e7c0dccff0a3a533b74bfa9b489e357b2739a473311f1332a"
},
- {
- "name": "star_of_david",
- "unicode": "2721",
+ "star_of_david": {
+ "category": "symbols",
+ "moji": "✡",
+ "unicodeVersion": "1.1",
"digest": "28a0bd0eeac9d0835ceb8425d72c2472464e863dd09b76a0ddc1c08cf1986402"
},
- {
- "name": "stars",
- "unicode": "1F320",
+ "stars": {
+ "category": "travel",
+ "moji": "🌠",
+ "unicodeVersion": "6.0",
"digest": "837d9045316b8fb5e533457eac61241534f641eb78d8cb75f688f80fb8e8a7f0"
},
- {
- "name": "station",
- "unicode": "1F689",
+ "station": {
+ "category": "travel",
+ "moji": "🚉",
+ "unicodeVersion": "6.0",
"digest": "27a163ac0aea4ed247a121cae826eafc475977c68b0d888e9405bea14326ff56"
},
- {
- "name": "statue_of_liberty",
- "unicode": "1F5FD",
+ "statue_of_liberty": {
+ "category": "travel",
+ "moji": "🗽",
+ "unicodeVersion": "6.0",
"digest": "f5a43599ab3f24ed3a78a745e06e2ac3e33107a292386ad81c67935ee5b22493"
},
- {
- "name": "steam_locomotive",
- "unicode": "1F682",
+ "steam_locomotive": {
+ "category": "travel",
+ "moji": "🚂",
+ "unicodeVersion": "6.0",
"digest": "52ad0073f37b978faf3884fb193046f2b0614e1557bbcc9de1b020e42aff2dba"
},
- {
- "name": "stew",
- "unicode": "1F372",
+ "stew": {
+ "category": "food",
+ "moji": "🍲",
+ "unicodeVersion": "6.0",
"digest": "c16f61236db314ad8d9f2dd241ec1e15c8d64e5872cce93ec4d0996490dd39df"
},
- {
- "name": "stop_button",
- "unicode": "23F9",
+ "stop_button": {
+ "category": "symbols",
+ "moji": "⏹",
+ "unicodeVersion": "7.0",
"digest": "83f9d0da3ad845fef41b4e8336815d30e9c8f042ab2a8340894ade2f428fc98a"
},
- {
- "name": "stopwatch",
- "unicode": "23F1",
+ "stopwatch": {
+ "category": "objects",
+ "moji": "⏱",
+ "unicodeVersion": "6.0",
"digest": "9b6b9491a24d8ab4f896eb876da7973f028bd5e7c51a3767ba7e61bb6fbb2be0"
},
- {
- "name": "straight_ruler",
- "unicode": "1F4CF",
+ "straight_ruler": {
+ "category": "objects",
+ "moji": "📏",
+ "unicodeVersion": "6.0",
"digest": "cee31101767bd3f961363599924dc3790675d05a1285a8396428d2f91771c111"
},
- {
- "name": "strawberry",
- "unicode": "1F353",
+ "strawberry": {
+ "category": "food",
+ "moji": "🍓",
+ "unicodeVersion": "6.0",
"digest": "5750a15e12f21259286ddbc3a8222a385b3b97a9f368897f42dd000060343174"
},
- {
- "name": "stuck_out_tongue",
- "unicode": "1F61B",
+ "stuck_out_tongue": {
+ "category": "people",
+ "moji": "😛",
+ "unicodeVersion": "6.1",
"digest": "92dc42980a6dfdd7204fc874a762d6a0bbf0fdbfb5a7c0698fca04782e99fde6"
},
- {
- "name": "stuck_out_tongue_closed_eyes",
- "unicode": "1F61D",
+ "stuck_out_tongue_closed_eyes": {
+ "category": "people",
+ "moji": "😝",
+ "unicodeVersion": "6.0",
"digest": "434d25ac24cad7ba699eae876a25d9a99b584449cca50b124bf6aa7f20a83d51"
},
- {
- "name": "stuck_out_tongue_winking_eye",
- "unicode": "1F61C",
+ "stuck_out_tongue_winking_eye": {
+ "category": "people",
+ "moji": "😜",
+ "unicodeVersion": "6.0",
"digest": "dbacd6428a2a2933212e6a4dc0c7f302177fb23b963626ccb26f27f91737f03d"
},
- {
- "name": "stuffed_flatbread",
- "unicode": "1F959",
+ "stuffed_flatbread": {
+ "category": "food",
+ "moji": "🥙",
+ "unicodeVersion": "9.0",
"digest": "9f841f2520640d69be4f20a3199023d5811842b28556b5e1152e5ec11f0fda07"
},
- {
- "name": "stuffed_pita",
- "unicode": "1F959",
- "digest": "9f841f2520640d69be4f20a3199023d5811842b28556b5e1152e5ec11f0fda07"
- },
- {
- "name": "sun_with_face",
- "unicode": "1F31E",
+ "sun_with_face": {
+ "category": "nature",
+ "moji": "🌞",
+ "unicodeVersion": "6.0",
"digest": "7256ff5263006c64c03f1eb66e3ddb56d67d785d65dacc37aa886d0cd4be63be"
},
- {
- "name": "sunflower",
- "unicode": "1F33B",
+ "sunflower": {
+ "category": "nature",
+ "moji": "🌻",
+ "unicodeVersion": "6.0",
"digest": "27d1161f50f932a6b26c404cf2e8f7083683ed0f2382d62b7472acccaa6eb695"
},
- {
- "name": "sunglasses",
- "unicode": "1F60E",
+ "sunglasses": {
+ "category": "people",
+ "moji": "😎",
+ "unicodeVersion": "6.0",
"digest": "966684382e5c59e98319e4c0ea7c304c61c2638ad5408faa49ce2c83c4416757"
},
- {
- "name": "sunny",
- "unicode": "2600",
+ "sunny": {
+ "category": "nature",
+ "moji": "☀",
+ "unicodeVersion": "1.1",
"digest": "460fea4cbbdd1595450c1033a2ee5de7fea2e2f147822efa49f7e204812415aa"
},
- {
- "name": "sunrise",
- "unicode": "1F305",
+ "sunrise": {
+ "category": "travel",
+ "moji": "🌅",
+ "unicodeVersion": "6.0",
"digest": "7718a49636b0cdd1862ed67c7a9d6e72f471c2591ff0d912485b1be55d1ea115"
},
- {
- "name": "sunrise_over_mountains",
- "unicode": "1F304",
+ "sunrise_over_mountains": {
+ "category": "travel",
+ "moji": "🌄",
+ "unicodeVersion": "6.0",
"digest": "743d0701cdbe2a814962363813c3153d3c5e62c3e410349f56d49dbb9581f356"
},
- {
- "name": "surfer",
- "unicode": "1F3C4",
+ "surfer": {
+ "category": "activity",
+ "moji": "🏄",
+ "unicodeVersion": "6.0",
"digest": "bb440775e9213430942015c37db8de58b5a561ee971b2a0f3993fc3f1d2554d4"
},
- {
- "name": "surfer_tone1",
- "unicode": "1F3C4-1F3FB",
+ "surfer_tone1": {
+ "category": "activity",
+ "moji": "🏄🏻",
+ "unicodeVersion": "8.0",
"digest": "a4937b030aca30b68bb644f37cf63c38aebce3c00b57d1c8a0ffe596b57d2f1e"
},
- {
- "name": "surfer_tone2",
- "unicode": "1F3C4-1F3FC",
+ "surfer_tone2": {
+ "category": "activity",
+ "moji": "🏄🏼",
+ "unicodeVersion": "8.0",
"digest": "1c2a954a9c5284dedf0327d6f3c954c9fdd3953b848076d298874775ad8bf0a3"
},
- {
- "name": "surfer_tone3",
- "unicode": "1F3C4-1F3FD",
+ "surfer_tone3": {
+ "category": "activity",
+ "moji": "🏄🏽",
+ "unicodeVersion": "8.0",
"digest": "418a3408b9ab026124f067c8597b500217e56bc28d9844a29eea5eee6f604ff8"
},
- {
- "name": "surfer_tone4",
- "unicode": "1F3C4-1F3FE",
+ "surfer_tone4": {
+ "category": "activity",
+ "moji": "🏄🏾",
+ "unicodeVersion": "8.0",
"digest": "530870b9ac9f4d45ff750e264feb90b44fb93ca2852f323987b06f5f12fb5a4d"
},
- {
- "name": "surfer_tone5",
- "unicode": "1F3C4-1F3FF",
+ "surfer_tone5": {
+ "category": "activity",
+ "moji": "🏄🏿",
+ "unicodeVersion": "8.0",
"digest": "40e11b1ae652cfd085d083377f1da24160065ed1b67403c6fa4655e6e44169ec"
},
- {
- "name": "sushi",
- "unicode": "1F363",
+ "sushi": {
+ "category": "food",
+ "moji": "🍣",
+ "unicodeVersion": "6.0",
"digest": "b924c621236ca3284b349b0509ae1043f2fc2c7f6d67615716f9717ada78c992"
},
- {
- "name": "suspension_railway",
- "unicode": "1F69F",
+ "suspension_railway": {
+ "category": "travel",
+ "moji": "🚟",
+ "unicodeVersion": "6.0",
"digest": "cd3d21da79864f0c018b863e82fb0561fff3c5e3c065303cfcb89c3663d638ba"
},
- {
- "name": "sweat",
- "unicode": "1F613",
+ "sweat": {
+ "category": "people",
+ "moji": "😓",
+ "unicodeVersion": "6.0",
"digest": "1aa771479aa1ac5eeea4bafbe93ebd85a0f692f6d869034f31e25b689c2e264d"
},
- {
- "name": "sweat_drops",
- "unicode": "1F4A6",
+ "sweat_drops": {
+ "category": "nature",
+ "moji": "💦",
+ "unicodeVersion": "6.0",
"digest": "b575b85415bc9852cf6415d417ebf799167fde03c6819ebcaa24ae1b3dde8dab"
},
- {
- "name": "sweat_smile",
- "unicode": "1F605",
+ "sweat_smile": {
+ "category": "people",
+ "moji": "😅",
+ "unicodeVersion": "6.0",
"digest": "171b0d0845d46c33bedb6d3b39fb1ff366e22ba90685eedabebd91bb2b0680de"
},
- {
- "name": "sweet_potato",
- "unicode": "1F360",
+ "sweet_potato": {
+ "category": "food",
+ "moji": "🍠",
+ "unicodeVersion": "6.0",
"digest": "4b91920f0b87d42763313bc476f4c821a74e4c12dc1c92165a859dddeaaf8844"
},
- {
- "name": "swimmer",
- "unicode": "1F3CA",
+ "swimmer": {
+ "category": "activity",
+ "moji": "🏊",
+ "unicodeVersion": "6.0",
"digest": "2c4ed4a51aad99d9957ae11a219d5164db9748fc3a65002c6085a9f15adfa9e2"
},
- {
- "name": "swimmer_tone1",
- "unicode": "1F3CA-1F3FB",
+ "swimmer_tone1": {
+ "category": "activity",
+ "moji": "🏊🏻",
+ "unicodeVersion": "8.0",
"digest": "48588f129ee4af52ca2e0f4594213391978601087cd607896b2f979ca077284b"
},
- {
- "name": "swimmer_tone2",
- "unicode": "1F3CA-1F3FC",
+ "swimmer_tone2": {
+ "category": "activity",
+ "moji": "🏊🏼",
+ "unicodeVersion": "8.0",
"digest": "fff209448524bd1ef4d6decabf6c1ead94c8d3d5b1bfb5e54f20cc8e139232fc"
},
- {
- "name": "swimmer_tone3",
- "unicode": "1F3CA-1F3FD",
+ "swimmer_tone3": {
+ "category": "activity",
+ "moji": "🏊🏽",
+ "unicodeVersion": "8.0",
"digest": "2003932cb2cf4ae9a10b23338bf375a9293fb18c0ecf91bdfae73be6eebb3800"
},
- {
- "name": "swimmer_tone4",
- "unicode": "1F3CA-1F3FE",
+ "swimmer_tone4": {
+ "category": "activity",
+ "moji": "🏊🏾",
+ "unicodeVersion": "8.0",
"digest": "20b4bff9baa1c694ad98067dde834c56092f023b9664bec382c2e512232bd480"
},
- {
- "name": "swimmer_tone5",
- "unicode": "1F3CA-1F3FF",
+ "swimmer_tone5": {
+ "category": "activity",
+ "moji": "🏊🏿",
+ "unicodeVersion": "8.0",
"digest": "0ff8eb57c2be8e80a1bc6ba75b8d9ffb9bd8d3be636150c4c03399ec1886f218"
},
- {
- "name": "symbols",
- "unicode": "1F523",
+ "symbols": {
+ "category": "symbols",
+ "moji": "🔣",
+ "unicodeVersion": "6.0",
"digest": "2a2a79816c4d0751a0d73586eec5e63b410653d3c85cc968906bf1fc03d89b94"
},
- {
- "name": "synagogue",
- "unicode": "1F54D",
+ "synagogue": {
+ "category": "travel",
+ "moji": "🕍",
+ "unicodeVersion": "8.0",
"digest": "98569cdd7c61528963b67b7891dfa46025c5e810cbb22ee18ddb3bd85de2da69"
},
- {
- "name": "syringe",
- "unicode": "1F489",
+ "syringe": {
+ "category": "objects",
+ "moji": "💉",
+ "unicodeVersion": "6.0",
"digest": "e1538e645ccc571227c994b71b3d1be2c4d072d8bd9c944a42ff4a11c91a34a6"
},
- {
- "name": "taco",
- "unicode": "1F32E",
+ "taco": {
+ "category": "food",
+ "moji": "🌮",
+ "unicodeVersion": "8.0",
"digest": "e1e45aefdb7445faeae75c3831df6a3d6f2590fcdd48a20d847593c246df613b"
},
- {
- "name": "tada",
- "unicode": "1F389",
+ "tada": {
+ "category": "objects",
+ "moji": "🎉",
+ "unicodeVersion": "6.0",
"digest": "1d2e6cbb2a3244240bc70209715d2213d1efee2e370cccfbcc046c333ae2d650"
},
- {
- "name": "tanabata_tree",
- "unicode": "1F38B",
+ "tanabata_tree": {
+ "category": "nature",
+ "moji": "🎋",
+ "unicodeVersion": "6.0",
"digest": "592f2907ffc1b914390e1a106c15120ff3607e99192158b94d237975647c5540"
},
- {
- "name": "tangerine",
- "unicode": "1F34A",
+ "tangerine": {
+ "category": "food",
+ "moji": "🍊",
+ "unicodeVersion": "6.0",
"digest": "40c9ddcde1b0bcfaeb466629a87825eb8c2037835720cbee5e2fda04be3c8d0a"
},
- {
- "name": "taurus",
- "unicode": "2649",
+ "taurus": {
+ "category": "symbols",
+ "moji": "♉",
+ "unicodeVersion": "1.1",
"digest": "21cf24cb6410ab6596e2df8b3e242cc07f9dbb247eabc00c590fe184b373d068"
},
- {
- "name": "taxi",
- "unicode": "1F695",
+ "taxi": {
+ "category": "travel",
+ "moji": "🚕",
+ "unicodeVersion": "6.0",
"digest": "c546cc743831cfbf0c15452767cf2a4faf3775066797e997ae7c1fcbe4eca479"
},
- {
- "name": "tea",
- "unicode": "1F375",
+ "tea": {
+ "category": "food",
+ "moji": "🍵",
+ "unicodeVersion": "6.0",
"digest": "00e3f1e389fa58c4fcd8c53ebbf83d25872f4315845ab1984b35410ae65553d9"
},
- {
- "name": "telephone",
- "unicode": "260E",
+ "telephone": {
+ "category": "objects",
+ "moji": "☎",
+ "unicodeVersion": "1.1",
"digest": "3a53851e641f8ad938ce3597b1afca2ea63c9314ff81f62563b99937496a13d7"
},
- {
- "name": "telephone_receiver",
- "unicode": "1F4DE",
+ "telephone_receiver": {
+ "category": "objects",
+ "moji": "📞",
+ "unicodeVersion": "6.0",
"digest": "1614d67f3d8814b0d75f39d55f9149e4d28ef57b343498625e62fcfff8365046"
},
- {
- "name": "telescope",
- "unicode": "1F52D",
+ "telescope": {
+ "category": "objects",
+ "moji": "🔭",
+ "unicodeVersion": "6.0",
"digest": "4adf40387870276c4f59fb050d441023e8dac784365b6a8c0282fb519780b495"
},
- {
- "name": "ten",
- "unicode": "1F51F",
+ "ten": {
+ "category": "symbols",
+ "moji": "🔟",
+ "unicodeVersion": "6.0",
"digest": "c7c9491021740d2c17edddb856f79579b0b943d8dc85a2f48dbaac84f35b8a40"
},
- {
- "name": "tennis",
- "unicode": "1F3BE",
+ "tennis": {
+ "category": "activity",
+ "moji": "🎾",
+ "unicodeVersion": "6.0",
"digest": "dc1600b4d8dce3d26259eb0d1c6ab042566565e3c1f2c96112210f1550a716fd"
},
- {
- "name": "tent",
- "unicode": "26FA",
+ "tent": {
+ "category": "travel",
+ "moji": "⛺",
+ "unicodeVersion": "5.2",
"digest": "30d9b17ac3219d4970ddf54d7c1a288b0ae50f7f3b82ed232c0b1b19ef585662"
},
- {
- "name": "thermometer",
- "unicode": "1F321",
+ "thermometer": {
+ "category": "objects",
+ "moji": "🌡",
+ "unicodeVersion": "7.0",
"digest": "66616babbcaef256d7b652796c760e8e893cb950c073348a408fe70904f80f25"
},
- {
- "name": "thermometer_face",
- "unicode": "1F912",
- "digest": "ac2b5caddd128563711a9dcc7f690cf210f684d5e8b64b09c0431d6902437126"
- },
- {
- "name": "face_with_thermometer",
- "unicode": "1F912",
+ "thermometer_face": {
+ "category": "people",
+ "moji": "🤒",
+ "unicodeVersion": "8.0",
"digest": "ac2b5caddd128563711a9dcc7f690cf210f684d5e8b64b09c0431d6902437126"
},
- {
- "name": "thinking",
- "unicode": "1F914",
+ "thinking": {
+ "category": "people",
+ "moji": "🤔",
+ "unicodeVersion": "8.0",
"digest": "4f0b84e5ab8a650cafb166e93688f0e9b31b9ade22a91035261ac90490edb9d3"
},
- {
- "name": "thinking_face",
- "unicode": "1F914",
- "digest": "4f0b84e5ab8a650cafb166e93688f0e9b31b9ade22a91035261ac90490edb9d3"
- },
- {
- "name": "third_place",
- "unicode": "1F949",
- "digest": "27c9bcba44ad95bee30882cc0722e8b0a798206306655dd648e884447ed26808"
- },
- {
- "name": "third_place_medal",
- "unicode": "1F949",
+ "third_place": {
+ "category": "activity",
+ "moji": "🥉",
+ "unicodeVersion": "9.0",
"digest": "27c9bcba44ad95bee30882cc0722e8b0a798206306655dd648e884447ed26808"
},
- {
- "name": "thought_balloon",
- "unicode": "1F4AD",
+ "thought_balloon": {
+ "category": "symbols",
+ "moji": "💭",
+ "unicodeVersion": "6.0",
"digest": "bf59624560c333561d636aedf2c8827089e275895cf434974daaabb3d5cea46e"
},
- {
- "name": "three",
- "unicode": "0033-20E3",
+ "three": {
+ "category": "symbols",
+ "moji": "3️⃣",
+ "unicodeVersion": "3.0",
"digest": "d3f85828787799c769655c38a519cad0743ab799ab276c7606e6e6894cc442e6"
},
- {
- "name": "thumbsdown",
- "unicode": "1F44E",
+ "thumbsdown": {
+ "category": "people",
+ "moji": "👎",
+ "unicodeVersion": "6.0",
"digest": "5954334e2dae5357312b3d629f10a496c728029e02216f8c8b887f9b51561c61"
},
- {
- "name": "-1",
- "unicode": "1F44E",
- "digest": "5954334e2dae5357312b3d629f10a496c728029e02216f8c8b887f9b51561c61"
- },
- {
- "name": "thumbsdown_tone1",
- "unicode": "1F44E-1F3FB",
- "digest": "3c2853491473fd7ae2d1b5415a425cc390d26a8754446f8736c1360e4cb18ba3"
- },
- {
- "name": "-1_tone1",
- "unicode": "1F44E-1F3FB",
+ "thumbsdown_tone1": {
+ "category": "people",
+ "moji": "👎🏻",
+ "unicodeVersion": "8.0",
"digest": "3c2853491473fd7ae2d1b5415a425cc390d26a8754446f8736c1360e4cb18ba3"
},
- {
- "name": "thumbsdown_tone2",
- "unicode": "1F44E-1F3FC",
+ "thumbsdown_tone2": {
+ "category": "people",
+ "moji": "👎🏼",
+ "unicodeVersion": "8.0",
"digest": "4e0f8f86a06b69e423df8d93f41ec393f12800633acc82c4cb6dff64ca0d8507"
},
- {
- "name": "-1_tone2",
- "unicode": "1F44E-1F3FC",
- "digest": "4e0f8f86a06b69e423df8d93f41ec393f12800633acc82c4cb6dff64ca0d8507"
- },
- {
- "name": "thumbsdown_tone3",
- "unicode": "1F44E-1F3FD",
- "digest": "e08fa35575f59978612d4330bbc35313eca9c4dfa04f4212626abc700819effe"
- },
- {
- "name": "-1_tone3",
- "unicode": "1F44E-1F3FD",
+ "thumbsdown_tone3": {
+ "category": "people",
+ "moji": "👎🏽",
+ "unicodeVersion": "8.0",
"digest": "e08fa35575f59978612d4330bbc35313eca9c4dfa04f4212626abc700819effe"
},
- {
- "name": "thumbsdown_tone4",
- "unicode": "1F44E-1F3FE",
+ "thumbsdown_tone4": {
+ "category": "people",
+ "moji": "👎🏾",
+ "unicodeVersion": "8.0",
"digest": "7c6d118d20d5add8ca003e4a53e42685a1f9436b872ed10d79f67ad418fb2a44"
},
- {
- "name": "-1_tone4",
- "unicode": "1F44E-1F3FE",
- "digest": "7c6d118d20d5add8ca003e4a53e42685a1f9436b872ed10d79f67ad418fb2a44"
- },
- {
- "name": "thumbsdown_tone5",
- "unicode": "1F44E-1F3FF",
- "digest": "8697c4a4ee4d6669dc2d47aa97699c42012ca59b80818ad6845878b37b4a9c58"
- },
- {
- "name": "-1_tone5",
- "unicode": "1F44E-1F3FF",
+ "thumbsdown_tone5": {
+ "category": "people",
+ "moji": "👎🏿",
+ "unicodeVersion": "8.0",
"digest": "8697c4a4ee4d6669dc2d47aa97699c42012ca59b80818ad6845878b37b4a9c58"
},
- {
- "name": "thumbsup",
- "unicode": "1F44D",
+ "thumbsup": {
+ "category": "people",
+ "moji": "👍",
+ "unicodeVersion": "6.0",
"digest": "59ec2457ab33e8897261d01a495f6cf5c668d0004807dc541c3b1be5294b1e61"
},
- {
- "name": "+1",
- "unicode": "1F44D",
- "digest": "59ec2457ab33e8897261d01a495f6cf5c668d0004807dc541c3b1be5294b1e61"
- },
- {
- "name": "thumbsup_tone1",
- "unicode": "1F44D-1F3FB",
+ "thumbsup_tone1": {
+ "category": "people",
+ "moji": "👍🏻",
+ "unicodeVersion": "8.0",
"digest": "f57e6c525e8830779ea5026590eec3ca10869dc438a0c779734b617d04f28d21"
},
- {
- "name": "+1_tone1",
- "unicode": "1F44D-1F3FB",
- "digest": "f57e6c525e8830779ea5026590eec3ca10869dc438a0c779734b617d04f28d21"
- },
- {
- "name": "thumbsup_tone2",
- "unicode": "1F44D-1F3FC",
+ "thumbsup_tone2": {
+ "category": "people",
+ "moji": "👍🏼",
+ "unicodeVersion": "8.0",
"digest": "980eeeb1d8f5d79dae35c7ff81a576e980aa13a440d07b10e32e98ed34cbf7f1"
},
- {
- "name": "+1_tone2",
- "unicode": "1F44D-1F3FC",
- "digest": "980eeeb1d8f5d79dae35c7ff81a576e980aa13a440d07b10e32e98ed34cbf7f1"
- },
- {
- "name": "thumbsup_tone3",
- "unicode": "1F44D-1F3FD",
- "digest": "b3881060569e56e1dd75ca7960feab0e58ae51f440458781948d65d461116b4e"
- },
- {
- "name": "+1_tone3",
- "unicode": "1F44D-1F3FD",
+ "thumbsup_tone3": {
+ "category": "people",
+ "moji": "👍🏽",
+ "unicodeVersion": "8.0",
"digest": "b3881060569e56e1dd75ca7960feab0e58ae51f440458781948d65d461116b4e"
},
- {
- "name": "thumbsup_tone4",
- "unicode": "1F44D-1F3FE",
+ "thumbsup_tone4": {
+ "category": "people",
+ "moji": "👍🏾",
+ "unicodeVersion": "8.0",
"digest": "86fbe2c95414bce5e38fb5c33da31305d7942fca2c9c79168dcffdbd895e9ad6"
},
- {
- "name": "+1_tone4",
- "unicode": "1F44D-1F3FE",
- "digest": "86fbe2c95414bce5e38fb5c33da31305d7942fca2c9c79168dcffdbd895e9ad6"
- },
- {
- "name": "thumbsup_tone5",
- "unicode": "1F44D-1F3FF",
- "digest": "49fa63ff725c746a18649df16c8fab69bad88bbb564884df79d1d15f553b7343"
- },
- {
- "name": "+1_tone5",
- "unicode": "1F44D-1F3FF",
+ "thumbsup_tone5": {
+ "category": "people",
+ "moji": "👍🏿",
+ "unicodeVersion": "8.0",
"digest": "49fa63ff725c746a18649df16c8fab69bad88bbb564884df79d1d15f553b7343"
},
- {
- "name": "thunder_cloud_rain",
- "unicode": "26C8",
- "digest": "dacc20b4f6b68e5834aa1b8391afa5e83b5e6eb28e2d2174d3a68186a770506d"
- },
- {
- "name": "thunder_cloud_and_rain",
- "unicode": "26C8",
+ "thunder_cloud_rain": {
+ "category": "nature",
+ "moji": "⛈",
+ "unicodeVersion": "5.2",
"digest": "dacc20b4f6b68e5834aa1b8391afa5e83b5e6eb28e2d2174d3a68186a770506d"
},
- {
- "name": "ticket",
- "unicode": "1F3AB",
+ "ticket": {
+ "category": "activity",
+ "moji": "🎫",
+ "unicodeVersion": "6.0",
"digest": "b4326fe7761940216e6c76ee2928110a6b37bf913da9d694e96557e7c7c10420"
},
- {
- "name": "tickets",
- "unicode": "1F39F",
- "digest": "fb73358c3697c04fcfde6a1e705b1c3b47635b93b9cadfe31d5657566c7d190a"
- },
- {
- "name": "admission_tickets",
- "unicode": "1F39F",
+ "tickets": {
+ "category": "activity",
+ "moji": "🎟",
+ "unicodeVersion": "7.0",
"digest": "fb73358c3697c04fcfde6a1e705b1c3b47635b93b9cadfe31d5657566c7d190a"
},
- {
- "name": "tiger",
- "unicode": "1F42F",
+ "tiger": {
+ "category": "nature",
+ "moji": "🐯",
+ "unicodeVersion": "6.0",
"digest": "e139531e6c930bc46242dc0ed274661229de026b5419d8ea8f99fdb0f8a719ab"
},
- {
- "name": "tiger2",
- "unicode": "1F405",
+ "tiger2": {
+ "category": "nature",
+ "moji": "🐅",
+ "unicodeVersion": "6.0",
"digest": "f930cc8714198310d9b0edca6baff243ac5a3320f75fadb56fa5acc6fe34ff24"
},
- {
- "name": "timer",
- "unicode": "23F2",
+ "timer": {
+ "category": "objects",
+ "moji": "⏲",
+ "unicodeVersion": "6.0",
"digest": "69b33f219523d89d81cbbc070ad7e528711e4b34e124a50acb12a0280a34d0b0"
},
- {
- "name": "timer_clock",
- "unicode": "23F2",
- "digest": "69b33f219523d89d81cbbc070ad7e528711e4b34e124a50acb12a0280a34d0b0"
- },
- {
- "name": "tired_face",
- "unicode": "1F62B",
+ "tired_face": {
+ "category": "people",
+ "moji": "😫",
+ "unicodeVersion": "6.0",
"digest": "775739bc9324517e614878ca0960d793df97775feeb62b14dbfb311a42a21802"
},
- {
- "name": "tm",
- "unicode": "2122",
+ "tm": {
+ "category": "symbols",
+ "moji": "™",
+ "unicodeVersion": "1.1",
"digest": "7d9fafdb72d91860478fc185719f289f359eab2c368a132cb936a269e2ab6a24"
},
- {
- "name": "toilet",
- "unicode": "1F6BD",
+ "toilet": {
+ "category": "objects",
+ "moji": "🚽",
+ "unicodeVersion": "6.0",
"digest": "0d1b0dd0078f51104e8632a0726e1b3f075561a1ffa8a2546602de15798415d0"
},
- {
- "name": "tokyo_tower",
- "unicode": "1F5FC",
+ "tokyo_tower": {
+ "category": "travel",
+ "moji": "🗼",
+ "unicodeVersion": "6.0",
"digest": "73eaf6fd59d16396673afef620c6d928857d5cf616e95a40eaf2861686e0956a"
},
- {
- "name": "tomato",
- "unicode": "1F345",
+ "tomato": {
+ "category": "food",
+ "moji": "🍅",
+ "unicodeVersion": "6.0",
"digest": "d092d8ad381d542e59b6a82b4f1ef0d10fc1ed48460952375c6c5c6258cea111"
},
- {
- "name": "tone1",
- "unicode": "1F3FB",
+ "tone1": {
+ "category": "modifier",
+ "moji": "🏻",
+ "unicodeVersion": "8.0",
"digest": "5c62003a098b774c068be45d658db3c0dd38483c0871f7c8ae293bc1222c4f0c"
},
- {
- "name": "tone2",
- "unicode": "1F3FC",
+ "tone2": {
+ "category": "modifier",
+ "moji": "🏼",
+ "unicodeVersion": "8.0",
"digest": "3c636ecbc4e58c7a360f2338daaf44e7da598fd07e0ba1514bb5c0f83fc8819f"
},
- {
- "name": "tone3",
- "unicode": "1F3FD",
+ "tone3": {
+ "category": "modifier",
+ "moji": "🏽",
+ "unicodeVersion": "8.0",
"digest": "398a1e5441b64c9c2d033bbc01d7a8d90b4db30ea9f30e28f0a9120c72a48df8"
},
- {
- "name": "tone4",
- "unicode": "1F3FE",
+ "tone4": {
+ "category": "modifier",
+ "moji": "🏾",
+ "unicodeVersion": "8.0",
"digest": "ff4a12195aeb7494c785b81266efad8cd60c8022c407a0fc032a02e8b83216b3"
},
- {
- "name": "tone5",
- "unicode": "1F3FF",
+ "tone5": {
+ "category": "modifier",
+ "moji": "🏿",
+ "unicodeVersion": "8.0",
"digest": "9e9f0125b5d57011b7456c84719e6be6cf71d06c1b198081d0937c0979164a81"
},
- {
- "name": "tongue",
- "unicode": "1F445",
+ "tongue": {
+ "category": "people",
+ "moji": "👅",
+ "unicodeVersion": "6.0",
"digest": "286e9d2583c371431d6fc979dd4ab48981676da26baada51a846657a3654c19b"
},
- {
- "name": "tools",
- "unicode": "1F6E0",
- "digest": "bf08d60dedc06de73d04dab05703bb8ad81989c72b5035d1a07821e51096f158"
- },
- {
- "name": "hammer_and_wrench",
- "unicode": "1F6E0",
+ "tools": {
+ "category": "objects",
+ "moji": "🛠",
+ "unicodeVersion": "7.0",
"digest": "bf08d60dedc06de73d04dab05703bb8ad81989c72b5035d1a07821e51096f158"
},
- {
- "name": "top",
- "unicode": "1F51D",
+ "top": {
+ "category": "symbols",
+ "moji": "🔝",
+ "unicodeVersion": "6.0",
"digest": "c9a9f25b17db014e76b6be54aa07ef89bb18f8adb41b3199d180a559ff1d9ea5"
},
- {
- "name": "tophat",
- "unicode": "1F3A9",
+ "tophat": {
+ "category": "people",
+ "moji": "🎩",
+ "unicodeVersion": "6.0",
"digest": "43a45dfb5d6b57a63a0491f4e3ec780774c0301b53ed39a303a0bd803d16ed71"
},
- {
- "name": "track_next",
- "unicode": "23ED",
- "digest": "88592ef6c720a32aeb752322fb4c794bf5110a72408e21e898630452115c731c"
- },
- {
- "name": "next_track",
- "unicode": "23ED",
+ "track_next": {
+ "category": "symbols",
+ "moji": "⏭",
+ "unicodeVersion": "6.0",
"digest": "88592ef6c720a32aeb752322fb4c794bf5110a72408e21e898630452115c731c"
},
- {
- "name": "track_previous",
- "unicode": "23EE",
- "digest": "98c1b3d643768d94857fb762f6d26cfb87282b449a67792242e8b7068643ac87"
- },
- {
- "name": "previous_track",
- "unicode": "23EE",
+ "track_previous": {
+ "category": "symbols",
+ "moji": "⏮",
+ "unicodeVersion": "6.0",
"digest": "98c1b3d643768d94857fb762f6d26cfb87282b449a67792242e8b7068643ac87"
},
- {
- "name": "trackball",
- "unicode": "1F5B2",
+ "trackball": {
+ "category": "objects",
+ "moji": "🖲",
+ "unicodeVersion": "7.0",
"digest": "32a819a3129429f797ad434d0c40e263dc236808e34878c599ed2304b43702f5"
},
- {
- "name": "tractor",
- "unicode": "1F69C",
+ "tractor": {
+ "category": "travel",
+ "moji": "🚜",
+ "unicodeVersion": "6.0",
"digest": "5e4686290f1a4c9953ae208340b7d276f25b3b2197a43e52469aeb6450e93997"
},
- {
- "name": "traffic_light",
- "unicode": "1F6A5",
+ "traffic_light": {
+ "category": "travel",
+ "moji": "🚥",
+ "unicodeVersion": "6.0",
"digest": "d96aacade33d1ad3e0414f8a920513010f36eb7e5889774251c1d91148917ead"
},
- {
- "name": "train",
- "unicode": "1F68B",
+ "train": {
+ "category": "travel",
+ "moji": "🚋",
+ "unicodeVersion": "6.0",
"digest": "7423d17e131df7aadaa350b5d39dcbce3b28de331ff8b6703a3b2d0093963f4b"
},
- {
- "name": "train2",
- "unicode": "1F686",
+ "train2": {
+ "category": "travel",
+ "moji": "🚆",
+ "unicodeVersion": "6.0",
"digest": "06e65d549e771632f3c64287a38ba67236f9800ccb6a23c3b592bc010e24e122"
},
- {
- "name": "tram",
- "unicode": "1F68A",
+ "tram": {
+ "category": "travel",
+ "moji": "🚊",
+ "unicodeVersion": "6.0",
"digest": "21a7699f1a94f06dcb4d1e896448b98a4205f8efe902a8ac169a5005d11ab100"
},
- {
- "name": "triangular_flag_on_post",
- "unicode": "1F6A9",
+ "triangular_flag_on_post": {
+ "category": "objects",
+ "moji": "🚩",
+ "unicodeVersion": "6.0",
"digest": "1f5ce3828a42f5b1717bac1521d0502cf7081ad9f15e8ed292c1a65f0d1386da"
},
- {
- "name": "triangular_ruler",
- "unicode": "1F4D0",
+ "triangular_ruler": {
+ "category": "objects",
+ "moji": "📐",
+ "unicodeVersion": "6.0",
"digest": "a0367dcf663ec934f1fc7c88bfaccc02b229a896f60930a66bb02241c933e501"
},
- {
- "name": "trident",
- "unicode": "1F531",
+ "trident": {
+ "category": "symbols",
+ "moji": "🔱",
+ "unicodeVersion": "6.0",
"digest": "ee45920845d3b35c2e45b934cf30ce97bfe2f24c5d72ef1ac6e0842e52b50fc1"
},
- {
- "name": "triumph",
- "unicode": "1F624",
+ "triumph": {
+ "category": "people",
+ "moji": "😤",
+ "unicodeVersion": "6.0",
"digest": "4aa44b8e1682c1269624a359f4b0bf613553683b883d947561ab169d7f85da0f"
},
- {
- "name": "trolleybus",
- "unicode": "1F68E",
+ "trolleybus": {
+ "category": "travel",
+ "moji": "🚎",
+ "unicodeVersion": "6.0",
"digest": "f610b4fd1123f06778a8e3bb8f738d5b0079aeb0b0926b6a63268c0dd0ee03ed"
},
- {
- "name": "trophy",
- "unicode": "1F3C6",
+ "trophy": {
+ "category": "activity",
+ "moji": "🏆",
+ "unicodeVersion": "6.0",
"digest": "50cfbedac18bf0fa5dec727643e15ec47f64068944b536e97518ee3be4f08006"
},
- {
- "name": "tropical_drink",
- "unicode": "1F379",
+ "tropical_drink": {
+ "category": "food",
+ "moji": "🍹",
+ "unicodeVersion": "6.0",
"digest": "54144fce60d650f426b1edf09e47c70b2762222398c1fe40231881f074603a69"
},
- {
- "name": "tropical_fish",
- "unicode": "1F420",
+ "tropical_fish": {
+ "category": "nature",
+ "moji": "🐠",
+ "unicodeVersion": "6.0",
"digest": "fd92100aaa9328da35e6090388824921b9726b474d1432a926d2cf9c45ad6528"
},
- {
- "name": "truck",
- "unicode": "1F69A",
+ "truck": {
+ "category": "travel",
+ "moji": "🚚",
+ "unicodeVersion": "6.0",
"digest": "0d1571e58e900abc453df0ff683fe7acb5906ecbdd52ab35b7101074359faf18"
},
- {
- "name": "trumpet",
- "unicode": "1F3BA",
+ "trumpet": {
+ "category": "activity",
+ "moji": "🎺",
+ "unicodeVersion": "6.0",
"digest": "cea3614c309f5573f328f4603120dbe930016a35f0dfa400b0d968fe9fff2d55"
},
- {
- "name": "tulip",
- "unicode": "1F337",
+ "tulip": {
+ "category": "nature",
+ "moji": "🌷",
+ "unicodeVersion": "6.0",
"digest": "e744e8dbbdc6b126bd5b15aad56b524191de5a604189f4ab6d96730dfef4d086"
},
- {
- "name": "tumbler_glass",
- "unicode": "1F943",
- "digest": "7a38658274b9ff28836725a1dbfad49b8fa3af5ec8385e629db6bfdc7d93907a"
- },
- {
- "name": "whisky",
- "unicode": "1F943",
+ "tumbler_glass": {
+ "category": "food",
+ "moji": "🥃",
+ "unicodeVersion": "9.0",
"digest": "7a38658274b9ff28836725a1dbfad49b8fa3af5ec8385e629db6bfdc7d93907a"
},
- {
- "name": "turkey",
- "unicode": "1F983",
+ "turkey": {
+ "category": "nature",
+ "moji": "🦃",
+ "unicodeVersion": "8.0",
"digest": "bf5daef15716b66636a5fdb6d059420521443c0603e2d56bd7c99c791a7285f4"
},
- {
- "name": "turtle",
- "unicode": "1F422",
+ "turtle": {
+ "category": "nature",
+ "moji": "🐢",
+ "unicodeVersion": "6.0",
"digest": "588c35fb42c9502a908e9805517d4cc8c4ba4e74c9beed4035779fea1efe14f8"
},
- {
- "name": "tv",
- "unicode": "1F4FA",
+ "tv": {
+ "category": "objects",
+ "moji": "📺",
+ "unicodeVersion": "6.0",
"digest": "1279f3f3955a58dbbf74e248fc914b0bdba9c4c6b6a5176e9d12bf2750ecfeb4"
},
- {
- "name": "twisted_rightwards_arrows",
- "unicode": "1F500",
+ "twisted_rightwards_arrows": {
+ "category": "symbols",
+ "moji": "🔀",
+ "unicodeVersion": "6.0",
"digest": "fed07eebc2cf0d977ca0826bbd80defafbbcf118508444148f47b58949ebe27c"
},
- {
- "name": "two",
- "unicode": "0032-20E3",
+ "two": {
+ "category": "symbols",
+ "moji": "2️⃣",
+ "unicodeVersion": "3.0",
"digest": "b346f51f6523b02ebcbd753256804e2f9cc1574c96aa634362bf9401dac2c661"
},
- {
- "name": "two_hearts",
- "unicode": "1F495",
+ "two_hearts": {
+ "category": "symbols",
+ "moji": "💕",
+ "unicodeVersion": "6.0",
"digest": "6ded120a59aed790b441ec8fbbdea6f5cbfb4fa48e9e4b224cc29c9fde2d2e4c"
},
- {
- "name": "two_men_holding_hands",
- "unicode": "1F46C",
+ "two_men_holding_hands": {
+ "category": "people",
+ "moji": "👬",
+ "unicodeVersion": "6.0",
"digest": "bfcf9e20a67d00262cdf6e85f1acd545dda91f2e370d68bfd41ce02f232a2987"
},
- {
- "name": "two_women_holding_hands",
- "unicode": "1F46D",
+ "two_women_holding_hands": {
+ "category": "people",
+ "moji": "👭",
+ "unicodeVersion": "6.0",
"digest": "9d9d2b37a7f8e16fde1468dd8b5645003ea81ae4bf8bcf68471e2381845dd0dd"
},
- {
- "name": "u5272",
- "unicode": "1F239",
+ "u5272": {
+ "category": "symbols",
+ "moji": "🈹",
+ "unicodeVersion": "6.0",
"digest": "01e6cb8f74ea3c19fdade59c2d13d158b90dc6b4b293421b2014b7478bf20870"
},
- {
- "name": "u5408",
- "unicode": "1F234",
+ "u5408": {
+ "category": "symbols",
+ "moji": "🈴",
+ "unicodeVersion": "6.0",
"digest": "084cdbd5436670ea4dc22010e269c1ab7b0432897b8675301e69120374bcdd14"
},
- {
- "name": "u55b6",
- "unicode": "1F23A",
+ "u55b6": {
+ "category": "symbols",
+ "moji": "🈺",
+ "unicodeVersion": "6.0",
"digest": "c1017023d20d4aae78d59342dd3bfc5282716ea0601d9a8c2476335cbf7a2e12"
},
- {
- "name": "u6307",
- "unicode": "1F22F",
+ "u6307": {
+ "category": "symbols",
+ "moji": "🈯",
+ "unicodeVersion": "5.2",
"digest": "f459b092b974f459db1fb9cc13617a448b2e4f2b4dc46cc316d8c46af6e7d8bd"
},
- {
- "name": "u6708",
- "unicode": "1F237",
+ "u6708": {
+ "category": "symbols",
+ "moji": "🈷",
+ "unicodeVersion": "6.0",
"digest": "928815abf5b30f92efe5168de0c7e6cf8c17899a03e358ab42f42667e0a4a04c"
},
- {
- "name": "u6709",
- "unicode": "1F236",
+ "u6709": {
+ "category": "symbols",
+ "moji": "🈶",
+ "unicodeVersion": "6.0",
"digest": "f63a48ee06c892d24acec8b5634c021658d2ebde67a42d8faa86f27804a9f26d"
},
- {
- "name": "u6e80",
- "unicode": "1F235",
+ "u6e80": {
+ "category": "symbols",
+ "moji": "🈵",
+ "unicodeVersion": "6.0",
"digest": "489181d90a5e43068459530673a153e4af04fdad8514ec341ff7afbcfd366c3b"
},
- {
- "name": "u7121",
- "unicode": "1F21A",
+ "u7121": {
+ "category": "symbols",
+ "moji": "🈚",
+ "unicodeVersion": "5.2",
"digest": "9c50fd2ba14221affd2dcd3746322c2137dd75458493f4d385b544eb5bd8d6cd"
},
- {
- "name": "u7533",
- "unicode": "1F238",
+ "u7533": {
+ "category": "symbols",
+ "moji": "🈸",
+ "unicodeVersion": "6.0",
"digest": "2b05819b380a2ea47cc5fde8fcce3d53922fd223d6f5bd83d696d44175b69f18"
},
- {
- "name": "u7981",
- "unicode": "1F232",
+ "u7981": {
+ "category": "symbols",
+ "moji": "🈲",
+ "unicodeVersion": "6.0",
"digest": "adbe12601b22972003ddebcb0bd1532b979aa9c78bfdc147511854b5014eabc0"
},
- {
- "name": "u7a7a",
- "unicode": "1F233",
+ "u7a7a": {
+ "category": "symbols",
+ "moji": "🈳",
+ "unicodeVersion": "6.0",
"digest": "b9ee0ec7bb0b86c3eb73d4dbbb91848c427bf356ae30a263b9b44bd9bd784482"
},
- {
- "name": "umbrella",
- "unicode": "2614",
+ "umbrella": {
+ "category": "nature",
+ "moji": "☔",
+ "unicodeVersion": "4.0",
"digest": "0328a2f48b7df47905e2655460e524c0794ef12d3d7c32a049a10892d5662f77"
},
- {
- "name": "umbrella2",
- "unicode": "2602",
+ "umbrella2": {
+ "category": "nature",
+ "moji": "☂",
+ "unicodeVersion": "1.1",
"digest": "2f6a58110dc590480a822a3ffa2b5bc86f295e0c994a4a632837d25d4cf9fc58"
},
- {
- "name": "unamused",
- "unicode": "1F612",
+ "unamused": {
+ "category": "people",
+ "moji": "😒",
+ "unicodeVersion": "6.0",
"digest": "0d597088e3e7880918d0166e5c69243b18fe64afa31685c39bfdbc71494aa132"
},
- {
- "name": "underage",
- "unicode": "1F51E",
+ "underage": {
+ "category": "symbols",
+ "moji": "🔞",
+ "unicodeVersion": "6.0",
"digest": "b6b194614ca714ac2b1c2c17b75fe5922c7fdadb3d1157ba89ab2a5d03494a67"
},
- {
- "name": "unicorn",
- "unicode": "1F984",
- "digest": "f71bb485a7c208e999dd45f2b36d7b7d517898c0627947926b05aa28603804ca"
- },
- {
- "name": "unicorn_face",
- "unicode": "1F984",
+ "unicorn": {
+ "category": "nature",
+ "moji": "🦄",
+ "unicodeVersion": "8.0",
"digest": "f71bb485a7c208e999dd45f2b36d7b7d517898c0627947926b05aa28603804ca"
},
- {
- "name": "unlock",
- "unicode": "1F513",
+ "unlock": {
+ "category": "objects",
+ "moji": "🔓",
+ "unicodeVersion": "6.0",
"digest": "9554ef3a6a315938b873e77970d9b0212e61f13c6cc36e4f17f87acc930a9a53"
},
- {
- "name": "up",
- "unicode": "1F199",
+ "up": {
+ "category": "symbols",
+ "moji": "🆙",
+ "unicodeVersion": "6.0",
"digest": "ff2554ccf08c7208b38794c5fa3d9a93a46ff191a49401195d8f740846121906"
},
- {
- "name": "upside_down",
- "unicode": "1F643",
- "digest": "5129121f0a28f5b334268c28565de26a5907559568deca11de6ec620b097dfe1"
- },
- {
- "name": "upside_down_face",
- "unicode": "1F643",
+ "upside_down": {
+ "category": "people",
+ "moji": "🙃",
+ "unicodeVersion": "8.0",
"digest": "5129121f0a28f5b334268c28565de26a5907559568deca11de6ec620b097dfe1"
},
- {
- "name": "urn",
- "unicode": "26B1",
- "digest": "9bebf589eed8dd361f6a03cd1b325078f2cd0e82270ef63a7dd1b6aee08cd1e6"
- },
- {
- "name": "funeral_urn",
- "unicode": "26B1",
+ "urn": {
+ "category": "objects",
+ "moji": "⚱",
+ "unicodeVersion": "4.1",
"digest": "9bebf589eed8dd361f6a03cd1b325078f2cd0e82270ef63a7dd1b6aee08cd1e6"
},
- {
- "name": "v",
- "unicode": "270C",
+ "v": {
+ "category": "people",
+ "moji": "✌",
+ "unicodeVersion": "1.1",
"digest": "9825bf440df289a8edf8ede494e8c778dc63c95f967f4d7bbea3245cf4f558ec"
},
- {
- "name": "v_tone1",
- "unicode": "270C-1F3FB",
+ "v_tone1": {
+ "category": "people",
+ "moji": "✌🏻",
+ "unicodeVersion": "8.0",
"digest": "76e358250d9ca519b60b8d7b6a32900700d784433dcc609e9442254a410f6e37"
},
- {
- "name": "v_tone2",
- "unicode": "270C-1F3FC",
+ "v_tone2": {
+ "category": "people",
+ "moji": "✌🏼",
+ "unicodeVersion": "8.0",
"digest": "4081b674be8416136022523fa9f29ec70a0f7e3aa05ca13152606609f3fd003c"
},
- {
- "name": "v_tone3",
- "unicode": "270C-1F3FD",
+ "v_tone3": {
+ "category": "people",
+ "moji": "✌🏽",
+ "unicodeVersion": "8.0",
"digest": "b6afb3a4c78384280610b953592d378241c75597a82aa6d16c86a993f8d8f3b0"
},
- {
- "name": "v_tone4",
- "unicode": "270C-1F3FE",
+ "v_tone4": {
+ "category": "people",
+ "moji": "✌🏾",
+ "unicodeVersion": "8.0",
"digest": "7ddc3cdd0138da2c8d7f6d8257ffdb8801496043e8a2395f93b0663447ac7fce"
},
- {
- "name": "v_tone5",
- "unicode": "270C-1F3FF",
+ "v_tone5": {
+ "category": "people",
+ "moji": "✌🏿",
+ "unicodeVersion": "8.0",
"digest": "a85dc5c589f0d1cf32f8bfa5c82e5c11c40b35439636914686a2f06f7359f539"
},
- {
- "name": "vertical_traffic_light",
- "unicode": "1F6A6",
+ "vertical_traffic_light": {
+ "category": "travel",
+ "moji": "🚦",
+ "unicodeVersion": "6.0",
"digest": "8cfd49a8f96b15a8313ef855f2e234ea3fa58332e68896dea34760740de9f020"
},
- {
- "name": "vhs",
- "unicode": "1F4FC",
+ "vhs": {
+ "category": "objects",
+ "moji": "📼",
+ "unicodeVersion": "6.0",
"digest": "3fb1acaf25805cf86f8d40ee2c17cf25da587b7ca93b931167ab43fce041eee8"
},
- {
- "name": "vibration_mode",
- "unicode": "1F4F3",
+ "vibration_mode": {
+ "category": "symbols",
+ "moji": "📳",
+ "unicodeVersion": "6.0",
"digest": "c9a8899222f46fe51dd8cee3e59f77c48268f0b7cfae2bcb34a791213acb1755"
},
- {
- "name": "video_camera",
- "unicode": "1F4F9",
+ "video_camera": {
+ "category": "objects",
+ "moji": "📹",
+ "unicodeVersion": "6.0",
"digest": "62e56f26c286a7964ef1021f0f23fcb4b38cdcfb5b5af569b472340c412c619a"
},
- {
- "name": "video_game",
- "unicode": "1F3AE",
+ "video_game": {
+ "category": "activity",
+ "moji": "🎮",
+ "unicodeVersion": "6.0",
"digest": "2787e302aa9e6fd7e9dc382c9bc7f5fbf244ef4940e08a4f9e80d33324f3032e"
},
- {
- "name": "violin",
- "unicode": "1F3BB",
+ "violin": {
+ "category": "activity",
+ "moji": "🎻",
+ "unicodeVersion": "6.0",
"digest": "1e69d531ce2b5d5bf1dd9470187dbbe76f479d14428834b6a9e2bf5296dc0ec9"
},
- {
- "name": "virgo",
- "unicode": "264D",
+ "virgo": {
+ "category": "symbols",
+ "moji": "♍",
+ "unicodeVersion": "1.1",
"digest": "0f75e9c228bc467fd0cec0f93f0e087c943bc5fb1d945fb0d4de53d07718388e"
},
- {
- "name": "volcano",
- "unicode": "1F30B",
+ "volcano": {
+ "category": "travel",
+ "moji": "🌋",
+ "unicodeVersion": "6.0",
"digest": "41c92ef88ca533df342a0ebe59d2b676873bfa944c3988495b8a96060a9b8e16"
},
- {
- "name": "volleyball",
- "unicode": "1F3D0",
+ "volleyball": {
+ "category": "activity",
+ "moji": "🏐",
+ "unicodeVersion": "8.0",
"digest": "774a83357f7aee890b4d4383236f0a90946dbd7c86aaabadc5753dcc9b4c9d69"
},
- {
- "name": "vs",
- "unicode": "1F19A",
+ "vs": {
+ "category": "symbols",
+ "moji": "🆚",
+ "unicodeVersion": "6.0",
"digest": "ac943e4c737459c2e1adbac8b71d3fdaebb704dbaf5713012e7a77beb09db1ef"
},
- {
- "name": "vulcan",
- "unicode": "1F596",
- "digest": "b4d409a0b019e7b06333cefd15ea46cb54aef5132d86e8ba361c1c3b911fe265"
- },
- {
- "name": "raised_hand_with_part_between_middle_and_ring_fingers",
- "unicode": "1F596",
+ "vulcan": {
+ "category": "people",
+ "moji": "🖖",
+ "unicodeVersion": "7.0",
"digest": "b4d409a0b019e7b06333cefd15ea46cb54aef5132d86e8ba361c1c3b911fe265"
},
- {
- "name": "vulcan_tone1",
- "unicode": "1F596-1F3FB",
- "digest": "cc6072c85031b5081995f98a57f09ab177168318f69a51f3acc63251760499a4"
- },
- {
- "name": "raised_hand_with_part_between_middle_and_ring_fingers_tone1",
- "unicode": "1F596-1F3FB",
+ "vulcan_tone1": {
+ "category": "people",
+ "moji": "🖖🏻",
+ "unicodeVersion": "8.0",
"digest": "cc6072c85031b5081995f98a57f09ab177168318f69a51f3acc63251760499a4"
},
- {
- "name": "vulcan_tone2",
- "unicode": "1F596-1F3FC",
- "digest": "858bd5a1ac91dc4d7735f57ba4dd69d39138aa6dac1c80cfc05de30a59a5bc33"
- },
- {
- "name": "raised_hand_with_part_between_middle_and_ring_fingers_tone2",
- "unicode": "1F596-1F3FC",
+ "vulcan_tone2": {
+ "category": "people",
+ "moji": "🖖🏼",
+ "unicodeVersion": "8.0",
"digest": "858bd5a1ac91dc4d7735f57ba4dd69d39138aa6dac1c80cfc05de30a59a5bc33"
},
- {
- "name": "vulcan_tone3",
- "unicode": "1F596-1F3FD",
+ "vulcan_tone3": {
+ "category": "people",
+ "moji": "🖖🏽",
+ "unicodeVersion": "8.0",
"digest": "2f74b6f3eab2a75063591b66f1c7350af0d23153e1427af91de20c48a5f4a54a"
},
- {
- "name": "raised_hand_with_part_between_middle_and_ring_fingers_tone3",
- "unicode": "1F596-1F3FD",
- "digest": "2f74b6f3eab2a75063591b66f1c7350af0d23153e1427af91de20c48a5f4a54a"
- },
- {
- "name": "vulcan_tone4",
- "unicode": "1F596-1F3FE",
- "digest": "87cf8b87d3610f742857a9704b658462df32b4924d8f1ddba26f761e738c4e11"
- },
- {
- "name": "raised_hand_with_part_between_middle_and_ring_fingers_tone4",
- "unicode": "1F596-1F3FE",
+ "vulcan_tone4": {
+ "category": "people",
+ "moji": "🖖🏾",
+ "unicodeVersion": "8.0",
"digest": "87cf8b87d3610f742857a9704b658462df32b4924d8f1ddba26f761e738c4e11"
},
- {
- "name": "vulcan_tone5",
- "unicode": "1F596-1F3FF",
+ "vulcan_tone5": {
+ "category": "people",
+ "moji": "🖖🏿",
+ "unicodeVersion": "8.0",
"digest": "11e9ff62f2385edeb477dbf66c63734536531def5771daf80b66a3425ac71493"
},
- {
- "name": "raised_hand_with_part_between_middle_and_ring_fingers_tone5",
- "unicode": "1F596-1F3FF",
- "digest": "11e9ff62f2385edeb477dbf66c63734536531def5771daf80b66a3425ac71493"
- },
- {
- "name": "walking",
- "unicode": "1F6B6",
+ "walking": {
+ "category": "people",
+ "moji": "🚶",
+ "unicodeVersion": "6.0",
"digest": "ae77471fe1e8a734d11711cdb589f64347c35d6ee2fc10f6db16ac550c0557fa"
},
- {
- "name": "walking_tone1",
- "unicode": "1F6B6-1F3FB",
+ "walking_tone1": {
+ "category": "people",
+ "moji": "🚶🏻",
+ "unicodeVersion": "8.0",
"digest": "3de871c234e1340ccf95338df7babd94d175cfcb17a57b5a74d950e0a31f03b1"
},
- {
- "name": "walking_tone2",
- "unicode": "1F6B6-1F3FC",
+ "walking_tone2": {
+ "category": "people",
+ "moji": "🚶🏼",
+ "unicodeVersion": "8.0",
"digest": "620eb7bfb753a331a5822b02bdaf08d8dde7b573efd210287a3d3dfdd84a40b9"
},
- {
- "name": "walking_tone3",
- "unicode": "1F6B6-1F3FD",
+ "walking_tone3": {
+ "category": "people",
+ "moji": "🚶🏽",
+ "unicodeVersion": "8.0",
"digest": "ff39545acc2256006128f8c186433c28052b8c9aaec46fe06f25cff02c71f6b8"
},
- {
- "name": "walking_tone4",
- "unicode": "1F6B6-1F3FE",
+ "walking_tone4": {
+ "category": "people",
+ "moji": "🚶🏾",
+ "unicodeVersion": "8.0",
"digest": "a9499d142392977a9b9e54fb957952359e9bdffce7ec2f1e8320523d185fb066"
},
- {
- "name": "walking_tone5",
- "unicode": "1F6B6-1F3FF",
+ "walking_tone5": {
+ "category": "people",
+ "moji": "🚶🏿",
+ "unicodeVersion": "8.0",
"digest": "b47a4c48ce40298f842f454fc1abccae70f69725d73ee2c80e4018f4c4065d7d"
},
- {
- "name": "waning_crescent_moon",
- "unicode": "1F318",
+ "waning_crescent_moon": {
+ "category": "nature",
+ "moji": "🌘",
+ "unicodeVersion": "6.0",
"digest": "2ec7896eefcf821e0ea013556a17af59e997503662c07f080d0a84ab13ef4cf1"
},
- {
- "name": "waning_gibbous_moon",
- "unicode": "1F316",
+ "waning_gibbous_moon": {
+ "category": "nature",
+ "moji": "🌖",
+ "unicodeVersion": "6.0",
"digest": "ce2f5aca8fccdacaaf174d10da4e493e853e4608cc4d159aa3081d108a8b58d5"
},
- {
- "name": "warning",
- "unicode": "26A0",
+ "warning": {
+ "category": "symbols",
+ "moji": "⚠",
+ "unicodeVersion": "4.0",
"digest": "745f1d203958f42bf37ecb5909cd0819934e300308ba0ff20964c8c203092f90"
},
- {
- "name": "wastebasket",
- "unicode": "1F5D1",
+ "wastebasket": {
+ "category": "objects",
+ "moji": "🗑",
+ "unicodeVersion": "7.0",
"digest": "221a1b6d9975051038d9d97e18a16556cdf4254a6bca4c29bf1c51f306c79f2a"
},
- {
- "name": "watch",
- "unicode": "231A",
+ "watch": {
+ "category": "objects",
+ "moji": "⌚",
+ "unicodeVersion": "1.1",
"digest": "acc0c96751404a789b3085f10425cf34f942185215df459515d2439cde3efc6b"
},
- {
- "name": "water_buffalo",
- "unicode": "1F403",
+ "water_buffalo": {
+ "category": "nature",
+ "moji": "🐃",
+ "unicodeVersion": "6.0",
"digest": "ba6a840d4f57f8f9f3e9f29b8a030faf02a3a3d912e3e31b067616b2ac48a3d1"
},
- {
- "name": "water_polo",
- "unicode": "1F93D",
+ "water_polo": {
+ "category": "activity",
+ "moji": "🤽",
+ "unicodeVersion": "9.0",
"digest": "fc77e1d2a84a9f4cf0cf19c1ea10cf137cf0940b9103a523121eda87677ad148"
},
- {
- "name": "water_polo_tone1",
- "unicode": "1F93D-1F3FB",
+ "water_polo_tone1": {
+ "category": "activity",
+ "moji": "🤽🏻",
+ "unicodeVersion": "9.0",
"digest": "3be28384edd29ada8109f07720d601a9d5866ed63e6234efe9ee1a194ed5d0c5"
},
- {
- "name": "water_polo_tone2",
- "unicode": "1F93D-1F3FC",
+ "water_polo_tone2": {
+ "category": "activity",
+ "moji": "🤽🏼",
+ "unicodeVersion": "9.0",
"digest": "afcd3f28c6719f869ca79a6fd1ccade2ea976ade844fbc1081fc72865bcb652f"
},
- {
- "name": "water_polo_tone3",
- "unicode": "1F93D-1F3FD",
+ "water_polo_tone3": {
+ "category": "activity",
+ "moji": "🤽🏽",
+ "unicodeVersion": "9.0",
"digest": "d19481c9b82d9413e99c2652e020fd763f2b54408dedaffec8dfe80973ded407"
},
- {
- "name": "water_polo_tone4",
- "unicode": "1F93D-1F3FE",
+ "water_polo_tone4": {
+ "category": "activity",
+ "moji": "🤽🏾",
+ "unicodeVersion": "9.0",
"digest": "375972d882b627e8d525e632e58b30346fc3e01858d7d08d62a9d3bf8132bbc7"
},
- {
- "name": "water_polo_tone5",
- "unicode": "1F93D-1F3FF",
+ "water_polo_tone5": {
+ "category": "activity",
+ "moji": "🤽🏿",
+ "unicodeVersion": "9.0",
"digest": "a8e1ced1c5382a8147a1d1801a133cada9a0e52e41de6272e56c3c1f426f6048"
},
- {
- "name": "watermelon",
- "unicode": "1F349",
+ "watermelon": {
+ "category": "food",
+ "moji": "🍉",
+ "unicodeVersion": "6.0",
"digest": "42a3821d2e4dd595c93f5db7a5c70b7af486b8f0ddd3b9d26bc4e743a88e699a"
},
- {
- "name": "wave",
- "unicode": "1F44B",
+ "wave": {
+ "category": "people",
+ "moji": "👋",
+ "unicodeVersion": "6.0",
"digest": "cddbd764d471604446cbaca91f77f6c4119d1cfc2c856732ca0eaac4593cb736"
},
- {
- "name": "wave_tone1",
- "unicode": "1F44B-1F3FB",
+ "wave_tone1": {
+ "category": "people",
+ "moji": "👋🏻",
+ "unicodeVersion": "8.0",
"digest": "cf40797437ddf68ec0275f337e6aac4bed81e28da7636d56c9f817ddf8e2b30a"
},
- {
- "name": "wave_tone2",
- "unicode": "1F44B-1F3FC",
+ "wave_tone2": {
+ "category": "people",
+ "moji": "👋🏼",
+ "unicodeVersion": "8.0",
"digest": "12c8a3e82c03ee35a734c642be482ba2d9d5948dacf91ec1fda243316dd4a0d0"
},
- {
- "name": "wave_tone3",
- "unicode": "1F44B-1F3FD",
+ "wave_tone3": {
+ "category": "people",
+ "moji": "👋🏽",
+ "unicodeVersion": "8.0",
"digest": "ebcaef43e21b475f76de811d4f4d1a67d9393973b57b03876e02164345a2ba4a"
},
- {
- "name": "wave_tone4",
- "unicode": "1F44B-1F3FE",
+ "wave_tone4": {
+ "category": "people",
+ "moji": "👋🏾",
+ "unicodeVersion": "8.0",
"digest": "7df7b70cf76766836ba146c3d91b6104930c384450cf2688426e60c1c06a1fc8"
},
- {
- "name": "wave_tone5",
- "unicode": "1F44B-1F3FF",
+ "wave_tone5": {
+ "category": "people",
+ "moji": "👋🏿",
+ "unicodeVersion": "8.0",
"digest": "8dfdba6aeff5d7dfd807467d431a137547726b34d021f1a5a0b74e155d270ea7"
},
- {
- "name": "wavy_dash",
- "unicode": "3030",
+ "wavy_dash": {
+ "category": "symbols",
+ "moji": "〰",
+ "unicodeVersion": "1.1",
"digest": "7b1968474f01d12fd09a1f2572282927138d9e9d6a3642de4bf68af80a8c3738"
},
- {
- "name": "waxing_crescent_moon",
- "unicode": "1F312",
+ "waxing_crescent_moon": {
+ "category": "nature",
+ "moji": "🌒",
+ "unicodeVersion": "6.0",
"digest": "852d7e55a19074d061fa3aa80d6b1e7e87a9280bdf44d94bbdbbe6d59178b1be"
},
- {
- "name": "waxing_gibbous_moon",
- "unicode": "1F314",
+ "waxing_gibbous_moon": {
+ "category": "nature",
+ "moji": "🌔",
+ "unicodeVersion": "6.0",
"digest": "a3a1c7cc72521a3f74929789a90e1c35d81ac86e21225c9f844d718d8940e3b3"
},
- {
- "name": "wc",
- "unicode": "1F6BE",
+ "wc": {
+ "category": "symbols",
+ "moji": "🚾",
+ "unicodeVersion": "6.0",
"digest": "4b95d54e0b53e4b705277917653503b32d6a143c2eaf6c547bc8e01c2dc23659"
},
- {
- "name": "weary",
- "unicode": "1F629",
+ "weary": {
+ "category": "people",
+ "moji": "😩",
+ "unicodeVersion": "6.0",
"digest": "3528f85540996cd5b562efe5421c495fc1bb414dc797bc20062783ae1b730847"
},
- {
- "name": "wedding",
- "unicode": "1F492",
+ "wedding": {
+ "category": "travel",
+ "moji": "💒",
+ "unicodeVersion": "6.0",
"digest": "980f3522cc4c19c3096e668032ea2cd19e7900cdc4b73bbb1c9b4c4d28dc78af"
},
- {
- "name": "whale",
- "unicode": "1F433",
+ "whale": {
+ "category": "nature",
+ "moji": "🐳",
+ "unicodeVersion": "6.0",
"digest": "6368fe4bc4a7f68aa2bd5386686a5f1b159feacbec16d59515f2b6e5d01adfbd"
},
- {
- "name": "whale2",
- "unicode": "1F40B",
+ "whale2": {
+ "category": "nature",
+ "moji": "🐋",
+ "unicodeVersion": "6.0",
"digest": "ccd3edf88167965f2abc18631ffb80e2532f728da35bc0c11144376685da18e8"
},
- {
- "name": "wheel_of_dharma",
- "unicode": "2638",
+ "wheel_of_dharma": {
+ "category": "symbols",
+ "moji": "☸",
+ "unicodeVersion": "1.1",
"digest": "4a0a13fcd507b9621686c8090bf340aa8770c064e0e3eb576fbae1229000d6da"
},
- {
- "name": "wheelchair",
- "unicode": "267F",
+ "wheelchair": {
+ "category": "symbols",
+ "moji": "♿",
+ "unicodeVersion": "4.1",
"digest": "f5250f2b4b5b4ffe6a6f77d30865c3f5d7173fc91aee547869589b2a96da91c8"
},
- {
- "name": "white_check_mark",
- "unicode": "2705",
+ "white_check_mark": {
+ "category": "symbols",
+ "moji": "✅",
+ "unicodeVersion": "6.0",
"digest": "45eb17bde6e503f22c8579d6e4d507ad6557a15f9eaad14aa716ec9ba1540876"
},
- {
- "name": "white_circle",
- "unicode": "26AA",
+ "white_circle": {
+ "category": "symbols",
+ "moji": "⚪",
+ "unicodeVersion": "4.1",
"digest": "2e7323fa4d1e3929e529d49210a0b82a043eae4f7c95128ec86b98c46fdb0e7c"
},
- {
- "name": "white_flower",
- "unicode": "1F4AE",
+ "white_flower": {
+ "category": "symbols",
+ "moji": "💮",
+ "unicodeVersion": "6.0",
"digest": "ace093b310eeefdecf4a4bdaf4fbcbb568457b0191ac80778a466ac5f3f4025a"
},
- {
- "name": "white_large_square",
- "unicode": "2B1C",
+ "white_large_square": {
+ "category": "symbols",
+ "moji": "⬜",
+ "unicodeVersion": "5.1",
"digest": "0db6957ee9ff7325b534b730fc05345a63d4ed9060f0f816807d0dcf004baa3e"
},
- {
- "name": "white_medium_small_square",
- "unicode": "25FD",
+ "white_medium_small_square": {
+ "category": "symbols",
+ "moji": "◽",
+ "unicodeVersion": "3.2",
"digest": "d79689981a7b38211c60a025a81e44fd39ac6ea4062e227cae3aab8f51572cd4"
},
- {
- "name": "white_medium_square",
- "unicode": "25FB",
+ "white_medium_square": {
+ "category": "symbols",
+ "moji": "◻",
+ "unicodeVersion": "3.2",
"digest": "6c4ce26d3f69667219f29ea18b04f3e79373024426275f25936e09a683e9a4fc"
},
- {
- "name": "white_small_square",
- "unicode": "25AB",
+ "white_small_square": {
+ "category": "symbols",
+ "moji": "▫",
+ "unicodeVersion": "1.1",
"digest": "ae0d35a6bbba4592b89b2f0f1f2d183efb2f93cf2a2136c0c195aab72f0bb1c8"
},
- {
- "name": "white_square_button",
- "unicode": "1F533",
+ "white_square_button": {
+ "category": "symbols",
+ "moji": "🔳",
+ "unicodeVersion": "6.0",
"digest": "797f3d9e44e88e940ffc118e52d0f709eec2ef14b13bdf873ad4b0c96cc0b042"
},
- {
- "name": "white_sun_cloud",
- "unicode": "1F325",
- "digest": "0e714038bb0a5b091dd4ad8829c5c72dece493e09da6d56ceadcd0b68e1c0fd5"
- },
- {
- "name": "white_sun_behind_cloud",
- "unicode": "1F325",
+ "white_sun_cloud": {
+ "category": "nature",
+ "moji": "🌥",
+ "unicodeVersion": "7.0",
"digest": "0e714038bb0a5b091dd4ad8829c5c72dece493e09da6d56ceadcd0b68e1c0fd5"
},
- {
- "name": "white_sun_rain_cloud",
- "unicode": "1F326",
+ "white_sun_rain_cloud": {
+ "category": "nature",
+ "moji": "🌦",
+ "unicodeVersion": "7.0",
"digest": "82fb2a91d43c7c511afed216e12f98e32aef4475e7f3c7ccc0f39732d2f7d5e5"
},
- {
- "name": "white_sun_behind_cloud_with_rain",
- "unicode": "1F326",
- "digest": "82fb2a91d43c7c511afed216e12f98e32aef4475e7f3c7ccc0f39732d2f7d5e5"
- },
- {
- "name": "white_sun_small_cloud",
- "unicode": "1F324",
- "digest": "0a6164cdadf2413555b7ef47b95f823f5a010f36d2dacfb1a38335a0f59e9601"
- },
- {
- "name": "white_sun_with_small_cloud",
- "unicode": "1F324",
+ "white_sun_small_cloud": {
+ "category": "nature",
+ "moji": "🌤",
+ "unicodeVersion": "7.0",
"digest": "0a6164cdadf2413555b7ef47b95f823f5a010f36d2dacfb1a38335a0f59e9601"
},
- {
- "name": "wilted_rose",
- "unicode": "1F940",
+ "wilted_rose": {
+ "category": "nature",
+ "moji": "🥀",
+ "unicodeVersion": "9.0",
"digest": "2c9e01ab9a61d057c71478b09ba7d82ae08f4a5a1c2212b7ad562b74f616677f"
},
- {
- "name": "wilted_flower",
- "unicode": "1F940",
- "digest": "2c9e01ab9a61d057c71478b09ba7d82ae08f4a5a1c2212b7ad562b74f616677f"
- },
- {
- "name": "wind_blowing_face",
- "unicode": "1F32C",
+ "wind_blowing_face": {
+ "category": "nature",
+ "moji": "🌬",
+ "unicodeVersion": "7.0",
"digest": "e4f63149cbc8829118571f6a93487b96d26665fc15d17d578cca4e5c752cd54f"
},
- {
- "name": "wind_chime",
- "unicode": "1F390",
+ "wind_chime": {
+ "category": "objects",
+ "moji": "🎐",
+ "unicodeVersion": "6.0",
"digest": "1b1b212fbd74a9edc62aee7ffab9bcf91d3a9f69bffb2be4b7fd527914c14ced"
},
- {
- "name": "wine_glass",
- "unicode": "1F377",
+ "wine_glass": {
+ "category": "food",
+ "moji": "🍷",
+ "unicodeVersion": "6.0",
"digest": "d99107d6809386bc5e219aa58ee4930d27b7c3a6d2b10deb9f523df369f766d1"
},
- {
- "name": "wink",
- "unicode": "1F609",
+ "wink": {
+ "category": "people",
+ "moji": "😉",
+ "unicodeVersion": "6.0",
"digest": "56e29994a47335a901d0c98fa141d26faae8f647a860517bd3615fa980921885"
},
- {
- "name": "wolf",
- "unicode": "1F43A",
+ "wolf": {
+ "category": "nature",
+ "moji": "🐺",
+ "unicodeVersion": "6.0",
"digest": "4a983f5ec8ec0872fcde7890e17605b1229064e5e194b6fca1c4259068d1caed"
},
- {
- "name": "woman",
- "unicode": "1F469",
+ "woman": {
+ "category": "people",
+ "moji": "👩",
+ "unicodeVersion": "6.0",
"digest": "a06a22a48eeb3aeb885321358fe234e97797ed33be17f52d232ce2830cfbcd97"
},
- {
- "name": "woman_tone1",
- "unicode": "1F469-1F3FB",
+ "woman_tone1": {
+ "category": "people",
+ "moji": "👩🏻",
+ "unicodeVersion": "8.0",
"digest": "c2e4b135c1dac6a0b002569a6ccd9d098f6cb18481c68b5d9115e11241a0978d"
},
- {
- "name": "woman_tone2",
- "unicode": "1F469-1F3FC",
+ "woman_tone2": {
+ "category": "people",
+ "moji": "👩🏼",
+ "unicodeVersion": "8.0",
"digest": "4848e650051214a53c4cd9f6d3d94158f77f65ecb34f891789de34ee0a713006"
},
- {
- "name": "woman_tone3",
- "unicode": "1F469-1F3FD",
+ "woman_tone3": {
+ "category": "people",
+ "moji": "👩🏽",
+ "unicodeVersion": "8.0",
"digest": "b6f751ad47da019cdfb9d6d78f9610adb92120abf204c30df79a9150b57dbdee"
},
- {
- "name": "woman_tone4",
- "unicode": "1F469-1F3FE",
+ "woman_tone4": {
+ "category": "people",
+ "moji": "👩🏾",
+ "unicodeVersion": "8.0",
"digest": "fd27d3a669dc34313fbfe518df7dc2ded3ade5dde695f8d773afe87bf8a8b0d4"
},
- {
- "name": "woman_tone5",
- "unicode": "1F469-1F3FF",
+ "woman_tone5": {
+ "category": "people",
+ "moji": "👩🏿",
+ "unicodeVersion": "8.0",
"digest": "9ae9b14dfff40fa60a565d89479727feeba4fd6ffea9acb353a81b14aba751d4"
},
- {
- "name": "womans_clothes",
- "unicode": "1F45A",
+ "womans_clothes": {
+ "category": "people",
+ "moji": "👚",
+ "unicodeVersion": "6.0",
"digest": "d12a27810780fe5cd8118ed4587e0c4e70dbe9bcd014c6866fe6a8c9c7c55698"
},
- {
- "name": "womans_hat",
- "unicode": "1F452",
+ "womans_hat": {
+ "category": "people",
+ "moji": "👒",
+ "unicodeVersion": "6.0",
"digest": "52a0255b3483085bd125d39b74516ab6a81003964f44995c2fac821e7ff93086"
},
- {
- "name": "womens",
- "unicode": "1F6BA",
+ "womens": {
+ "category": "symbols",
+ "moji": "🚺",
+ "unicodeVersion": "6.0",
"digest": "7e38964006f8b28dfa2b3e9b2b16553bb50c18a63455f556b0bff35ee172137e"
},
- {
- "name": "worried",
- "unicode": "1F61F",
+ "worried": {
+ "category": "people",
+ "moji": "😟",
+ "unicodeVersion": "6.1",
"digest": "5a073985e1344bc34201ef94a491f7f2b946f5828c9fdbc57eeb2dcd87ac3a6b"
},
- {
- "name": "wrench",
- "unicode": "1F527",
+ "wrench": {
+ "category": "objects",
+ "moji": "🔧",
+ "unicodeVersion": "6.0",
"digest": "81aae53bc892035b905bf3ec5b442a8ecc95027c5fa9eb51b7c3e7d8fad3f3f4"
},
- {
- "name": "wrestlers",
- "unicode": "1F93C",
- "digest": "9be983f3f9438f3ab8f6b643a958371d1e710c6d78e728f3465141811f05c2d5"
- },
- {
- "name": "wrestling",
- "unicode": "1F93C",
+ "wrestlers": {
+ "category": "activity",
+ "moji": "🤼",
+ "unicodeVersion": "9.0",
"digest": "9be983f3f9438f3ab8f6b643a958371d1e710c6d78e728f3465141811f05c2d5"
},
- {
- "name": "wrestlers_tone1",
- "unicode": "1F93C-1F3FB",
+ "wrestlers_tone1": {
+ "category": "activity",
+ "moji": "🤼🏻",
+ "unicodeVersion": "9.0",
"digest": "60461f83bfc93ce59dd027eab4782b7f206a7b142719fa72f301e047dc83a5d9"
},
- {
- "name": "wrestling_tone1",
- "unicode": "1F93C-1F3FB",
- "digest": "60461f83bfc93ce59dd027eab4782b7f206a7b142719fa72f301e047dc83a5d9"
- },
- {
- "name": "wrestlers_tone2",
- "unicode": "1F93C-1F3FC",
- "digest": "67ad93c86e6c58d552c18e7a0105cc81fd9bb0474da51f788eba2e4c14b4a636"
- },
- {
- "name": "wrestling_tone2",
- "unicode": "1F93C-1F3FC",
+ "wrestlers_tone2": {
+ "category": "activity",
+ "moji": "🤼🏼",
+ "unicodeVersion": "9.0",
"digest": "67ad93c86e6c58d552c18e7a0105cc81fd9bb0474da51f788eba2e4c14b4a636"
},
- {
- "name": "wrestlers_tone3",
- "unicode": "1F93C-1F3FD",
+ "wrestlers_tone3": {
+ "category": "activity",
+ "moji": "🤼🏽",
+ "unicodeVersion": "9.0",
"digest": "6bfd06c4435cabf2def153912040e05bf8db424fa383148ddda6d0ce8a8a3349"
},
- {
- "name": "wrestling_tone3",
- "unicode": "1F93C-1F3FD",
- "digest": "6bfd06c4435cabf2def153912040e05bf8db424fa383148ddda6d0ce8a8a3349"
- },
- {
- "name": "wrestlers_tone4",
- "unicode": "1F93C-1F3FE",
- "digest": "597312678834c4d288c238482879856d5eba4620deb1eaef495f428e2ba5f2a5"
- },
- {
- "name": "wrestling_tone4",
- "unicode": "1F93C-1F3FE",
+ "wrestlers_tone4": {
+ "category": "activity",
+ "moji": "🤼🏾",
+ "unicodeVersion": "9.0",
"digest": "597312678834c4d288c238482879856d5eba4620deb1eaef495f428e2ba5f2a5"
},
- {
- "name": "wrestlers_tone5",
- "unicode": "1F93C-1F3FF",
+ "wrestlers_tone5": {
+ "category": "activity",
+ "moji": "🤼🏿",
+ "unicodeVersion": "9.0",
"digest": "d6aebdf1e44fd825b9a5b3716aefbc53f4b4dbb73cb2a628c0f2994ebfd34614"
},
- {
- "name": "wrestling_tone5",
- "unicode": "1F93C-1F3FF",
- "digest": "d6aebdf1e44fd825b9a5b3716aefbc53f4b4dbb73cb2a628c0f2994ebfd34614"
- },
- {
- "name": "writing_hand",
- "unicode": "270D",
+ "writing_hand": {
+ "category": "people",
+ "moji": "✍",
+ "unicodeVersion": "1.1",
"digest": "110517ae4da5587e8b0662881658e27da4120bfacec54734fd6657831d4d782f"
},
- {
- "name": "writing_hand_tone1",
- "unicode": "270D-1F3FB",
+ "writing_hand_tone1": {
+ "category": "people",
+ "moji": "✍🏻",
+ "unicodeVersion": "8.0",
"digest": "2c7e2108e1990490b681343c1b01b4183d4f18fbdef792f113b2f87595e0dad0"
},
- {
- "name": "writing_hand_tone2",
- "unicode": "270D-1F3FC",
+ "writing_hand_tone2": {
+ "category": "people",
+ "moji": "✍🏼",
+ "unicodeVersion": "8.0",
"digest": "87ec8d44f472d301adbcbd50d8c852b609e46584057f59cc1527401db363c1bf"
},
- {
- "name": "writing_hand_tone3",
- "unicode": "270D-1F3FD",
+ "writing_hand_tone3": {
+ "category": "people",
+ "moji": "✍🏽",
+ "unicodeVersion": "8.0",
"digest": "4a48ddef91f7264e8fa9cca223554db22b3a2e3153e94b88d146644ea6dd661e"
},
- {
- "name": "writing_hand_tone4",
- "unicode": "270D-1F3FE",
+ "writing_hand_tone4": {
+ "category": "people",
+ "moji": "✍🏾",
+ "unicodeVersion": "8.0",
"digest": "e5254564a1f91e42ee59f359d8cd26f52abdc04dca8f3b37cb2f140cb7f71390"
},
- {
- "name": "writing_hand_tone5",
- "unicode": "270D-1F3FF",
+ "writing_hand_tone5": {
+ "category": "people",
+ "moji": "✍🏿",
+ "unicodeVersion": "8.0",
"digest": "61299bf86d83d323ca3e6052c535ae66c6f7b3d9866a37db0464223b8bc28523"
},
- {
- "name": "x",
- "unicode": "274C",
+ "x": {
+ "category": "symbols",
+ "moji": "❌",
+ "unicodeVersion": "6.0",
"digest": "3e5a7918e31ddefdf1ce73972365e2f0bfd2917d6a450c1a278c108349c9425d"
},
- {
- "name": "yellow_heart",
- "unicode": "1F49B",
+ "yellow_heart": {
+ "category": "symbols",
+ "moji": "💛",
+ "unicodeVersion": "6.0",
"digest": "a1098f2f04c29754cc9974324508386787d4d803b57cf691d42de414cb2679d6"
},
- {
- "name": "yen",
- "unicode": "1F4B4",
+ "yen": {
+ "category": "objects",
+ "moji": "💴",
+ "unicodeVersion": "6.0",
"digest": "944daaeb3f6369c807c0e63b106cee1360040f7800a70c0d942a992f25a55da7"
},
- {
- "name": "yin_yang",
- "unicode": "262F",
+ "yin_yang": {
+ "category": "symbols",
+ "moji": "☯",
+ "unicodeVersion": "1.1",
"digest": "5ee8d13dacf41306a09237bfcff6abeef110331b40eb7d6e80600628c1327545"
},
- {
- "name": "yum",
- "unicode": "1F60B",
+ "yum": {
+ "category": "people",
+ "moji": "😋",
+ "unicodeVersion": "6.0",
"digest": "31a89088c21bd7a74a3a26d731a907d1bc49436300a9f9c55248703cf7ef44c7"
},
- {
- "name": "zap",
- "unicode": "26A1",
+ "zap": {
+ "category": "nature",
+ "moji": "⚡",
+ "unicodeVersion": "4.0",
"digest": "9f8144ae6f866129aea41bbf694b0c858ef9352a139969e57cd8db73385f52c3"
},
- {
- "name": "zero",
- "unicode": "0030-20E3",
+ "zero": {
+ "category": "symbols",
+ "moji": "0️⃣",
+ "unicodeVersion": "3.0",
"digest": "1b27b5c904defadbdd28ace67a6be5c277ff043297db7cd9f672bbf84e37fa1a"
},
- {
- "name": "zipper_mouth",
- "unicode": "1F910",
- "digest": "81bee5aa1202dfd5a4c7badb71ec0e44b8f75c2cbef94e6fd35c593d8770ae43"
- },
- {
- "name": "zipper_mouth_face",
- "unicode": "1F910",
+ "zipper_mouth": {
+ "category": "people",
+ "moji": "🤐",
+ "unicodeVersion": "8.0",
"digest": "81bee5aa1202dfd5a4c7badb71ec0e44b8f75c2cbef94e6fd35c593d8770ae43"
},
- {
- "name": "zzz",
- "unicode": "1F4A4",
+ "zzz": {
+ "category": "people",
+ "moji": "💤",
+ "unicodeVersion": "6.0",
"digest": "b3313d0c44a59fa9d4ce9f7eb4d07ff71dfc8bb01798154250f27cdcf3c693b5"
}
-] \ No newline at end of file
+} \ No newline at end of file
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 8dbe8875fe8..1bf20f76ad6 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -5,6 +5,8 @@ module API
version %w(v3 v4), using: :path
version 'v3', using: :path do
+ helpers ::API::V3::Helpers
+
mount ::API::V3::AwardEmoji
mount ::API::V3::Boards
mount ::API::V3::Branches
diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb
index 07a1bcdbe18..f9e0c2c4e16 100644
--- a/lib/api/award_emoji.rb
+++ b/lib/api/award_emoji.rb
@@ -3,12 +3,16 @@ module API
include PaginationParams
before { authenticate! }
- AWARDABLES = %w[issue merge_request snippet].freeze
+ AWARDABLES = [
+ { type: 'issue', find_by: :iid },
+ { type: 'merge_request', find_by: :iid },
+ { type: 'snippet', find_by: :id }
+ ].freeze
resource :projects do
- AWARDABLES.each do |awardable_type|
- awardable_string = awardable_type.pluralize
- awardable_id_string = "#{awardable_type}_id"
+ AWARDABLES.each do |awardable_params|
+ awardable_string = awardable_params[:type].pluralize
+ awardable_id_string = "#{awardable_params[:type]}_#{awardable_params[:find_by]}"
params do
requires :id, type: String, desc: 'The ID of a project'
@@ -104,10 +108,10 @@ module API
note_id = params.delete(:note_id)
awardable.notes.find(note_id)
- elsif params.include?(:issue_id)
- user_project.issues.find(params[:issue_id])
- elsif params.include?(:merge_request_id)
- user_project.merge_requests.find(params[:merge_request_id])
+ elsif params.include?(:issue_iid)
+ user_project.issues.find_by!(iid: params[:issue_iid])
+ elsif params.include?(:merge_request_iid)
+ user_project.merge_requests.find_by!(iid: params[:merge_request_iid])
else
user_project.snippets.find(params[:snippet_id])
end
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index b0aa10f8bf2..42401abfe0f 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -18,22 +18,34 @@ module API
optional :ref_name, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used'
optional :since, type: DateTime, desc: 'Only commits after or on this date will be returned'
optional :until, type: DateTime, desc: 'Only commits before or on this date will be returned'
- optional :page, type: Integer, default: 0, desc: 'The page for pagination'
- optional :per_page, type: Integer, default: 20, desc: 'The number of results per page'
optional :path, type: String, desc: 'The file path'
+ use :pagination
end
get ":id/repository/commits" do
- ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
- offset = params[:page] * params[:per_page]
+ path = params[:path]
+ before = params[:until]
+ after = params[:since]
+ ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
+ offset = (params[:page] - 1) * params[:per_page]
commits = user_project.repository.commits(ref,
- path: params[:path],
+ path: path,
limit: params[:per_page],
offset: offset,
- after: params[:since],
- before: params[:until])
+ before: before,
+ after: after)
+
+ commit_count =
+ if path || before || after
+ user_project.repository.count_commits(ref: ref, path: path, before: before, after: after)
+ else
+ # Cacheable commit count.
+ user_project.repository.commit_count_for_ref(ref)
+ end
+
+ paginated_commits = Kaminari.paginate_array(commits, total_count: commit_count)
- present commits, with: Entities::RepoCommit
+ present paginate(paginated_commits), with: Entities::RepoCommit
end
desc 'Commit multiple file changes as one commit' do
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 965f8fbab8f..0a12ee72d49 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -671,7 +671,7 @@ module API
end
class Environment < EnvironmentBasic
- expose :project, using: Entities::Project
+ expose :project, using: Entities::BasicProjectDetails
end
class Deployment < Grape::Entity
@@ -705,5 +705,99 @@ module API
expose :id, :message, :starts_at, :ends_at, :color, :font
expose :active?, as: :active
end
+
+ class PersonalAccessToken < Grape::Entity
+ expose :id, :name, :revoked, :created_at, :scopes
+ expose :active?, as: :active
+ expose :expires_at do |personal_access_token|
+ personal_access_token.expires_at ? personal_access_token.expires_at.strftime("%Y-%m-%d") : nil
+ end
+ end
+
+ class PersonalAccessTokenWithToken < PersonalAccessToken
+ expose :token
+ end
+
+ class ImpersonationToken < PersonalAccessTokenWithToken
+ expose :impersonation
+ end
+
+ module JobRequest
+ class JobInfo < Grape::Entity
+ expose :name, :stage
+ expose :project_id, :project_name
+ end
+
+ class GitInfo < Grape::Entity
+ expose :repo_url, :ref, :sha, :before_sha
+ expose :ref_type do |model|
+ if model.tag
+ 'tag'
+ else
+ 'branch'
+ end
+ end
+ end
+
+ class RunnerInfo < Grape::Entity
+ expose :timeout
+ end
+
+ class Step < Grape::Entity
+ expose :name, :script, :timeout, :when, :allow_failure
+ end
+
+ class Image < Grape::Entity
+ expose :name
+ end
+
+ class Artifacts < Grape::Entity
+ expose :name, :untracked, :paths, :when, :expire_in
+ end
+
+ class Cache < Grape::Entity
+ expose :key, :untracked, :paths
+ end
+
+ class Credentials < Grape::Entity
+ expose :type, :url, :username, :password
+ end
+
+ class ArtifactFile < Grape::Entity
+ expose :filename, :size
+ end
+
+ class Dependency < Grape::Entity
+ expose :id, :name
+ expose :artifacts_file, using: ArtifactFile, if: ->(job, _) { job.artifacts? }
+ end
+
+ class Response < Grape::Entity
+ expose :id
+ expose :token
+ expose :allow_git_fetch
+
+ expose :job_info, using: JobInfo do |model|
+ model
+ end
+
+ expose :git_info, using: GitInfo do |model|
+ model
+ end
+
+ expose :runner_info, using: RunnerInfo do |model|
+ model
+ end
+
+ expose :variables
+ expose :steps, using: Step
+ expose :image, using: Image
+ expose :services, using: Image
+ expose :artifacts, using: Artifacts
+ expose :cache, using: Cache
+ expose :credentials, using: Credentials
+ expose :depends_on_builds, as: :dependencies, using: Dependency
+ end
+ end
end
end
diff --git a/lib/api/files.rb b/lib/api/files.rb
index 9c4e43d77cc..bb8f5c3076d 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -14,6 +14,19 @@ module API
}
end
+ def assign_file_vars!
+ authorize! :download_code, user_project
+
+ @commit = user_project.commit(params[:ref])
+ not_found!('Commit') unless @commit
+
+ @repo = user_project.repository
+ @blob = @repo.blob_at(@commit.sha, params[:file_path])
+
+ not_found!('File') unless @blob
+ @blob.load_all_data!(@repo)
+ end
+
def commit_response(attrs)
{
file_path: attrs[:file_path],
@@ -22,7 +35,7 @@ module API
end
params :simple_file_params do
- requires :file_path, type: String, desc: 'The path to new file. Ex. lib/class.rb'
+ requires :file_path, type: String, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb'
requires :branch, type: String, desc: 'The name of branch'
requires :commit_message, type: String, desc: 'Commit Message'
optional :author_email, type: String, desc: 'The email of the author'
@@ -40,34 +53,35 @@ module API
requires :id, type: String, desc: 'The project ID'
end
resource :projects do
- desc 'Get a file from repository'
+ desc 'Get raw file contents from the repository'
params do
- requires :file_path, type: String, desc: 'The path to the file. Ex. lib/class.rb'
- requires :ref, type: String, desc: 'The name of branch, tag, or commit'
+ requires :file_path, type: String, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb'
+ requires :ref, type: String, desc: 'The name of branch, tag commit'
end
- get ":id/repository/files" do
- authorize! :download_code, user_project
-
- commit = user_project.commit(params[:ref])
- not_found!('Commit') unless commit
+ get ":id/repository/files/:file_path/raw" do
+ assign_file_vars!
- repo = user_project.repository
- blob = repo.blob_at(commit.sha, params[:file_path])
- not_found!('File') unless blob
+ send_git_blob @repo, @blob
+ end
- blob.load_all_data!(repo)
- status(200)
+ desc 'Get a file from the repository'
+ params do
+ requires :file_path, type: String, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb'
+ requires :ref, type: String, desc: 'The name of branch, tag or commit'
+ end
+ get ":id/repository/files/:file_path", requirements: { file_path: /.+/ } do
+ assign_file_vars!
{
- file_name: blob.name,
- file_path: blob.path,
- size: blob.size,
+ file_name: @blob.name,
+ file_path: @blob.path,
+ size: @blob.size,
encoding: "base64",
- content: Base64.strict_encode64(blob.data),
+ content: Base64.strict_encode64(@blob.data),
ref: params[:ref],
- blob_id: blob.id,
- commit_id: commit.id,
- last_commit_id: repo.last_commit_id_for_path(commit.sha, params[:file_path])
+ blob_id: @blob.id,
+ commit_id: @commit.id,
+ last_commit_id: @repo.last_commit_id_for_path(@commit.sha, params[:file_path])
}
end
@@ -75,7 +89,7 @@ module API
params do
use :extended_file_params
end
- post ":id/repository/files" do
+ post ":id/repository/files/:file_path", requirements: { file_path: /.+/ } do
authorize! :push_code, user_project
file_params = declared_params(include_missing: false)
@@ -93,7 +107,7 @@ module API
params do
use :extended_file_params
end
- put ":id/repository/files" do
+ put ":id/repository/files/:file_path", requirements: { file_path: /.+/ } do
authorize! :push_code, user_project
file_params = declared_params(include_missing: false)
@@ -112,7 +126,7 @@ module API
params do
use :simple_file_params
end
- delete ":id/repository/files" do
+ delete ":id/repository/files/:file_path", requirements: { file_path: /.+/ } do
authorize! :push_code, user_project
file_params = declared_params(include_missing: false)
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index f325f0a3050..a9b364da9e1 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -82,16 +82,16 @@ module API
label || not_found!('Label')
end
- def find_project_issue(id)
- IssuesFinder.new(current_user, project_id: user_project.id).find(id)
+ def find_project_issue(iid)
+ IssuesFinder.new(current_user, project_id: user_project.id).find_by!(iid: iid)
end
- def find_project_merge_request(id)
- MergeRequestsFinder.new(current_user, project_id: user_project.id).find(id)
+ def find_project_merge_request(iid)
+ MergeRequestsFinder.new(current_user, project_id: user_project.id).find_by!(iid: iid)
end
- def find_merge_request_with_access(id, access_level = :read_merge_request)
- merge_request = user_project.merge_requests.find(id)
+ def find_merge_request_with_access(iid, access_level = :read_merge_request)
+ merge_request = user_project.merge_requests.find_by!(iid: iid)
authorize! access_level, merge_request
merge_request
end
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index 080a6274957..2135a787b11 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -9,11 +9,11 @@ module API
# In addition, they may have a '.git' extension and multiple namespaces
#
# Transform all these cases to 'namespace/project'
- def clean_project_path(project_path, storage_paths = Repository.storages.values)
+ def clean_project_path(project_path, storages = Gitlab.config.repositories.storages.values)
project_path = project_path.sub(/\.git\z/, '')
- storage_paths.each do |storage_path|
- storage_path = File.expand_path(storage_path)
+ storages.each do |storage|
+ storage_path = File.expand_path(storage['path'])
if project_path.start_with?(storage_path)
project_path = project_path.sub(storage_path, '')
diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb
index 119ca81b883..ec2bcaed929 100644
--- a/lib/api/helpers/runner.rb
+++ b/lib/api/helpers/runner.rb
@@ -1,6 +1,10 @@
module API
module Helpers
module Runner
+ JOB_TOKEN_HEADER = 'HTTP_JOB_TOKEN'.freeze
+ JOB_TOKEN_PARAM = :token
+ UPDATE_RUNNER_EVERY = 10 * 60
+
def runner_registration_token_valid?
ActiveSupport::SecurityUtils.variable_size_secure_compare(params[:token],
current_application_settings.runners_registration_token)
@@ -18,6 +22,56 @@ module API
def current_runner
@runner ||= ::Ci::Runner.find_by_token(params[:token].to_s)
end
+
+ def update_runner_info
+ return unless update_runner?
+
+ current_runner.contacted_at = Time.now
+ current_runner.assign_attributes(get_runner_version_from_params)
+ current_runner.save if current_runner.changed?
+ end
+
+ def update_runner?
+ # Use a random threshold to prevent beating DB updates.
+ # It generates a distribution between [40m, 80m].
+ #
+ contacted_at_max_age = UPDATE_RUNNER_EVERY + Random.rand(UPDATE_RUNNER_EVERY)
+
+ current_runner.contacted_at.nil? ||
+ (Time.now - current_runner.contacted_at) >= contacted_at_max_age
+ end
+
+ def job_not_found!
+ if headers['User-Agent'].to_s =~ /gitlab(-ci-multi)?-runner \d+\.\d+\.\d+(~beta\.\d+\.g[0-9a-f]+)? /
+ no_content!
+ else
+ not_found!
+ end
+ end
+
+ def validate_job!(job)
+ not_found! unless job
+
+ yield if block_given?
+
+ forbidden!('Project has been deleted!') unless job.project
+ forbidden!('Job has been erased!') if job.erased?
+ end
+
+ def authenticate_job!(job)
+ validate_job!(job) do
+ forbidden! unless job_token_valid?(job)
+ end
+ end
+
+ def job_token_valid?(job)
+ token = (params[JOB_TOKEN_PARAM] || env[JOB_TOKEN_HEADER]).to_s
+ token && job.valid_token?(token)
+ end
+
+ def max_artifacts_size
+ current_application_settings.max_artifacts_size.megabytes.to_i
+ end
end
end
end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index bda74069ad5..4a9f2b26fb2 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -102,10 +102,10 @@ module API
success Entities::Issue
end
params do
- requires :issue_id, type: Integer, desc: 'The ID of a project issue'
+ requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
end
- get ":id/issues/:issue_id" do
- issue = find_project_issue(params[:issue_id])
+ get ":id/issues/:issue_iid" do
+ issue = find_project_issue(params[:issue_iid])
present issue, with: Entities::Issue, current_user: current_user, project: user_project
end
@@ -152,7 +152,7 @@ module API
success Entities::Issue
end
params do
- requires :issue_id, type: Integer, desc: 'The ID of a project issue'
+ requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
optional :title, type: String, desc: 'The title of an issue'
optional :updated_at, type: DateTime,
desc: 'Date time when the issue was updated. Available only for admins and project owners.'
@@ -161,8 +161,8 @@ module API
at_least_one_of :title, :description, :assignee_id, :milestone_id,
:labels, :created_at, :due_date, :confidential, :state_event
end
- put ':id/issues/:issue_id' do
- issue = user_project.issues.find(params.delete(:issue_id))
+ put ':id/issues/:issue_iid' do
+ issue = user_project.issues.find_by!(iid: params.delete(:issue_iid))
authorize! :update_issue, issue
# Setting created_at time only allowed for admins and project owners
@@ -189,11 +189,11 @@ module API
success Entities::Issue
end
params do
- requires :issue_id, type: Integer, desc: 'The ID of a project issue'
+ requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
requires :to_project_id, type: Integer, desc: 'The ID of the new project'
end
- post ':id/issues/:issue_id/move' do
- issue = user_project.issues.find_by(id: params[:issue_id])
+ post ':id/issues/:issue_iid/move' do
+ issue = user_project.issues.find_by(iid: params[:issue_iid])
not_found!('Issue') unless issue
new_project = Project.find_by(id: params[:to_project_id])
@@ -209,10 +209,10 @@ module API
desc 'Delete a project issue'
params do
- requires :issue_id, type: Integer, desc: 'The ID of a project issue'
+ requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
end
- delete ":id/issues/:issue_id" do
- issue = user_project.issues.find_by(id: params[:issue_id])
+ delete ":id/issues/:issue_iid" do
+ issue = user_project.issues.find_by(iid: params[:issue_iid])
not_found!('Issue') unless issue
authorize!(:destroy_issue, issue)
diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb
index 33c05e8aa63..44118522abe 100644
--- a/lib/api/jobs.rb
+++ b/lib/api/jobs.rb
@@ -18,6 +18,8 @@ module API
[scope]
when Hashie::Mash
scope.values
+ when Hashie::Array
+ scope
else
['unknown']
end
@@ -36,8 +38,23 @@ module API
builds = user_project.builds.order('id DESC')
builds = filter_builds(builds, params[:scope])
- present paginate(builds), with: Entities::Job,
- user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ present paginate(builds), with: Entities::Job
+ end
+
+ desc 'Get pipeline jobs' do
+ success Entities::Job
+ end
+ params do
+ requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
+ use :optional_scope
+ use :pagination
+ end
+ get ':id/pipelines/:pipeline_id/jobs' do
+ pipeline = user_project.pipelines.find(params[:pipeline_id])
+ builds = pipeline.builds
+ builds = filter_builds(builds, params[:scope])
+
+ present paginate(builds), with: Entities::Job
end
desc 'Get a specific job of a project' do
@@ -51,8 +68,7 @@ module API
build = get_build!(params[:job_id])
- present build, with: Entities::Job,
- user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ present build, with: Entities::Job
end
desc 'Download the artifacts file from a job' do
@@ -119,8 +135,7 @@ module API
build.cancel
- present build, with: Entities::Job,
- user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ present build, with: Entities::Job
end
desc 'Retry a specific build of a project' do
@@ -137,8 +152,7 @@ module API
build = Ci::Build.retry(build, current_user)
- present build, with: Entities::Job,
- user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ present build, with: Entities::Job
end
desc 'Erase job (remove artifacts and the trace)' do
@@ -154,8 +168,7 @@ module API
return forbidden!('Job is not erasable!') unless build.erasable?
build.erase(erased_by: current_user)
- present build, with: Entities::Job,
- user_can_download_artifacts: can?(current_user, :download_build_artifacts, user_project)
+ present build, with: Entities::Job
end
desc 'Keep the artifacts to prevent them from being deleted' do
@@ -173,8 +186,7 @@ module API
build.keep_artifacts!
status 200
- present build, with: Entities::Job,
- user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ present build, with: Entities::Job
end
desc 'Trigger a manual job' do
@@ -194,8 +206,7 @@ module API
build.play(current_user)
status 200
- present build, with: Entities::Job,
- user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ present build, with: Entities::Job
end
end
diff --git a/lib/api/merge_request_diffs.rb b/lib/api/merge_request_diffs.rb
index 4901a7cfea6..a59e39cca26 100644
--- a/lib/api/merge_request_diffs.rb
+++ b/lib/api/merge_request_diffs.rb
@@ -13,11 +13,11 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
- requires :merge_request_id, type: Integer, desc: 'The ID of a merge request'
+ requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request'
use :pagination
end
- get ":id/merge_requests/:merge_request_id/versions" do
- merge_request = find_merge_request_with_access(params[:merge_request_id])
+ get ":id/merge_requests/:merge_request_iid/versions" do
+ merge_request = find_merge_request_with_access(params[:merge_request_iid])
present paginate(merge_request.merge_request_diffs), with: Entities::MergeRequestDiff
end
@@ -29,12 +29,12 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
- requires :merge_request_id, type: Integer, desc: 'The ID of a merge request'
+ requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request'
requires :version_id, type: Integer, desc: 'The ID of a merge request diff version'
end
- get ":id/merge_requests/:merge_request_id/versions/:version_id" do
- merge_request = find_merge_request_with_access(params[:merge_request_id])
+ get ":id/merge_requests/:merge_request_iid/versions/:version_id" do
+ merge_request = find_merge_request_with_access(params[:merge_request_iid])
present merge_request.merge_request_diffs.find(params[:version_id]), with: Entities::MergeRequestDiffFull
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 6fc33a7a54a..7a03955a045 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -101,23 +101,23 @@ module API
desc 'Delete a merge request'
params do
- requires :merge_request_id, type: Integer, desc: 'The ID of a merge request'
+ requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request'
end
- delete ":id/merge_requests/:merge_request_id" do
- merge_request = find_project_merge_request(params[:merge_request_id])
+ delete ":id/merge_requests/:merge_request_iid" do
+ merge_request = find_project_merge_request(params[:merge_request_iid])
authorize!(:destroy_merge_request, merge_request)
merge_request.destroy
end
params do
- requires :merge_request_id, type: Integer, desc: 'The ID of a merge request'
+ requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request'
end
desc 'Get a single merge request' do
success Entities::MergeRequest
end
- get ':id/merge_requests/:merge_request_id' do
- merge_request = find_merge_request_with_access(params[:merge_request_id])
+ get ':id/merge_requests/:merge_request_iid' do
+ merge_request = find_merge_request_with_access(params[:merge_request_iid])
present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project
end
@@ -125,8 +125,8 @@ module API
desc 'Get the commits of a merge request' do
success Entities::RepoCommit
end
- get ':id/merge_requests/:merge_request_id/commits' do
- merge_request = find_merge_request_with_access(params[:merge_request_id])
+ get ':id/merge_requests/:merge_request_iid/commits' do
+ merge_request = find_merge_request_with_access(params[:merge_request_iid])
commits = ::Kaminari.paginate_array(merge_request.commits)
present paginate(commits), with: Entities::RepoCommit
@@ -135,8 +135,8 @@ module API
desc 'Show the merge request changes' do
success Entities::MergeRequestChanges
end
- get ':id/merge_requests/:merge_request_id/changes' do
- merge_request = find_merge_request_with_access(params[:merge_request_id])
+ get ':id/merge_requests/:merge_request_iid/changes' do
+ merge_request = find_merge_request_with_access(params[:merge_request_iid])
present merge_request, with: Entities::MergeRequestChanges, current_user: current_user
end
@@ -154,8 +154,8 @@ module API
:milestone_id, :labels, :state_event,
:remove_source_branch
end
- put ':id/merge_requests/:merge_request_id' do
- merge_request = find_merge_request_with_access(params.delete(:merge_request_id), :update_merge_request)
+ put ':id/merge_requests/:merge_request_iid' do
+ merge_request = find_merge_request_with_access(params.delete(:merge_request_iid), :update_merge_request)
mr_params = declared_params(include_missing: false)
mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) if mr_params[:remove_source_branch].present?
@@ -180,8 +180,8 @@ module API
desc: 'When true, this merge request will be merged when the pipeline succeeds'
optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch'
end
- put ':id/merge_requests/:merge_request_id/merge' do
- merge_request = find_project_merge_request(params[:merge_request_id])
+ put ':id/merge_requests/:merge_request_iid/merge' do
+ merge_request = find_project_merge_request(params[:merge_request_iid])
# Merge request can not be merged
# because user dont have permissions to push into target branch
@@ -216,8 +216,8 @@ module API
desc 'Cancel merge if "Merge When Pipeline Succeeds" is enabled' do
success Entities::MergeRequest
end
- post ':id/merge_requests/:merge_request_id/cancel_merge_when_pipeline_succeeds' do
- merge_request = find_project_merge_request(params[:merge_request_id])
+ post ':id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_succeeds' do
+ merge_request = find_project_merge_request(params[:merge_request_iid])
unauthorized! unless merge_request.can_cancel_merge_when_pipeline_succeeds?(current_user)
@@ -232,8 +232,8 @@ module API
params do
use :pagination
end
- get ':id/merge_requests/:merge_request_id/comments' do
- merge_request = find_merge_request_with_access(params[:merge_request_id])
+ get ':id/merge_requests/:merge_request_iid/comments' do
+ merge_request = find_merge_request_with_access(params[:merge_request_iid])
present paginate(merge_request.notes.fresh), with: Entities::MRNote
end
@@ -243,8 +243,8 @@ module API
params do
requires :note, type: String, desc: 'The text of the comment'
end
- post ':id/merge_requests/:merge_request_id/comments' do
- merge_request = find_merge_request_with_access(params[:merge_request_id], :create_note)
+ post ':id/merge_requests/:merge_request_iid/comments' do
+ merge_request = find_merge_request_with_access(params[:merge_request_iid], :create_note)
opts = {
note: params[:note],
@@ -267,8 +267,8 @@ module API
params do
use :pagination
end
- get ':id/merge_requests/:merge_request_id/closes_issues' do
- merge_request = find_merge_request_with_access(params[:merge_request_id])
+ get ':id/merge_requests/:merge_request_iid/closes_issues' do
+ merge_request = find_merge_request_with_access(params[:merge_request_iid])
issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user))
present paginate(issues), with: issue_entity(user_project), current_user: current_user
end
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 36166780149..531ef5a63ea 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -17,19 +17,34 @@ module API
end
not_found!
end
+
+ def assign_blob_vars!
+ authorize! :download_code, user_project
+
+ @repo = user_project.repository
+
+ begin
+ @blob = Gitlab::Git::Blob.raw(@repo, params[:sha])
+ @blob.load_all_data!(@repo)
+ rescue
+ not_found! 'Blob'
+ end
+
+ not_found! 'Blob' unless @blob
+ end
end
desc 'Get a project repository tree' do
success Entities::RepoTreeObject
end
params do
- optional :ref_name, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used'
+ optional :ref, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used'
optional :path, type: String, desc: 'The path of the tree'
optional :recursive, type: Boolean, default: false, desc: 'Used to get a recursive tree'
use :pagination
end
get ':id/repository/tree' do
- ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
+ ref = params[:ref] || user_project.try(:default_branch) || 'master'
path = params[:path] || nil
commit = user_project.commit(ref)
@@ -40,39 +55,29 @@ module API
present paginate(entries), with: Entities::RepoTreeObject
end
- desc 'Get a raw file contents'
+ desc 'Get raw blob contents from the repository'
params do
requires :sha, type: String, desc: 'The commit, branch name, or tag name'
- requires :filepath, type: String, desc: 'The path to the file to display'
end
- get [":id/repository/blobs/:sha", ":id/repository/commits/:sha/blob"] do
- repo = user_project.repository
-
- commit = repo.commit(params[:sha])
- not_found! "Commit" unless commit
+ get ':id/repository/blobs/:sha/raw' do
+ assign_blob_vars!
- blob = Gitlab::Git::Blob.find(repo, commit.id, params[:filepath])
- not_found! "File" unless blob
-
- send_git_blob repo, blob
+ send_git_blob @repo, @blob
end
- desc 'Get a raw blob contents by blob sha'
+ desc 'Get a blob from the repository'
params do
requires :sha, type: String, desc: 'The commit, branch name, or tag name'
end
- get ':id/repository/raw_blobs/:sha' do
- repo = user_project.repository
-
- begin
- blob = Gitlab::Git::Blob.raw(repo, params[:sha])
- rescue
- not_found! 'Blob'
- end
-
- not_found! 'Blob' unless blob
+ get ':id/repository/blobs/:sha' do
+ assign_blob_vars!
- send_git_blob repo, blob
+ {
+ size: @blob.size,
+ encoding: "base64",
+ content: Base64.strict_encode64(@blob.data),
+ sha: @blob.id
+ }
end
desc 'Get an archive of the repository'
diff --git a/lib/api/runner.rb b/lib/api/runner.rb
index 47858f1866b..c700d2ef4a1 100644
--- a/lib/api/runner.rb
+++ b/lib/api/runner.rb
@@ -48,5 +48,203 @@ module API
Ci::Runner.find_by_token(params[:token]).destroy
end
end
+
+ resource :jobs do
+ desc 'Request a job' do
+ success Entities::JobRequest::Response
+ end
+ params do
+ requires :token, type: String, desc: %q(Runner's authentication token)
+ optional :last_update, type: String, desc: %q(Runner's queue last_update token)
+ optional :info, type: Hash, desc: %q(Runner's metadata)
+ end
+ post '/request' do
+ authenticate_runner!
+ not_found! unless current_runner.active?
+ update_runner_info
+
+ if current_runner.is_runner_queue_value_latest?(params[:last_update])
+ header 'X-GitLab-Last-Update', params[:last_update]
+ Gitlab::Metrics.add_event(:build_not_found_cached)
+ return job_not_found!
+ end
+
+ new_update = current_runner.ensure_runner_queue_value
+ result = ::Ci::RegisterJobService.new(current_runner).execute
+
+ if result.valid?
+ if result.build
+ Gitlab::Metrics.add_event(:build_found,
+ project: result.build.project.path_with_namespace)
+ present result.build, with: Entities::JobRequest::Response
+ else
+ Gitlab::Metrics.add_event(:build_not_found)
+ header 'X-GitLab-Last-Update', new_update
+ job_not_found!
+ end
+ else
+ # We received build that is invalid due to concurrency conflict
+ Gitlab::Metrics.add_event(:build_invalid)
+ conflict!
+ end
+ end
+
+ desc 'Updates a job' do
+ http_codes [[200, 'Job was updated'], [403, 'Forbidden']]
+ end
+ params do
+ requires :token, type: String, desc: %q(Runners's authentication token)
+ requires :id, type: Integer, desc: %q(Job's ID)
+ optional :trace, type: String, desc: %q(Job's full trace)
+ optional :state, type: String, desc: %q(Job's status: success, failed)
+ end
+ put '/:id' do
+ job = Ci::Build.find_by_id(params[:id])
+ authenticate_job!(job)
+
+ job.update_attributes(trace: params[:trace]) if params[:trace]
+
+ Gitlab::Metrics.add_event(:update_build,
+ project: job.project.path_with_namespace)
+
+ case params[:state].to_s
+ when 'success'
+ job.success
+ when 'failed'
+ job.drop
+ end
+ end
+
+ desc 'Appends a patch to the job trace' do
+ http_codes [[202, 'Trace was patched'],
+ [400, 'Missing Content-Range header'],
+ [403, 'Forbidden'],
+ [416, 'Range not satisfiable']]
+ end
+ params do
+ requires :id, type: Integer, desc: %q(Job's ID)
+ optional :token, type: String, desc: %q(Job's authentication token)
+ end
+ patch '/:id/trace' do
+ job = Ci::Build.find_by_id(params[:id])
+ authenticate_job!(job)
+
+ error!('400 Missing header Content-Range', 400) unless request.headers.has_key?('Content-Range')
+ content_range = request.headers['Content-Range']
+ content_range = content_range.split('-')
+
+ current_length = job.trace_length
+ unless current_length == content_range[0].to_i
+ return error!('416 Range Not Satisfiable', 416, { 'Range' => "0-#{current_length}" })
+ end
+
+ job.append_trace(request.body.read, content_range[0].to_i)
+
+ status 202
+ header 'Job-Status', job.status
+ header 'Range', "0-#{job.trace_length}"
+ end
+
+ desc 'Authorize artifacts uploading for job' do
+ http_codes [[200, 'Upload allowed'],
+ [403, 'Forbidden'],
+ [405, 'Artifacts support not enabled'],
+ [413, 'File too large']]
+ end
+ params do
+ requires :id, type: Integer, desc: %q(Job's ID)
+ optional :token, type: String, desc: %q(Job's authentication token)
+ optional :filesize, type: Integer, desc: %q(Artifacts filesize)
+ end
+ post '/:id/artifacts/authorize' do
+ not_allowed! unless Gitlab.config.artifacts.enabled
+ require_gitlab_workhorse!
+ Gitlab::Workhorse.verify_api_request!(headers)
+
+ job = Ci::Build.find_by_id(params[:id])
+ authenticate_job!(job)
+ forbidden!('Job is not running') unless job.running?
+
+ if params[:filesize]
+ file_size = params[:filesize].to_i
+ file_to_large! unless file_size < max_artifacts_size
+ end
+
+ status 200
+ content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
+ Gitlab::Workhorse.artifact_upload_ok
+ end
+
+ desc 'Upload artifacts for job' do
+ success Entities::JobRequest::Response
+ http_codes [[201, 'Artifact uploaded'],
+ [400, 'Bad request'],
+ [403, 'Forbidden'],
+ [405, 'Artifacts support not enabled'],
+ [413, 'File too large']]
+ end
+ params do
+ requires :id, type: Integer, desc: %q(Job's ID)
+ optional :token, type: String, desc: %q(Job's authentication token)
+ optional :expire_in, type: String, desc: %q(Specify when artifacts should expire)
+ optional :file, type: File, desc: %q(Artifact's file)
+ optional 'file.path', type: String, desc: %q(path to locally stored body (generated by Workhorse))
+ optional 'file.name', type: String, desc: %q(real filename as send in Content-Disposition (generated by Workhorse))
+ optional 'file.type', type: String, desc: %q(real content type as send in Content-Type (generated by Workhorse))
+ optional 'metadata.path', type: String, desc: %q(path to locally stored body (generated by Workhorse))
+ optional 'metadata.name', type: String, desc: %q(filename (generated by Workhorse))
+ end
+ post '/:id/artifacts' do
+ not_allowed! unless Gitlab.config.artifacts.enabled
+ require_gitlab_workhorse!
+
+ job = Ci::Build.find_by_id(params[:id])
+ authenticate_job!(job)
+ forbidden!('Job is not running!') unless job.running?
+
+ artifacts_upload_path = ArtifactUploader.artifacts_upload_path
+ artifacts = uploaded_file(:file, artifacts_upload_path)
+ metadata = uploaded_file(:metadata, artifacts_upload_path)
+
+ bad_request!('Missing artifacts file!') unless artifacts
+ file_to_large! unless artifacts.size < max_artifacts_size
+
+ job.artifacts_file = artifacts
+ job.artifacts_metadata = metadata
+ job.artifacts_expire_in = params['expire_in'] ||
+ Gitlab::CurrentSettings.current_application_settings.default_artifacts_expire_in
+
+ if job.save
+ present job, with: Entities::JobRequest::Response
+ else
+ render_validation_error!(job)
+ end
+ end
+
+ desc 'Download the artifacts file for job' do
+ http_codes [[200, 'Upload allowed'],
+ [403, 'Forbidden'],
+ [404, 'Artifact not found']]
+ end
+ params do
+ requires :id, type: Integer, desc: %q(Job's ID)
+ optional :token, type: String, desc: %q(Job's authentication token)
+ end
+ get '/:id/artifacts' do
+ job = Ci::Build.find_by_id(params[:id])
+ authenticate_job!(job)
+
+ artifacts_file = job.artifacts_file
+ unless artifacts_file.file_storage?
+ return redirect_to job.artifacts_file.url
+ end
+
+ unless artifacts_file.exists?
+ not_found!
+ end
+
+ present_file!(artifacts_file.path, artifacts_file.filename)
+ end
+ end
end
end
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 1cf29d9a1a3..5aa2f5eba7b 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -422,6 +422,14 @@ module API
desc: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches.'
}
],
+ 'prometheus' => [
+ {
+ required: true,
+ name: :api_url,
+ type: String,
+ desc: 'Prometheus API Base URL, like http://prometheus.example.com/'
+ }
+ ],
'pushover' => [
{
required: true,
@@ -558,6 +566,7 @@ module API
SlackSlashCommandsService,
PipelinesEmailService,
PivotaltrackerService,
+ PrometheusService,
PushoverService,
RedmineService,
SlackService,
diff --git a/lib/api/time_tracking_endpoints.rb b/lib/api/time_tracking_endpoints.rb
index 85b5f7d98b8..05b4b490e27 100644
--- a/lib/api/time_tracking_endpoints.rb
+++ b/lib/api/time_tracking_endpoints.rb
@@ -5,11 +5,11 @@ module API
included do
helpers do
def issuable_name
- declared_params.has_key?(:issue_id) ? 'issue' : 'merge_request'
+ declared_params.has_key?(:issue_iid) ? 'issue' : 'merge_request'
end
def issuable_key
- "#{issuable_name}_id".to_sym
+ "#{issuable_name}_iid".to_sym
end
def update_issuable_key
@@ -50,7 +50,7 @@ module API
issuable_name = name.end_with?('Issues') ? 'issue' : 'merge_request'
issuable_collection_name = issuable_name.pluralize
- issuable_key = "#{issuable_name}_id".to_sym
+ issuable_key = "#{issuable_name}_iid".to_sym
desc "Set a time estimate for a project #{issuable_name}"
params do
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index e59030428da..d9b8837a5bb 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -5,8 +5,8 @@ module API
before { authenticate! }
ISSUABLE_TYPES = {
- 'merge_requests' => ->(id) { find_merge_request_with_access(id) },
- 'issues' => ->(id) { find_project_issue(id) }
+ 'merge_requests' => ->(iid) { find_merge_request_with_access(iid) },
+ 'issues' => ->(iid) { find_project_issue(iid) }
}.freeze
params do
@@ -14,13 +14,13 @@ module API
end
resource :projects do
ISSUABLE_TYPES.each do |type, finder|
- type_id_str = "#{type.singularize}_id".to_sym
+ type_id_str = "#{type.singularize}_iid".to_sym
desc 'Create a todo on an issuable' do
success Entities::Todo
end
params do
- requires type_id_str, type: Integer, desc: 'The ID of an issuable'
+ requires type_id_str, type: Integer, desc: 'The IID of an issuable'
end
post ":id/#{type}/:#{type_id_str}/todo" do
issuable = instance_exec(params[type_id_str], &finder)
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 7bb4b76f830..549003f576a 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -9,6 +9,11 @@ module API
resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do
helpers do
+ def find_user(params)
+ id = params[:user_id] || params[:id]
+ User.find_by(id: id) || not_found!('User')
+ end
+
params :optional_attributes do
optional :skype, type: String, desc: 'The Skype username'
optional :linkedin, type: String, desc: 'The LinkedIn username'
@@ -362,6 +367,76 @@ module API
present paginate(events), with: Entities::Event
end
+
+ params do
+ requires :user_id, type: Integer, desc: 'The ID of the user'
+ end
+ segment ':user_id' do
+ resource :impersonation_tokens do
+ helpers do
+ def finder(options = {})
+ user = find_user(params)
+ PersonalAccessTokensFinder.new({ user: user, impersonation: true }.merge(options))
+ end
+
+ def find_impersonation_token
+ finder.find_by(id: declared_params[:impersonation_token_id]) || not_found!('Impersonation Token')
+ end
+ end
+
+ before { authenticated_as_admin! }
+
+ desc 'Retrieve impersonation tokens. Available only for admins.' do
+ detail 'This feature was introduced in GitLab 9.0'
+ success Entities::ImpersonationToken
+ end
+ params do
+ use :pagination
+ optional :state, type: String, default: 'all', values: %w[all active inactive], desc: 'Filters (all|active|inactive) impersonation_tokens'
+ end
+ get { present paginate(finder(declared_params(include_missing: false)).execute), with: Entities::ImpersonationToken }
+
+ desc 'Create a impersonation token. Available only for admins.' do
+ detail 'This feature was introduced in GitLab 9.0'
+ success Entities::ImpersonationToken
+ end
+ params do
+ requires :name, type: String, desc: 'The name of the impersonation token'
+ optional :expires_at, type: Date, desc: 'The expiration date in the format YEAR-MONTH-DAY of the impersonation token'
+ optional :scopes, type: Array, desc: 'The array of scopes of the impersonation token'
+ end
+ post do
+ impersonation_token = finder.build(declared_params(include_missing: false))
+
+ if impersonation_token.save
+ present impersonation_token, with: Entities::ImpersonationToken
+ else
+ render_validation_error!(impersonation_token)
+ end
+ end
+
+ desc 'Retrieve impersonation token. Available only for admins.' do
+ detail 'This feature was introduced in GitLab 9.0'
+ success Entities::ImpersonationToken
+ end
+ params do
+ requires :impersonation_token_id, type: Integer, desc: 'The ID of the impersonation token'
+ end
+ get ':impersonation_token_id' do
+ present find_impersonation_token, with: Entities::ImpersonationToken
+ end
+
+ desc 'Revoke a impersonation token. Available only for admins.' do
+ detail 'This feature was introduced in GitLab 9.0'
+ end
+ params do
+ requires :impersonation_token_id, type: Integer, desc: 'The ID of the impersonation token'
+ end
+ delete ':impersonation_token_id' do
+ find_impersonation_token.revoke!
+ end
+ end
+ end
end
resource :user do
diff --git a/lib/api/v3/award_emoji.rb b/lib/api/v3/award_emoji.rb
index 1e35283631f..cf9e1551f60 100644
--- a/lib/api/v3/award_emoji.rb
+++ b/lib/api/v3/award_emoji.rb
@@ -16,11 +16,64 @@ module API
requires :"#{awardable_id_string}", type: Integer, desc: "The ID of an Issue, Merge Request or Snippet"
end
- [":id/#{awardable_string}/:#{awardable_id_string}/award_emoji",
- ":id/#{awardable_string}/:#{awardable_id_string}/notes/:note_id/award_emoji"].each do |endpoint|
+ [
+ ":id/#{awardable_string}/:#{awardable_id_string}/award_emoji",
+ ":id/#{awardable_string}/:#{awardable_id_string}/notes/:note_id/award_emoji"
+ ].each do |endpoint|
+
+ desc 'Get a list of project +awardable+ award emoji' do
+ detail 'This feature was introduced in 8.9'
+ success Entities::AwardEmoji
+ end
+ params do
+ use :pagination
+ end
+ get endpoint do
+ if can_read_awardable?
+ awards = awardable.award_emoji
+ present paginate(awards), with: Entities::AwardEmoji
+ else
+ not_found!("Award Emoji")
+ end
+ end
+
+ desc 'Get a specific award emoji' do
+ detail 'This feature was introduced in 8.9'
+ success Entities::AwardEmoji
+ end
+ params do
+ requires :award_id, type: Integer, desc: 'The ID of the award'
+ end
+ get "#{endpoint}/:award_id" do
+ if can_read_awardable?
+ present awardable.award_emoji.find(params[:award_id]), with: Entities::AwardEmoji
+ else
+ not_found!("Award Emoji")
+ end
+ end
+
+ desc 'Award a new Emoji' do
+ detail 'This feature was introduced in 8.9'
+ success Entities::AwardEmoji
+ end
+ params do
+ requires :name, type: String, desc: 'The name of a award_emoji (without colons)'
+ end
+ post endpoint do
+ not_found!('Award Emoji') unless can_read_awardable? && can_award_awardable?
+
+ award = awardable.create_award_emoji(params[:name], current_user)
+
+ if award.persisted?
+ present award, with: Entities::AwardEmoji
+ else
+ not_found!("Award Emoji #{award.errors.messages}")
+ end
+ end
+
desc 'Delete a +awardables+ award emoji' do
detail 'This feature was introduced in 8.9'
- success ::API::Entities::AwardEmoji
+ success Entities::AwardEmoji
end
params do
requires :award_id, type: Integer, desc: 'The ID of an award emoji'
@@ -30,13 +83,22 @@ module API
unauthorized! unless award.user == current_user || current_user.admin?
- present award.destroy, with: ::API::Entities::AwardEmoji
+ award.destroy
+ present award, with: Entities::AwardEmoji
end
end
end
end
helpers do
+ def can_read_awardable?
+ can?(current_user, read_ability(awardable), awardable)
+ end
+
+ def can_award_awardable?
+ awardable.user_can_award?(current_user, params[:name])
+ end
+
def awardable
@awardable ||=
begin
@@ -53,6 +115,15 @@ module API
end
end
end
+
+ def read_ability(awardable)
+ case awardable
+ when Note
+ read_ability(awardable.noteable)
+ else
+ :"read_#{awardable.class.to_s.underscore}"
+ end
+ end
end
end
end
diff --git a/lib/api/v3/builds.rb b/lib/api/v3/builds.rb
index c8feba13527..6f97102c6ef 100644
--- a/lib/api/v3/builds.rb
+++ b/lib/api/v3/builds.rb
@@ -36,8 +36,7 @@ module API
builds = user_project.builds.order('id DESC')
builds = filter_builds(builds, params[:scope])
- present paginate(builds), with: ::API::V3::Entities::Build,
- user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ present paginate(builds), with: ::API::V3::Entities::Build
end
desc 'Get builds for a specific commit of a project' do
@@ -57,8 +56,7 @@ module API
builds = user_project.builds.where(pipeline: pipelines).order('id DESC')
builds = filter_builds(builds, params[:scope])
- present paginate(builds), with: ::API::V3::Entities::Build,
- user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ present paginate(builds), with: ::API::V3::Entities::Build
end
desc 'Get a specific build of a project' do
@@ -72,8 +70,7 @@ module API
build = get_build!(params[:build_id])
- present build, with: ::API::V3::Entities::Build,
- user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ present build, with: ::API::V3::Entities::Build
end
desc 'Download the artifacts file from build' do
@@ -140,8 +137,7 @@ module API
build.cancel
- present build, with: ::API::V3::Entities::Build,
- user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ present build, with: ::API::V3::Entities::Build
end
desc 'Retry a specific build of a project' do
@@ -158,8 +154,7 @@ module API
build = Ci::Build.retry(build, current_user)
- present build, with: ::API::V3::Entities::Build,
- user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ present build, with: ::API::V3::Entities::Build
end
desc 'Erase build (remove artifacts and build trace)' do
@@ -175,8 +170,7 @@ module API
return forbidden!('Build is not erasable!') unless build.erasable?
build.erase(erased_by: current_user)
- present build, with: ::API::V3::Entities::Build,
- user_can_download_artifacts: can?(current_user, :download_build_artifacts, user_project)
+ present build, with: ::API::V3::Entities::Build
end
desc 'Keep the artifacts to prevent them from being deleted' do
@@ -194,8 +188,7 @@ module API
build.keep_artifacts!
status 200
- present build, with: ::API::V3::Entities::Build,
- user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ present build, with: ::API::V3::Entities::Build
end
desc 'Trigger a manual build' do
@@ -215,8 +208,7 @@ module API
build.play(current_user)
status 200
- present build, with: ::API::V3::Entities::Build,
- user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ present build, with: ::API::V3::Entities::Build
end
end
diff --git a/lib/api/v3/helpers.rb b/lib/api/v3/helpers.rb
new file mode 100644
index 00000000000..0f234d4cdad
--- /dev/null
+++ b/lib/api/v3/helpers.rb
@@ -0,0 +1,19 @@
+module API
+ module V3
+ module Helpers
+ def find_project_issue(id)
+ IssuesFinder.new(current_user, project_id: user_project.id).find(id)
+ end
+
+ def find_project_merge_request(id)
+ MergeRequestsFinder.new(current_user, project_id: user_project.id).find(id)
+ end
+
+ def find_merge_request_with_access(id, access_level = :read_merge_request)
+ merge_request = user_project.merge_requests.find(id)
+ authorize! access_level, merge_request
+ merge_request
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/repositories.rb b/lib/api/v3/repositories.rb
index 3549ea225ef..44584e2eb70 100644
--- a/lib/api/v3/repositories.rb
+++ b/lib/api/v3/repositories.rb
@@ -38,6 +38,60 @@ module API
present tree.sorted_entries, with: ::API::Entities::RepoTreeObject
end
+ desc 'Get a raw file contents'
+ params do
+ requires :sha, type: String, desc: 'The commit, branch name, or tag name'
+ requires :filepath, type: String, desc: 'The path to the file to display'
+ end
+ get [":id/repository/blobs/:sha", ":id/repository/commits/:sha/blob"] do
+ repo = user_project.repository
+ commit = repo.commit(params[:sha])
+ not_found! "Commit" unless commit
+ blob = Gitlab::Git::Blob.find(repo, commit.id, params[:filepath])
+ not_found! "File" unless blob
+ send_git_blob repo, blob
+ end
+
+ desc 'Get a raw blob contents by blob sha'
+ params do
+ requires :sha, type: String, desc: 'The commit, branch name, or tag name'
+ end
+ get ':id/repository/raw_blobs/:sha' do
+ repo = user_project.repository
+ begin
+ blob = Gitlab::Git::Blob.raw(repo, params[:sha])
+ rescue
+ not_found! 'Blob'
+ end
+ not_found! 'Blob' unless blob
+ send_git_blob repo, blob
+ end
+
+ desc 'Get an archive of the repository'
+ params do
+ optional :sha, type: String, desc: 'The commit sha of the archive to be downloaded'
+ optional :format, type: String, desc: 'The archive format'
+ end
+ get ':id/repository/archive', requirements: { format: Gitlab::Regex.archive_formats_regex } do
+ begin
+ send_git_archive user_project.repository, ref: params[:sha], format: params[:format]
+ rescue
+ not_found!('File')
+ end
+ end
+
+ desc 'Compare two branches, tags, or commits' do
+ success ::API::Entities::Compare
+ end
+ params do
+ requires :from, type: String, desc: 'The commit, branch name, or tag name to start comparison'
+ requires :to, type: String, desc: 'The commit, branch name, or tag name to stop comparison'
+ end
+ get ':id/repository/compare' do
+ compare = Gitlab::Git::Compare.new(user_project.repository.raw_repository, params[:from], params[:to])
+ present compare, with: ::API::Entities::Compare
+ end
+
desc 'Get repository contributors' do
success ::API::Entities::Contributor
end
diff --git a/lib/api/v3/time_tracking_endpoints.rb b/lib/api/v3/time_tracking_endpoints.rb
new file mode 100644
index 00000000000..81ae4e8137d
--- /dev/null
+++ b/lib/api/v3/time_tracking_endpoints.rb
@@ -0,0 +1,116 @@
+module API
+ module V3
+ module TimeTrackingEndpoints
+ extend ActiveSupport::Concern
+
+ included do
+ helpers do
+ def issuable_name
+ declared_params.has_key?(:issue_id) ? 'issue' : 'merge_request'
+ end
+
+ def issuable_key
+ "#{issuable_name}_id".to_sym
+ end
+
+ def update_issuable_key
+ "update_#{issuable_name}".to_sym
+ end
+
+ def read_issuable_key
+ "read_#{issuable_name}".to_sym
+ end
+
+ def load_issuable
+ @issuable ||= begin
+ case issuable_name
+ when 'issue'
+ find_project_issue(params.delete(issuable_key))
+ when 'merge_request'
+ find_project_merge_request(params.delete(issuable_key))
+ end
+ end
+ end
+
+ def update_issuable(attrs)
+ custom_params = declared_params(include_missing: false)
+ custom_params.merge!(attrs)
+
+ issuable = update_service.new(user_project, current_user, custom_params).execute(load_issuable)
+ if issuable.valid?
+ present issuable, with: ::API::Entities::IssuableTimeStats
+ else
+ render_validation_error!(issuable)
+ end
+ end
+
+ def update_service
+ issuable_name == 'issue' ? ::Issues::UpdateService : ::MergeRequests::UpdateService
+ end
+ end
+
+ issuable_name = name.end_with?('Issues') ? 'issue' : 'merge_request'
+ issuable_collection_name = issuable_name.pluralize
+ issuable_key = "#{issuable_name}_id".to_sym
+
+ desc "Set a time estimate for a project #{issuable_name}"
+ params do
+ requires issuable_key, type: Integer, desc: "The ID of a project #{issuable_name}"
+ requires :duration, type: String, desc: 'The duration to be parsed'
+ end
+ post ":id/#{issuable_collection_name}/:#{issuable_key}/time_estimate" do
+ authorize! update_issuable_key, load_issuable
+
+ status :ok
+ update_issuable(time_estimate: Gitlab::TimeTrackingFormatter.parse(params.delete(:duration)))
+ end
+
+ desc "Reset the time estimate for a project #{issuable_name}"
+ params do
+ requires issuable_key, type: Integer, desc: "The ID of a project #{issuable_name}"
+ end
+ post ":id/#{issuable_collection_name}/:#{issuable_key}/reset_time_estimate" do
+ authorize! update_issuable_key, load_issuable
+
+ status :ok
+ update_issuable(time_estimate: 0)
+ end
+
+ desc "Add spent time for a project #{issuable_name}"
+ params do
+ requires issuable_key, type: Integer, desc: "The ID of a project #{issuable_name}"
+ requires :duration, type: String, desc: 'The duration to be parsed'
+ end
+ post ":id/#{issuable_collection_name}/:#{issuable_key}/add_spent_time" do
+ authorize! update_issuable_key, load_issuable
+
+ update_issuable(spend_time: {
+ duration: Gitlab::TimeTrackingFormatter.parse(params.delete(:duration)),
+ user: current_user
+ })
+ end
+
+ desc "Reset spent time for a project #{issuable_name}"
+ params do
+ requires issuable_key, type: Integer, desc: "The ID of a project #{issuable_name}"
+ end
+ post ":id/#{issuable_collection_name}/:#{issuable_key}/reset_spent_time" do
+ authorize! update_issuable_key, load_issuable
+
+ status :ok
+ update_issuable(spend_time: { duration: :reset, user: current_user })
+ end
+
+ desc "Show time stats for a project #{issuable_name}"
+ params do
+ requires issuable_key, type: Integer, desc: "The ID of a project #{issuable_name}"
+ end
+ get ":id/#{issuable_collection_name}/:#{issuable_key}/time_stats" do
+ authorize! read_issuable_key, load_issuable
+
+ present load_issuable, with: ::API::Entities::IssuableTimeStats
+ end
+ end
+ end
+ end
+end
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index 3c4ba5d50e6..cd745d35e7c 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -68,7 +68,8 @@ module Backup
end
def restore
- Gitlab.config.repositories.storages.each do |name, path|
+ Gitlab.config.repositories.storages.each do |name, repository_storage|
+ path = repository_storage['path']
next unless File.exist?(path)
# Move repos dir to 'repositories.old' dir
@@ -199,7 +200,7 @@ module Backup
private
def repository_storage_paths_args
- Gitlab.config.repositories.storages.values
+ Gitlab.config.repositories.storages.values.map { |rs| rs['path'] }
end
end
end
diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb
index a8c1ca0c60a..d6138816e70 100644
--- a/lib/banzai/filter/emoji_filter.rb
+++ b/lib/banzai/filter/emoji_filter.rb
@@ -17,8 +17,8 @@ module Banzai
next unless content.include?(':') || node.text.match(emoji_unicode_pattern)
- html = emoji_name_image_filter(content)
- html = emoji_unicode_image_filter(html)
+ html = emoji_unicode_element_unicode_filter(content)
+ html = emoji_name_element_unicode_filter(html)
next if html == content
@@ -27,33 +27,30 @@ module Banzai
doc
end
- # Replace :emoji: with corresponding images.
+ # Replace :emoji: with corresponding gl-emoji unicode.
#
# text - String text to replace :emoji: in.
#
- # Returns a String with :emoji: replaced with images.
- def emoji_name_image_filter(text)
+ # Returns a String with :emoji: replaced with gl-emoji unicode.
+ def emoji_name_element_unicode_filter(text)
text.gsub(emoji_pattern) do |match|
name = $1
- emoji_image_tag(name, emoji_url(name))
+ Gitlab::Emoji.gl_emoji_tag(name)
end
end
- # Replace unicode emoji with corresponding images if they exist.
+ # Replace unicode emoji with corresponding gl-emoji unicode.
#
# text - String text to replace unicode emoji in.
#
- # Returns a String with unicode emoji replaced with images.
- def emoji_unicode_image_filter(text)
+ # Returns a String with unicode emoji replaced with gl-emoji unicode.
+ def emoji_unicode_element_unicode_filter(text)
text.gsub(emoji_unicode_pattern) do |moji|
- emoji_image_tag(Gitlab::Emoji.emojis_by_moji[moji]['name'], emoji_unicode_url(moji))
+ emoji_info = Gitlab::Emoji.emojis_by_moji[moji]
+ Gitlab::Emoji.gl_emoji_tag(emoji_info['name'])
end
end
- def emoji_image_tag(emoji_name, emoji_url)
- "<img class='emoji' title=':#{emoji_name}:' alt=':#{emoji_name}:' src='#{emoji_url}' height='20' width='20' align='absmiddle' />"
- end
-
# Build a regexp that matches all valid :emoji: names.
def self.emoji_pattern
@emoji_pattern ||= /:(#{Gitlab::Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/
@@ -66,52 +63,13 @@ module Banzai
private
- def emoji_url(name)
- emoji_path = emoji_filename(name)
-
- if context[:asset_host]
- # Asset host is specified.
- url_to_image(emoji_path)
- elsif context[:asset_root]
- # Gitlab url is specified
- File.join(context[:asset_root], url_to_image(emoji_path))
- else
- # All other cases
- url_to_image(emoji_path)
- end
- end
-
- def emoji_unicode_url(moji)
- emoji_unicode_path = emoji_unicode_filename(moji)
-
- if context[:asset_host]
- url_to_image(emoji_unicode_path)
- elsif context[:asset_root]
- File.join(context[:asset_root], url_to_image(emoji_unicode_path))
- else
- url_to_image(emoji_unicode_path)
- end
- end
-
- def url_to_image(image)
- ActionController::Base.helpers.url_to_image(image)
- end
-
def emoji_pattern
self.class.emoji_pattern
end
- def emoji_filename(name)
- "#{Gitlab::Emoji.emoji_filename(name)}.png"
- end
-
def emoji_unicode_pattern
self.class.emoji_unicode_pattern
end
-
- def emoji_unicode_filename(name)
- "#{Gitlab::Emoji.emoji_unicode_filename(name)}.png"
- end
end
end
end
diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb
index b51e76d93f2..746e76a1b1f 100644
--- a/lib/ci/api/builds.rb
+++ b/lib/ci/api/builds.rb
@@ -24,7 +24,7 @@ module Ci
new_update = current_runner.ensure_runner_queue_value
- result = Ci::RegisterBuildService.new(current_runner).execute
+ result = Ci::RegisterJobService.new(current_runner).execute
if result.valid?
if result.build
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 0a0bd0e781c..eee5601b0ed 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -2,9 +2,17 @@ module Gitlab
module Auth
MissingPersonalTokenError = Class.new(StandardError)
- SCOPES = [:api, :read_user].freeze
+ # Scopes used for GitLab API access
+ API_SCOPES = [:api, :read_user].freeze
+
+ # Scopes used for OpenID Connect
+ OPENID_SCOPES = [:openid].freeze
+
+ # Default scopes for OAuth applications that don't define their own
DEFAULT_SCOPES = [:api].freeze
- OPTIONAL_SCOPES = SCOPES - DEFAULT_SCOPES
+
+ # Other available scopes
+ OPTIONAL_SCOPES = (API_SCOPES + OPENID_SCOPES - DEFAULT_SCOPES).freeze
class << self
def find_for_git_client(login, password, project:, ip:)
@@ -18,8 +26,8 @@ module Gitlab
build_access_token_check(login, password) ||
lfs_token_check(login, password) ||
oauth_access_token_check(login, password) ||
- personal_access_token_check(login, password) ||
user_with_password_for_git(login, password) ||
+ personal_access_token_check(password) ||
Gitlab::Auth::Result.new
rate_limit!(ip, success: result.success?, login: login)
@@ -40,7 +48,7 @@ module Gitlab
Gitlab::LDAP::Authentication.login(login, password)
else
- user if user.valid_password?(password)
+ user if user.active? && user.valid_password?(password)
end
end
end
@@ -105,14 +113,13 @@ module Gitlab
end
end
- def personal_access_token_check(login, password)
- if login && password
- token = PersonalAccessToken.active.find_by_token(password)
- validation = User.by_login(login)
+ def personal_access_token_check(password)
+ return unless password.present?
- if valid_personal_access_token?(token, validation)
- Gitlab::Auth::Result.new(validation, nil, :personal_token, full_authentication_abilities)
- end
+ token = PersonalAccessTokensFinder.new(state: 'active').find_by(token: password)
+
+ if token && valid_api_token?(token)
+ Gitlab::Auth::Result.new(token.user, nil, :personal_token, full_authentication_abilities)
end
end
@@ -120,10 +127,6 @@ module Gitlab
token && token.accessible? && valid_api_token?(token)
end
- def valid_personal_access_token?(token, user)
- token && token.user == user && valid_api_token?(token)
- end
-
def valid_api_token?(token)
AccessTokenValidationService.new(token).include_any_scope?(['api'])
end
diff --git a/lib/gitlab/award_emoji.rb b/lib/gitlab/award_emoji.rb
deleted file mode 100644
index 7555326d384..00000000000
--- a/lib/gitlab/award_emoji.rb
+++ /dev/null
@@ -1,84 +0,0 @@
-module Gitlab
- class AwardEmoji
- CATEGORIES = {
- objects: "Objects",
- travel: "Travel",
- symbols: "Symbols",
- nature: "Nature",
- people: "People",
- activity: "Activity",
- flags: "Flags",
- food: "Food"
- }.with_indifferent_access
-
- def self.normalize_emoji_name(name)
- aliases[name] || name
- end
-
- def self.emoji_by_category
- unless @emoji_by_category
- @emoji_by_category = Hash.new { |h, key| h[key] = [] }
-
- emojis.each do |emoji_name, data|
- data["name"] = emoji_name
-
- # Skip Fitzpatrick(tone) modifiers
- next if data["category"] == "modifier"
-
- category = data["category"]
-
- @emoji_by_category[category] << data
- end
-
- @emoji_by_category = @emoji_by_category.sort.to_h
- end
-
- @emoji_by_category
- end
-
- def self.emojis
- @emojis ||=
- begin
- json_path = File.join(Rails.root, 'fixtures', 'emojis', 'index.json' )
- JSON.parse(File.read(json_path))
- end
- end
-
- def self.aliases
- @aliases ||=
- begin
- json_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json')
- JSON.parse(File.read(json_path))
- end
- end
-
- # Returns an Array of Emoji names and their asset URLs.
- def self.urls
- @urls ||= begin
- path = File.join(Rails.root, 'fixtures', 'emojis', 'digests.json')
- # Construct the full asset path ourselves because
- # ActionView::Helpers::AssetUrlHelper.asset_url is slow for hundreds
- # of entries since it has to do a lot of extra work (e.g. regexps).
- prefix = Gitlab::Application.config.assets.prefix
- digest = Gitlab::Application.config.assets.digest
- base =
- if defined?(Gitlab::Application.config.relative_url_root) && Gitlab::Application.config.relative_url_root
- Gitlab::Application.config.relative_url_root
- else
- ''
- end
-
- JSON.parse(File.read(path)).map do |hash|
- fname =
- if digest
- "#{hash['unicode']}-#{hash['digest']}"
- else
- hash['unicode']
- end
-
- { name: hash['name'], path: File.join(base, prefix, "#{fname}.png") }
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/build/image.rb b/lib/gitlab/ci/build/image.rb
new file mode 100644
index 00000000000..c62aeb60fa9
--- /dev/null
+++ b/lib/gitlab/ci/build/image.rb
@@ -0,0 +1,33 @@
+module Gitlab
+ module Ci
+ module Build
+ class Image
+ attr_reader :name
+
+ class << self
+ def from_image(job)
+ image = Gitlab::Ci::Build::Image.new(job.options[:image])
+ return unless image.valid?
+ image
+ end
+
+ def from_services(job)
+ services = job.options[:services].to_a.map do |service|
+ Gitlab::Ci::Build::Image.new(service)
+ end
+
+ services.select(&:valid?).compact
+ end
+ end
+
+ def initialize(image)
+ @name = image
+ end
+
+ def valid?
+ @name.present?
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/build/step.rb b/lib/gitlab/ci/build/step.rb
new file mode 100644
index 00000000000..1877429ac46
--- /dev/null
+++ b/lib/gitlab/ci/build/step.rb
@@ -0,0 +1,46 @@
+module Gitlab
+ module Ci
+ module Build
+ class Step
+ WHEN_ON_FAILURE = 'on_failure'.freeze
+ WHEN_ON_SUCCESS = 'on_success'.freeze
+ WHEN_ALWAYS = 'always'.freeze
+
+ attr_reader :name
+ attr_writer :script
+ attr_accessor :timeout, :when, :allow_failure
+
+ class << self
+ def from_commands(job)
+ self.new(:script).tap do |step|
+ step.script = job.commands
+ step.timeout = job.timeout
+ step.when = WHEN_ON_SUCCESS
+ end
+ end
+
+ def from_after_script(job)
+ after_script = job.options[:after_script]
+ return unless after_script
+
+ self.new(:after_script).tap do |step|
+ step.script = after_script
+ step.timeout = job.timeout
+ step.when = WHEN_ALWAYS
+ step.allow_failure = true
+ end
+ end
+ end
+
+ def initialize(name)
+ @name = name
+ @allow_failure = false
+ end
+
+ def script
+ @script.split("\n")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/entry/cache.rb b/lib/gitlab/ci/config/entry/cache.rb
index 066643ccfcc..f074df9c7a1 100644
--- a/lib/gitlab/ci/config/entry/cache.rb
+++ b/lib/gitlab/ci/config/entry/cache.rb
@@ -22,6 +22,12 @@ module Gitlab
entry :paths, Entry::Paths,
description: 'Specify which paths should be cached across builds.'
+
+ helpers :key
+
+ def value
+ super.merge(key: key_value)
+ end
end
end
end
diff --git a/lib/gitlab/ci/config/entry/key.rb b/lib/gitlab/ci/config/entry/key.rb
index 0e4c9fe6edc..f27ad0a7759 100644
--- a/lib/gitlab/ci/config/entry/key.rb
+++ b/lib/gitlab/ci/config/entry/key.rb
@@ -11,6 +11,10 @@ module Gitlab
validations do
validates :config, key: true
end
+
+ def self.default
+ 'default'
+ end
end
end
end
diff --git a/lib/gitlab/ci/config/entry/node.rb b/lib/gitlab/ci/config/entry/node.rb
index 55a5447ab51..a6a914d79c1 100644
--- a/lib/gitlab/ci/config/entry/node.rb
+++ b/lib/gitlab/ci/config/entry/node.rb
@@ -70,6 +70,12 @@ module Gitlab
true
end
+ def inspect
+ val = leaf? ? config : descendants
+ unspecified = specified? ? '' : '(unspecified) '
+ "#<#{self.class.name} #{unspecified}{#{key}: #{val.inspect}}>"
+ end
+
def self.default
end
diff --git a/lib/gitlab/ci/config/entry/undefined.rb b/lib/gitlab/ci/config/entry/undefined.rb
index b33b8238230..1171ac10f22 100644
--- a/lib/gitlab/ci/config/entry/undefined.rb
+++ b/lib/gitlab/ci/config/entry/undefined.rb
@@ -29,6 +29,10 @@ module Gitlab
def relevant?
false
end
+
+ def inspect
+ "#<#{self.class.name}>"
+ end
end
end
end
diff --git a/lib/gitlab/emoji.rb b/lib/gitlab/emoji.rb
index bbbca8acc40..42703545c4f 100644
--- a/lib/gitlab/emoji.rb
+++ b/lib/gitlab/emoji.rb
@@ -1,7 +1,7 @@
module Gitlab
module Emoji
extend self
-
+
def emojis
Gemojione.index.instance_variable_get(:@emoji_by_name)
end
@@ -18,6 +18,10 @@ module Gitlab
emojis.keys
end
+ def emojis_aliases
+ @emoji_aliases ||= JSON.parse(File.read(Rails.root.join('fixtures', 'emojis', 'aliases.json')))
+ end
+
def emoji_filename(name)
emojis[name]["unicode"]
end
@@ -25,5 +29,42 @@ module Gitlab
def emoji_unicode_filename(moji)
emojis_by_moji[moji]["unicode"]
end
+
+ def emoji_unicode_version(name)
+ @emoji_unicode_versions_by_name ||= JSON.parse(File.read(Rails.root.join('node_modules', 'emoji-unicode-version', 'emoji-unicode-version-map.json')))
+ @emoji_unicode_versions_by_name[name]
+ end
+
+ def normalize_emoji_name(name)
+ emojis_aliases[name] || name
+ end
+
+ def emoji_image_tag(name, src)
+ "<img class='emoji' title=':#{name}:' alt=':#{name}:' src='#{src}' height='20' width='20' align='absmiddle' />"
+ end
+
+ # CSS sprite fallback takes precedence over image fallback
+ def gl_emoji_tag(name, image: false, sprite: false, force_fallback: false)
+ emoji_name = emojis_aliases[name] || name
+ emoji_info = emojis[emoji_name]
+ emoji_fallback_image_source = ActionController::Base.helpers.url_to_image("emoji/#{emoji_info['name']}.png")
+ emoji_fallback_sprite_class = "emoji-#{emoji_name}"
+
+ data = {
+ name: emoji_name,
+ unicode_version: emoji_unicode_version(emoji_name)
+ }
+ data[:fallback_src] = emoji_fallback_image_source if image
+ data[:fallback_sprite_class] = emoji_fallback_sprite_class if sprite
+ ActionController::Base.helpers.content_tag 'gl-emoji',
+ class: ("emoji-icon #{emoji_fallback_sprite_class}" if force_fallback && sprite),
+ data: data do
+ if force_fallback && !sprite
+ emoji_image_tag(emoji_name, emoji_fallback_image_source)
+ else
+ emoji_info['moji']
+ end
+ end
+ end
end
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 6540730ca7a..228ef7bb7a9 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -354,6 +354,18 @@ module Gitlab
lines.map! { |c| Rugged::Commit.new(rugged, c.strip) }
end
+ def count_commits(options)
+ cmd = %W[#{Gitlab.config.git.bin_path} --git-dir=#{path} rev-list]
+ cmd << "--after=#{options[:after].iso8601}" if options[:after]
+ cmd << "--before=#{options[:before].iso8601}" if options[:before]
+ cmd += %W[--count #{options[:ref]}]
+ cmd += %W[-- #{options[:path]}] if options[:path].present?
+
+ raw_output = IO.popen(cmd) { |io| io.read }
+
+ raw_output.to_i
+ end
+
def sha_from_ref(ref)
rev_parse_target(ref).oid
end
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 9c384069661..6c275a8d5de 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -4,16 +4,17 @@ module Gitlab
gon.api_version = 'v3' # v4 Is not officially released yet, therefore can't be considered as "frozen"
gon.default_avatar_url = URI.join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
gon.max_file_size = current_application_settings.max_attachment_size
+ gon.asset_host = ActionController::Base.asset_host
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
gon.shortcuts_path = help_page_path('shortcuts')
gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class
- gon.award_menu_url = emojis_path
gon.katex_css_url = ActionController::Base.helpers.asset_path('katex.css')
gon.katex_js_url = ActionController::Base.helpers.asset_path('katex.js')
if current_user
gon.current_user_id = current_user.id
gon.current_username = current_user.username
+ gon.current_user_fullname = current_user.name
end
end
end
diff --git a/lib/gitlab/middleware/go.rb b/lib/gitlab/middleware/go.rb
index 5764ab15652..6023fa1820f 100644
--- a/lib/gitlab/middleware/go.rb
+++ b/lib/gitlab/middleware/go.rb
@@ -30,21 +30,69 @@ module Gitlab
end
def go_body(request)
- base_url = Gitlab.config.gitlab.url
- # Go subpackages may be in the form of namespace/project/path1/path2/../pathN
- # We can just ignore the paths and leave the namespace/project
- path_info = request.env["PATH_INFO"]
- path_info.sub!(/^\//, '')
- project_path = path_info.split('/').first(2).join('/')
- request_url = URI.join(base_url, project_path)
- domain_path = strip_url(request_url.to_s)
+ project_url = URI.join(Gitlab.config.gitlab.url, project_path(request))
+ import_prefix = strip_url(project_url.to_s)
- "<!DOCTYPE html><html><head><meta content='#{domain_path} git #{request_url}.git' name='go-import'></head></html>\n"
+ "<!DOCTYPE html><html><head><meta content='#{import_prefix} git #{project_url}.git' name='go-import'></head></html>\n"
end
def strip_url(url)
url.gsub(/\Ahttps?:\/\//, '')
end
+
+ def project_path(request)
+ path_info = request.env["PATH_INFO"]
+ path_info.sub!(/^\//, '')
+
+ # Go subpackages may be in the form of `namespace/project/path1/path2/../pathN`.
+ # In a traditional project with a single namespace, this would denote repo
+ # `namespace/project` with subpath `path1/path2/../pathN`, but with nested
+ # groups, this could also be `namespace/project/path1` with subpath
+ # `path2/../pathN`, for example.
+
+ # We find all potential project paths out of the path segments
+ path_segments = path_info.split('/')
+ simple_project_path = path_segments.first(2).join('/')
+
+ # If the path is at most 2 segments long, it is a simple `namespace/project` path and we're done
+ return simple_project_path if path_segments.length <= 2
+
+ project_paths = []
+ begin
+ project_paths << path_segments.join('/')
+ path_segments.pop
+ end while path_segments.length >= 2
+
+ # We see if a project exists with any of these potential paths
+ project = project_for_paths(project_paths, request)
+
+ if project
+ # If a project is found and the user has access, we return the full project path
+ project.full_path
+ else
+ # If not, we return the first two components as if it were a simple `namespace/project` path,
+ # so that we don't reveal the existence of a nested project the user doesn't have access to.
+ # This means that for an unauthenticated request to `group/subgroup/project/subpackage`
+ # for a private `group/subgroup/project` with subpackage path `subpackage`, GitLab will respond
+ # as if the user is looking for project `group/subgroup`, with subpackage path `project/subpackage`.
+ # Since `go get` doesn't authenticate by default, this means that
+ # `go get gitlab.com/group/subgroup/project/subpackage` will not work for private projects.
+ # `go get gitlab.com/group/subgroup/project.git/subpackage` will work, since Go is smart enough
+ # to figure that out. `import 'gitlab.com/...'` behaves the same as `go get`.
+ simple_project_path
+ end
+ end
+
+ def project_for_paths(paths, request)
+ project = Project.where_full_path_in(paths).first
+ return unless Ability.allowed?(current_user(request), :read_project, project)
+
+ project
+ end
+
+ def current_user(request)
+ request.env['warden']&.authenticate
+ end
end
end
end
diff --git a/lib/gitlab/prometheus.rb b/lib/gitlab/prometheus.rb
new file mode 100644
index 00000000000..62239779454
--- /dev/null
+++ b/lib/gitlab/prometheus.rb
@@ -0,0 +1,70 @@
+module Gitlab
+ PrometheusError = Class.new(StandardError)
+
+ # Helper methods to interact with Prometheus network services & resources
+ class Prometheus
+ attr_reader :api_url
+
+ def initialize(api_url:)
+ @api_url = api_url
+ end
+
+ def ping
+ json_api_get('query', query: '1')
+ end
+
+ def query(query)
+ get_result('vector') do
+ json_api_get('query', query: query)
+ end
+ end
+
+ def query_range(query, start: 8.hours.ago)
+ get_result('matrix') do
+ json_api_get('query_range',
+ query: query,
+ start: start.to_f,
+ end: Time.now.utc.to_f,
+ step: 1.minute.to_i)
+ end
+ end
+
+ private
+
+ def json_api_get(type, args = {})
+ get(join_api_url(type, args))
+ rescue Errno::ECONNREFUSED
+ raise PrometheusError, 'Connection refused'
+ end
+
+ def join_api_url(type, args = {})
+ url = URI.parse(api_url)
+ rescue URI::Error
+ raise PrometheusError, "Invalid API URL: #{api_url}"
+ else
+ url.path = [url.path.sub(%r{/+\z}, ''), 'api', 'v1', type].join('/')
+ url.query = args.to_query
+
+ url.to_s
+ end
+
+ def get(url)
+ handle_response(HTTParty.get(url))
+ end
+
+ def handle_response(response)
+ if response.code == 200 && response['status'] == 'success'
+ response['data'] || {}
+ elsif response.code == 400
+ raise PrometheusError, response['error'] || 'Bad data received'
+ else
+ raise PrometheusError, "#{response.code} - #{response.body}"
+ end
+ end
+
+ def get_result(expected_type)
+ data = yield
+ data['result'] if data['resultType'] == expected_type
+ end
+ end
+end
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 3ff9f9eb5e7..eae1a0abf06 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -8,6 +8,7 @@ module Gitlab
VERSION_FILE = 'GITLAB_WORKHORSE_VERSION'.freeze
INTERNAL_API_CONTENT_TYPE = 'application/vnd.gitlab-workhorse+json'.freeze
INTERNAL_API_REQUEST_HEADER = 'Gitlab-Workhorse-Api-Request'.freeze
+ NOTIFICATION_CHANNEL = 'workhorse:notifications'.freeze
# Supposedly the effective key size for HMAC-SHA256 is 256 bits, i.e. 32
# bytes https://tools.ietf.org/html/rfc4868#section-2.6
@@ -154,6 +155,18 @@ module Gitlab
Rails.root.join('.gitlab_workhorse_secret')
end
+ def set_key_and_notify(key, value, expire: nil, overwrite: true)
+ Gitlab::Redis.with do |redis|
+ result = redis.set(key, value, ex: expire, nx: !overwrite)
+ if result
+ redis.publish(NOTIFICATION_CHANNEL, "#{key}=#{value}")
+ value
+ else
+ redis.get(key)
+ end
+ end
+ end
+
protected
def encode(hash)
diff --git a/lib/tasks/gemojione.rake b/lib/tasks/gemojione.rake
index 993112aee3b..1f93b5a4dd2 100644
--- a/lib/tasks/gemojione.rake
+++ b/lib/tasks/gemojione.rake
@@ -5,29 +5,29 @@ namespace :gemojione do
require 'json'
dir = Gemojione.images_path
- digests = []
- aliases = Hash.new { |hash, key| hash[key] = [] }
- aliases_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json')
-
- JSON.parse(File.read(aliases_path)).each do |alias_name, real_name|
- aliases[real_name] << alias_name
- end
-
- Gitlab::AwardEmoji.emojis.map do |name, emoji_hash|
- fpath = File.join(dir, "#{emoji_hash['unicode']}.png")
- digest = Digest::SHA256.file(fpath).hexdigest
-
- digests << { name: name, unicode: emoji_hash['unicode'], digest: digest }
+ resultant_emoji_map = {}
+
+ Gitlab::Emoji.emojis.each do |name, emoji_hash|
+ # Ignore aliases
+ unless Gitlab::Emoji.emojis_aliases.key?(name)
+ fpath = File.join(dir, "#{emoji_hash['unicode']}.png")
+ hash_digest = Digest::SHA256.file(fpath).hexdigest
+
+ entry = {
+ category: emoji_hash['category'],
+ moji: emoji_hash['moji'],
+ unicodeVersion: Gitlab::Emoji.emoji_unicode_version(name),
+ digest: hash_digest,
+ }
- aliases[name].each do |alias_name|
- digests << { name: alias_name, unicode: emoji_hash['unicode'], digest: digest }
+ resultant_emoji_map[name] = entry
end
end
out = File.join(Rails.root, 'fixtures', 'emojis', 'digests.json')
File.open(out, 'w') do |handle|
- handle.write(JSON.pretty_generate(digests))
+ handle.write(JSON.pretty_generate(resultant_emoji_map))
end
end
@@ -55,21 +55,40 @@ namespace :gemojione do
SPRITESHEET_WIDTH = 860
SPRITESHEET_HEIGHT = 840
+ # Setup a map to rename image files
+ emoji_unicode_string_to_name_map = {}
+ Gitlab::Emoji.emojis.each do |name, emoji_hash|
+ # Ignore aliases
+ unless Gitlab::Emoji.emojis_aliases.key?(name)
+ emoji_unicode_string_to_name_map[emoji_hash['unicode']] = name
+ end
+ end
+
+ # Copy the Gemojione assets to the temporary folder for renaming
+ emoji_dir = "app/assets/images/emoji"
+ FileUtils.rm_rf(emoji_dir)
+ FileUtils.mkdir_p(emoji_dir, mode: 0700)
+ FileUtils.cp_r(File.join(Gemojione.images_path, '.'), emoji_dir)
+ Dir[File.join(emoji_dir, "**/*.png")].each do |png|
+ image_path = png
+ rename_to_named_emoji_image!(emoji_unicode_string_to_name_map, image_path)
+ end
+
Dir.mktmpdir do |tmpdir|
- # Copy the Gemojione assets to the temporary folder for resizing
- FileUtils.cp_r(Gemojione.images_path, tmpdir)
+ FileUtils.cp_r(File.join(emoji_dir, '.'), tmpdir)
Dir.chdir(tmpdir) do
Dir["**/*.png"].each do |png|
- resize!(File.join(tmpdir, png), SIZE)
+ tmp_image_path = File.join(tmpdir, png)
+ resize!(tmp_image_path, SIZE)
end
end
- style_path = Rails.root.join(*%w(app assets stylesheets pages emojis.scss))
+ style_path = Rails.root.join(*%w(app assets stylesheets framework emoji-sprites.scss))
# Combine the resized assets into a packed sprite and re-generate the SCSS
SpriteFactory.cssurl = "image-url('$IMAGE')"
- SpriteFactory.run!(File.join(tmpdir, 'png'), {
+ SpriteFactory.run!(tmpdir, {
output_style: style_path,
output_image: "app/assets/images/emoji.png",
selector: '.emoji-',
@@ -83,7 +102,7 @@ namespace :gemojione do
# let's simplify it
system(%Q(sed -i '' "s/width: #{SIZE}px; height: #{SIZE}px; background: image-url('emoji.png')/background-position:/" #{style_path}))
system(%Q(sed -i '' "s/ no-repeat//" #{style_path}))
- system(%Q(sed -i '' "s/ 0px/ 0/" #{style_path}))
+ system(%Q(sed -i '' "s/ 0px/ 0/g" #{style_path}))
# Append a generic rule that applies to all Emojis
File.open(style_path, 'a') do |f|
@@ -92,6 +111,8 @@ namespace :gemojione do
.emoji-icon {
background-image: image-url('emoji.png');
background-repeat: no-repeat;
+ color: transparent;
+ text-indent: -99em;
height: #{SIZE}px;
width: #{SIZE}px;
@@ -112,16 +133,17 @@ namespace :gemojione do
# Now do it again but for Retina
Dir.mktmpdir do |tmpdir|
# Copy the Gemojione assets to the temporary folder for resizing
- FileUtils.cp_r(Gemojione.images_path, tmpdir)
+ FileUtils.cp_r(File.join(emoji_dir, '.'), tmpdir)
Dir.chdir(tmpdir) do
Dir["**/*.png"].each do |png|
- resize!(File.join(tmpdir, png), RETINA)
+ tmp_image_path = File.join(tmpdir, png)
+ resize!(tmp_image_path, RETINA)
end
end
# Combine the resized assets into a packed sprite and re-generate the SCSS
- SpriteFactory.run!(File.join(tmpdir), {
+ SpriteFactory.run!(tmpdir, {
output_image: "app/assets/images/emoji@2x.png",
style: false,
nocomments: true,
@@ -155,4 +177,20 @@ namespace :gemojione do
image.write(image_path) { self.quality = 100 }
image.destroy!
end
+
+ EMOJI_IMAGE_PATH_RE = /(.*?)(([0-9a-f]-?)+)\.png$/i
+ def rename_to_named_emoji_image!(emoji_unicode_string_to_name_map, image_path)
+ # Rename file from unicode to emoji name
+ matches = EMOJI_IMAGE_PATH_RE.match(image_path)
+ preceding_path = matches[1]
+ unicode_string = matches[2]
+ name = emoji_unicode_string_to_name_map[unicode_string]
+ if name
+ new_png_path = File.join(preceding_path, "#{name}.png")
+ FileUtils.mv(image_path, new_png_path)
+ new_png_path
+ else
+ puts "Warning: emoji_unicode_string_to_name_map missing entry for #{unicode_string}. Full path: #{image_path}"
+ end
+ end
end
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 38edd49b6ed..a6f8c4ced5d 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -354,7 +354,8 @@ namespace :gitlab do
def check_repo_base_exists
puts "Repo base directory exists?"
- Gitlab.config.repositories.storages.each do |name, repo_base_path|
+ Gitlab.config.repositories.storages.each do |name, repository_storage|
+ repo_base_path = repository_storage['path']
print "#{name}... "
if File.exist?(repo_base_path)
@@ -378,7 +379,8 @@ namespace :gitlab do
def check_repo_base_is_not_symlink
puts "Repo storage directories are symlinks?"
- Gitlab.config.repositories.storages.each do |name, repo_base_path|
+ Gitlab.config.repositories.storages.each do |name, repository_storage|
+ repo_base_path = repository_storage['path']
print "#{name}... "
unless File.exist?(repo_base_path)
@@ -401,7 +403,8 @@ namespace :gitlab do
def check_repo_base_permissions
puts "Repo paths access is drwxrws---?"
- Gitlab.config.repositories.storages.each do |name, repo_base_path|
+ Gitlab.config.repositories.storages.each do |name, repository_storage|
+ repo_base_path = repository_storage['path']
print "#{name}... "
unless File.exist?(repo_base_path)
@@ -431,7 +434,8 @@ namespace :gitlab do
gitlab_shell_owner_group = Gitlab.config.gitlab_shell.owner_group
puts "Repo paths owned by #{gitlab_shell_ssh_user}:#{gitlab_shell_owner_group}?"
- Gitlab.config.repositories.storages.each do |name, repo_base_path|
+ Gitlab.config.repositories.storages.each do |name, repository_storage|
+ repo_base_path = repository_storage['path']
print "#{name}... "
unless File.exist?(repo_base_path)
@@ -810,8 +814,8 @@ namespace :gitlab do
namespace :repo do
desc "GitLab | Check the integrity of the repositories managed by GitLab"
task check: :environment do
- Gitlab.config.repositories.storages.each do |name, path|
- namespace_dirs = Dir.glob(File.join(path, '*'))
+ Gitlab.config.repositories.storages.each do |name, repository_storage|
+ namespace_dirs = Dir.glob(File.join(repository_storage['path'], '*'))
namespace_dirs.each do |namespace_dir|
repo_dirs = Dir.glob(File.join(namespace_dir, '*'))
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index daf7382dd02..f76bef5f4bf 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -6,7 +6,8 @@ namespace :gitlab do
remove_flag = ENV['REMOVE']
namespaces = Namespace.pluck(:path)
- Gitlab.config.repositories.storages.each do |name, git_base_path|
+ Gitlab.config.repositories.storages.each do |name, repository_storage|
+ git_base_path = repository_storage['path']
all_dirs = Dir.glob(git_base_path + '/*')
puts git_base_path.color(:yellow)
@@ -47,7 +48,8 @@ namespace :gitlab do
warn_user_is_not_gitlab
move_suffix = "+orphaned+#{Time.now.to_i}"
- Gitlab.config.repositories.storages.each do |name, repo_root|
+ Gitlab.config.repositories.storages.each do |name, repository_storage|
+ repo_root = repository_storage['path']
# Look for global repos (legacy, depth 1) and normal repos (depth 2)
IO.popen(%W(find #{repo_root} -mindepth 1 -maxdepth 2 -name *.git)) do |find|
find.each_line do |path|
diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake
index 66e7b7685f7..48bd9139ce8 100644
--- a/lib/tasks/gitlab/import.rake
+++ b/lib/tasks/gitlab/import.rake
@@ -11,7 +11,8 @@ namespace :gitlab do
#
desc "GitLab | Import bare repositories from repositories -> storages into GitLab project instance"
task repos: :environment do
- Gitlab.config.repositories.storages.each do |name, git_base_path|
+ Gitlab.config.repositories.storages.each_value do |repository_storage|
+ git_base_path = repository_storage['path']
repos_to_import = Dir.glob(git_base_path + '/**/*.git')
repos_to_import.each do |repo_path|
diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake
index ae78fe64eb8..a2a2db487b7 100644
--- a/lib/tasks/gitlab/info.rake
+++ b/lib/tasks/gitlab/info.rake
@@ -14,6 +14,8 @@ namespace :gitlab do
rake_version = run_and_match(%w(rake --version), /[\d\.]+/).try(:to_s)
# check redis version
redis_version = run_and_match(%w(redis-cli --version), /redis-cli (\d+\.\d+\.\d+)/).to_a
+ # check Git version
+ git_version = run_and_match([Gitlab.config.git.bin_path, '--version'], /git version ([\d\.]+)/).to_a
puts ""
puts "System information".color(:yellow)
@@ -26,6 +28,7 @@ namespace :gitlab do
puts "Bundler Version:#{bunder_version || "unknown".color(:red)}"
puts "Rake Version:\t#{rake_version || "unknown".color(:red)}"
puts "Redis Version:\t#{redis_version[1] || "unknown".color(:red)}"
+ puts "Git Version:\t#{git_version[1] || "unknown".color(:red)}"
puts "Sidekiq Version:#{Sidekiq::VERSION}"
# check database adapter
@@ -62,8 +65,8 @@ namespace :gitlab do
puts "GitLab Shell".color(:yellow)
puts "Version:\t#{gitlab_shell_version || "unknown".color(:red)}"
puts "Repository storage paths:"
- Gitlab.config.repositories.storages.each do |name, path|
- puts "- #{name}: \t#{path}"
+ Gitlab.config.repositories.storages.each do |name, repository_storage|
+ puts "- #{name}: \t#{repository_storage['path']}"
end
puts "Hooks:\t\t#{Gitlab.config.gitlab_shell.hooks_path}"
puts "Git:\t\t#{Gitlab.config.git.bin_path}"
diff --git a/lib/tasks/gitlab/task_helpers.rb b/lib/tasks/gitlab/task_helpers.rb
index 2a999ad6959..bb755ae689b 100644
--- a/lib/tasks/gitlab/task_helpers.rb
+++ b/lib/tasks/gitlab/task_helpers.rb
@@ -130,8 +130,8 @@ module Gitlab
end
def all_repos
- Gitlab.config.repositories.storages.each do |name, path|
- IO.popen(%W(find #{path} -mindepth 2 -maxdepth 2 -type d -name *.git)) do |find|
+ Gitlab.config.repositories.storages.each_value do |repository_storage|
+ IO.popen(%W(find #{repository_storage['path']} -mindepth 2 -maxdepth 2 -type d -name *.git)) do |find|
find.each_line do |path|
yield path.chomp
end
@@ -140,7 +140,7 @@ module Gitlab
end
def repository_storage_paths_args
- Gitlab.config.repositories.storages.values
+ Gitlab.config.repositories.storages.values.map { |rs| rs['path'] }
end
def user_home
diff --git a/package.json b/package.json
index 6c6a490b0f9..efa3a63e693 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,9 @@
"bootstrap-sass": "^3.3.6",
"compression-webpack-plugin": "^0.3.2",
"d3": "^3.5.11",
+ "document-register-element": "^1.3.0",
"dropzone": "^4.2.0",
+ "emoji-unicode-version": "^0.2.1",
"es6-promise": "^4.0.5",
"jquery": "^2.2.1",
"jquery-ujs": "^1.2.1",
@@ -29,6 +31,8 @@
"raw-loader": "^0.5.1",
"select2": "3.5.2-browserify",
"stats-webpack-plugin": "^0.4.3",
+ "string.fromcodepoint": "^0.2.1",
+ "string.prototype.codepointat": "^0.2.0",
"timeago.js": "^2.0.5",
"underscore": "^1.8.3",
"vue": "^2.1.10",
diff --git a/rubocop/cop/migration/add_concurrent_index.rb b/rubocop/cop/migration/add_concurrent_index.rb
new file mode 100644
index 00000000000..332fb7dcbd7
--- /dev/null
+++ b/rubocop/cop/migration/add_concurrent_index.rb
@@ -0,0 +1,34 @@
+require_relative '../../migration_helpers'
+
+module RuboCop
+ module Cop
+ module Migration
+ # Cop that checks if `add_concurrent_index` is used with `up`/`down` methods
+ # and not `change`.
+ class AddConcurrentIndex < RuboCop::Cop::Cop
+ include MigrationHelpers
+
+ MSG = '`add_concurrent_index` is not reversible so you must manually define ' \
+ 'the `up` and `down` methods in your migration class, using `remove_index` in `down`'.freeze
+
+ def on_send(node)
+ return unless in_migration?(node)
+
+ name = node.children[1]
+
+ return unless name == :add_concurrent_index
+
+ node.each_ancestor(:def) do |def_node|
+ next unless method_name(def_node) == :change
+
+ add_offense(def_node, :name)
+ end
+ end
+
+ def method_name(node)
+ node.children.first
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index ea8e0f64b0d..a50a522cf9d 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -3,4 +3,5 @@ require_relative 'cop/gem_fetcher'
require_relative 'cop/migration/add_column'
require_relative 'cop/migration/add_column_with_default'
require_relative 'cop/migration/add_concurrent_foreign_key'
+require_relative 'cop/migration/add_concurrent_index'
require_relative 'cop/migration/add_index'
diff --git a/spec/controllers/admin/applications_controller_spec.rb b/spec/controllers/admin/applications_controller_spec.rb
new file mode 100644
index 00000000000..e311b8a63b2
--- /dev/null
+++ b/spec/controllers/admin/applications_controller_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+describe Admin::ApplicationsController do
+ let(:admin) { create(:admin) }
+ let(:application) { create(:oauth_application, owner_id: nil, owner_type: nil) }
+
+ before do
+ sign_in(admin)
+ end
+
+ describe 'GET #new' do
+ it 'renders the application form' do
+ get :new
+
+ expect(response).to render_template :new
+ expect(assigns[:scopes]).to be_kind_of(Doorkeeper::OAuth::Scopes)
+ end
+ end
+
+ describe 'GET #edit' do
+ it 'renders the application form' do
+ get :edit, id: application.id
+
+ expect(response).to render_template :edit
+ expect(assigns[:scopes]).to be_kind_of(Doorkeeper::OAuth::Scopes)
+ end
+ end
+
+ describe 'POST #create' do
+ it 'creates the application' do
+ expect do
+ post :create, doorkeeper_application: attributes_for(:application)
+ end.to change { Doorkeeper::Application.count }.by(1)
+
+ application = Doorkeeper::Application.last
+
+ expect(response).to redirect_to(admin_application_path(application))
+ end
+
+ it 'renders the application form on errors' do
+ expect do
+ post :create, doorkeeper_application: attributes_for(:application).merge(redirect_uri: nil)
+ end.not_to change { Doorkeeper::Application.count }
+
+ expect(response).to render_template :new
+ expect(assigns[:scopes]).to be_kind_of(Doorkeeper::OAuth::Scopes)
+ end
+ end
+
+ describe 'PATCH #update' do
+ it 'updates the application' do
+ patch :update, id: application.id, doorkeeper_application: { redirect_uri: 'http://example.com/' }
+
+ expect(response).to redirect_to(admin_application_path(application))
+ expect(application.reload.redirect_uri).to eq 'http://example.com/'
+ end
+
+ it 'renders the application form on errors' do
+ patch :update, id: application.id, doorkeeper_application: { redirect_uri: nil }
+
+ expect(response).to render_template :edit
+ expect(assigns[:scopes]).to be_kind_of(Doorkeeper::OAuth::Scopes)
+ end
+ end
+end
diff --git a/spec/controllers/profiles/personal_access_tokens_spec.rb b/spec/controllers/profiles/personal_access_tokens_spec.rb
index 9d5f4c99f6d..dfed1de2046 100644
--- a/spec/controllers/profiles/personal_access_tokens_spec.rb
+++ b/spec/controllers/profiles/personal_access_tokens_spec.rb
@@ -2,48 +2,55 @@ require 'spec_helper'
describe Profiles::PersonalAccessTokensController do
let(:user) { create(:user) }
+ let(:token_attributes) { attributes_for(:personal_access_token) }
+
+ before { sign_in(user) }
describe '#create' do
def created_token
PersonalAccessToken.order(:created_at).last
end
- before { sign_in(user) }
-
- it "allows creation of a token" do
+ it "allows creation of a token with scopes" do
name = FFaker::Product.brand
+ scopes = %w[api read_user]
- post :create, personal_access_token: { name: name }
+ post :create, personal_access_token: token_attributes.merge(scopes: scopes, name: name)
expect(created_token).not_to be_nil
expect(created_token.name).to eq(name)
- expect(created_token.expires_at).to be_nil
+ expect(created_token.scopes).to eq(scopes)
expect(PersonalAccessToken.active).to include(created_token)
end
it "allows creation of a token with an expiry date" do
- expires_at = 5.days.from_now
+ expires_at = 5.days.from_now.to_date
- post :create, personal_access_token: { name: FFaker::Product.brand, expires_at: expires_at }
+ post :create, personal_access_token: token_attributes.merge(expires_at: expires_at)
expect(created_token).not_to be_nil
- expect(created_token.expires_at.to_i).to eq(expires_at.to_i)
+ expect(created_token.expires_at).to eq(expires_at)
end
+ end
- context "scopes" do
- it "allows creation of a token with scopes" do
- post :create, personal_access_token: { name: FFaker::Product.brand, scopes: %w(api read_user) }
+ describe '#index' do
+ let!(:active_personal_access_token) { create(:personal_access_token, user: user) }
+ let!(:inactive_personal_access_token) { create(:personal_access_token, :revoked, user: user) }
+ let!(:impersonation_personal_access_token) { create(:personal_access_token, :impersonation, user: user) }
- expect(created_token).not_to be_nil
- expect(created_token.scopes).to eq(%w(api read_user))
- end
+ before { get :index }
- it "allows creation of a token with no scopes" do
- post :create, personal_access_token: { name: FFaker::Product.brand, scopes: [] }
+ it "retrieves active personal access tokens" do
+ expect(assigns(:active_personal_access_tokens)).to include(active_personal_access_token)
+ end
+
+ it "retrieves inactive personal access tokens" do
+ expect(assigns(:inactive_personal_access_tokens)).to include(inactive_personal_access_token)
+ end
- expect(created_token).not_to be_nil
- expect(created_token.scopes).to eq([])
- end
+ it "does not retrieve impersonation personal access tokens" do
+ expect(assigns(:active_personal_access_tokens)).not_to include(impersonation_personal_access_token)
+ expect(assigns(:inactive_personal_access_tokens)).not_to include(impersonation_personal_access_token)
end
end
end
diff --git a/spec/controllers/projects/boards/issues_controller_spec.rb b/spec/controllers/projects/boards/issues_controller_spec.rb
index 3d0533cb516..15667e8d4b1 100644
--- a/spec/controllers/projects/boards/issues_controller_spec.rb
+++ b/spec/controllers/projects/boards/issues_controller_spec.rb
@@ -43,6 +43,7 @@ describe Projects::Boards::IssuesController do
expect(response).to match_response_schema('issues')
expect(parsed_response.length).to eq 2
+ expect(development.issues.map(&:relative_position)).not_to include(nil)
end
end
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index 84d119f1867..83d80b376fb 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -187,6 +187,52 @@ describe Projects::EnvironmentsController do
end
end
+ describe 'GET #metrics' do
+ before do
+ allow(controller).to receive(:environment).and_return(environment)
+ end
+
+ context 'when environment has no metrics' do
+ before do
+ expect(environment).to receive(:metrics).and_return(nil)
+ end
+
+ it 'returns a metrics page' do
+ get :metrics, environment_params
+
+ expect(response).to be_ok
+ end
+
+ context 'when requesting metrics as JSON' do
+ it 'returns a metrics JSON document' do
+ get :metrics, environment_params(format: :json)
+
+ expect(response).to have_http_status(204)
+ expect(json_response).to eq({})
+ end
+ end
+ end
+
+ context 'when environment has some metrics' do
+ before do
+ expect(environment).to receive(:metrics).and_return({
+ success: true,
+ metrics: {},
+ last_update: 42
+ })
+ end
+
+ it 'returns a metrics JSON document' do
+ get :metrics, environment_params(format: :json)
+
+ expect(response).to be_ok
+ expect(json_response['success']).to be(true)
+ expect(json_response['metrics']).to eq({})
+ expect(json_response['last_update']).to eq(42)
+ end
+ end
+ end
+
def environment_params(opts = {})
opts.reverse_merge(namespace_id: project.namespace,
project_id: project,
diff --git a/spec/controllers/projects/settings/repository_controller_spec.rb b/spec/controllers/projects/settings/repository_controller_spec.rb
new file mode 100644
index 00000000000..f73471f8ca8
--- /dev/null
+++ b/spec/controllers/projects/settings/repository_controller_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe Projects::Settings::RepositoryController do
+ let(:project) { create(:project_empty_repo, :public) }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_master(user)
+ sign_in(user)
+ end
+
+ describe 'GET show' do
+ it 'renders show with 200 status code' do
+ get :show, namespace_id: project.namespace, project_id: project
+
+ expect(response).to have_http_status(200)
+ expect(response).to render_template(:show)
+ end
+ end
+end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 202759664a0..a1ec41322ad 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -158,14 +158,6 @@ describe ProjectsController do
expect(response).to render_template('_activity')
end
- it "renders the readme view" do
- allow(controller).to receive(:current_user).and_return(user)
- allow(user).to receive(:project_view).and_return('readme')
-
- get :show, namespace_id: public_project.namespace, id: public_project
- expect(response).to render_template('_readme')
- end
-
it "renders the files view" do
allow(controller).to receive(:current_user).and_return(user)
allow(user).to receive(:project_view).and_return('files')
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index c9584ddf18c..f67d26da0ac 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -1,4 +1,9 @@
require 'spec_helper'
+shared_examples 'content not cached without revalidation' do
+ it 'ensures content will not be cached without revalidation' do
+ expect(subject['Cache-Control']).to eq('max-age=0, private, must-revalidate')
+ end
+end
describe UploadsController do
let!(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
@@ -50,6 +55,13 @@ describe UploadsController do
expect(response).to have_http_status(200)
end
+
+ it_behaves_like 'content not cached without revalidation' do
+ subject do
+ get :show, model: 'user', mounted_as: 'avatar', id: user.id, filename: 'image.png'
+ response
+ end
+ end
end
end
@@ -59,6 +71,13 @@ describe UploadsController do
expect(response).to have_http_status(200)
end
+
+ it_behaves_like 'content not cached without revalidation' do
+ subject do
+ get :show, model: 'user', mounted_as: 'avatar', id: user.id, filename: 'image.png'
+ response
+ end
+ end
end
end
@@ -76,6 +95,13 @@ describe UploadsController do
expect(response).to have_http_status(200)
end
+
+ it_behaves_like 'content not cached without revalidation' do
+ subject do
+ get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'image.png'
+ response
+ end
+ end
end
context "when signed in" do
@@ -88,6 +114,13 @@ describe UploadsController do
expect(response).to have_http_status(200)
end
+
+ it_behaves_like 'content not cached without revalidation' do
+ subject do
+ get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'image.png'
+ response
+ end
+ end
end
end
@@ -133,6 +166,13 @@ describe UploadsController do
expect(response).to have_http_status(200)
end
+
+ it_behaves_like 'content not cached without revalidation' do
+ subject do
+ get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'image.png'
+ response
+ end
+ end
end
end
@@ -157,6 +197,13 @@ describe UploadsController do
expect(response).to have_http_status(200)
end
+
+ it_behaves_like 'content not cached without revalidation' do
+ subject do
+ get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'image.png'
+ response
+ end
+ end
end
context "when signed in" do
@@ -169,6 +216,13 @@ describe UploadsController do
expect(response).to have_http_status(200)
end
+
+ it_behaves_like 'content not cached without revalidation' do
+ subject do
+ get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'image.png'
+ response
+ end
+ end
end
end
@@ -205,6 +259,13 @@ describe UploadsController do
expect(response).to have_http_status(200)
end
+
+ it_behaves_like 'content not cached without revalidation' do
+ subject do
+ get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'image.png'
+ response
+ end
+ end
end
end
@@ -234,6 +295,13 @@ describe UploadsController do
expect(response).to have_http_status(200)
end
+
+ it_behaves_like 'content not cached without revalidation' do
+ subject do
+ get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'image.png'
+ response
+ end
+ end
end
context "when signed in" do
@@ -246,6 +314,13 @@ describe UploadsController do
expect(response).to have_http_status(200)
end
+
+ it_behaves_like 'content not cached without revalidation' do
+ subject do
+ get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'image.png'
+ response
+ end
+ end
end
end
@@ -291,6 +366,13 @@ describe UploadsController do
expect(response).to have_http_status(200)
end
+
+ it_behaves_like 'content not cached without revalidation' do
+ subject do
+ get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'image.png'
+ response
+ end
+ end
end
end
diff --git a/spec/factories/chat_teams.rb b/spec/factories/chat_teams.rb
new file mode 100644
index 00000000000..82f44fa3d15
--- /dev/null
+++ b/spec/factories/chat_teams.rb
@@ -0,0 +1,9 @@
+FactoryGirl.define do
+ factory :chat_team, class: ChatTeam do
+ sequence :team_id do |n|
+ "abcdefghijklm#{n}"
+ end
+
+ namespace factory: :group
+ end
+end
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 279583c2c44..6b0d084614b 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -15,8 +15,8 @@ FactoryGirl.define do
options do
{
- image: "ruby:2.1",
- services: ["postgres"]
+ image: 'ruby:2.1',
+ services: ['postgres']
}
end
@@ -166,5 +166,31 @@ FactoryGirl.define do
allow(build).to receive(:commit).and_return build(:commit)
end
end
+
+ trait :extended_options do
+ options do
+ {
+ image: 'ruby:2.1',
+ services: ['postgres'],
+ after_script: "ls\ndate",
+ artifacts: {
+ name: 'artifacts_file',
+ untracked: false,
+ paths: ['out/'],
+ when: 'always',
+ expire_in: '7d'
+ },
+ cache: {
+ key: 'cache_key',
+ untracked: false,
+ paths: ['vendor/*']
+ }
+ }
+ end
+ end
+
+ trait :no_options do
+ options { {} }
+ end
end
end
diff --git a/spec/factories/oauth_access_grants.rb b/spec/factories/oauth_access_grants.rb
new file mode 100644
index 00000000000..543b3e99274
--- /dev/null
+++ b/spec/factories/oauth_access_grants.rb
@@ -0,0 +1,11 @@
+FactoryGirl.define do
+ factory :oauth_access_grant do
+ resource_owner_id { create(:user).id }
+ application
+ token { Doorkeeper::OAuth::Helpers::UniqueToken.generate }
+ expires_in 2.hours
+
+ redirect_uri { application.redirect_uri }
+ scopes { application.scopes }
+ end
+end
diff --git a/spec/factories/oauth_access_tokens.rb b/spec/factories/oauth_access_tokens.rb
index ccf02d0719b..a46bc1d8ce8 100644
--- a/spec/factories/oauth_access_tokens.rb
+++ b/spec/factories/oauth_access_tokens.rb
@@ -2,6 +2,7 @@ FactoryGirl.define do
factory :oauth_access_token do
resource_owner
application
- token '123456'
+ token { Doorkeeper::OAuth::Helpers::UniqueToken.generate }
+ scopes { application.scopes }
end
end
diff --git a/spec/factories/oauth_applications.rb b/spec/factories/oauth_applications.rb
index d116a573830..86cdc208268 100644
--- a/spec/factories/oauth_applications.rb
+++ b/spec/factories/oauth_applications.rb
@@ -1,7 +1,7 @@
FactoryGirl.define do
factory :oauth_application, class: 'Doorkeeper::Application', aliases: [:application] do
name { FFaker::Name.name }
- uid { FFaker::Name.name }
+ uid { Doorkeeper::OAuth::Helpers::UniqueToken.generate }
redirect_uri { FFaker::Internet.uri('http') }
owner
owner_type 'User'
diff --git a/spec/factories/personal_access_tokens.rb b/spec/factories/personal_access_tokens.rb
index 811eab7e15b..7b15ba47de1 100644
--- a/spec/factories/personal_access_tokens.rb
+++ b/spec/factories/personal_access_tokens.rb
@@ -6,5 +6,22 @@ FactoryGirl.define do
revoked false
expires_at { 5.days.from_now }
scopes ['api']
+ impersonation false
+
+ trait :impersonation do
+ impersonation true
+ end
+
+ trait :revoked do
+ revoked true
+ end
+
+ trait :expired do
+ expires_at { 1.day.ago }
+ end
+
+ trait :invalid do
+ token nil
+ end
end
end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 70c65bc693a..c6f91e05d83 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -195,4 +195,15 @@ FactoryGirl.define do
factory :kubernetes_project, parent: :empty_project do
kubernetes_service
end
+
+ factory :prometheus_project, parent: :empty_project do
+ after :create do |project|
+ project.create_prometheus_service(
+ active: true,
+ properties: {
+ api_url: 'https://prometheus.example.com'
+ }
+ )
+ end
+ end
end
diff --git a/spec/features/admin/admin_users_impersonation_tokens_spec.rb b/spec/features/admin/admin_users_impersonation_tokens_spec.rb
new file mode 100644
index 00000000000..9ff5c2f9d40
--- /dev/null
+++ b/spec/features/admin/admin_users_impersonation_tokens_spec.rb
@@ -0,0 +1,72 @@
+require 'spec_helper'
+
+describe 'Admin > Users > Impersonation Tokens', feature: true, js: true do
+ let(:admin) { create(:admin) }
+ let!(:user) { create(:user) }
+
+ def active_impersonation_tokens
+ find(".table.active-tokens")
+ end
+
+ def inactive_impersonation_tokens
+ find(".table.inactive-tokens")
+ end
+
+ before { login_as(admin) }
+
+ describe "token creation" do
+ it "allows creation of a token" do
+ name = FFaker::Product.brand
+
+ visit admin_user_impersonation_tokens_path(user_id: user.username)
+ fill_in "Name", with: name
+
+ # Set date to 1st of next month
+ find_field("Expires at").trigger('focus')
+ find(".pika-next").click
+ click_on "1"
+
+ # Scopes
+ check "api"
+ check "read_user"
+
+ expect { click_on "Create Impersonation Token" }.to change { PersonalAccessTokensFinder.new(impersonation: true).execute.count }
+ expect(active_impersonation_tokens).to have_text(name)
+ expect(active_impersonation_tokens).to have_text('In')
+ expect(active_impersonation_tokens).to have_text('api')
+ expect(active_impersonation_tokens).to have_text('read_user')
+ end
+ end
+
+ describe 'active tokens' do
+ let!(:impersonation_token) { create(:personal_access_token, :impersonation, user: user) }
+ let!(:personal_access_token) { create(:personal_access_token, user: user) }
+
+ it 'only shows impersonation tokens' do
+ visit admin_user_impersonation_tokens_path(user_id: user.username)
+
+ expect(active_impersonation_tokens).to have_text(impersonation_token.name)
+ expect(active_impersonation_tokens).not_to have_text(personal_access_token.name)
+ end
+ end
+
+ describe "inactive tokens" do
+ let!(:impersonation_token) { create(:personal_access_token, :impersonation, user: user) }
+
+ it "allows revocation of an active impersonation token" do
+ visit admin_user_impersonation_tokens_path(user_id: user.username)
+
+ click_on "Revoke"
+
+ expect(inactive_impersonation_tokens).to have_text(impersonation_token.name)
+ end
+
+ it "moves expired tokens to the 'inactive' section" do
+ impersonation_token.update(expires_at: 5.days.ago)
+
+ visit admin_user_impersonation_tokens_path(user_id: user.username)
+
+ expect(inactive_impersonation_tokens).to have_text(impersonation_token.name)
+ end
+ end
+end
diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb
index b740e191f48..55e10a1a89b 100644
--- a/spec/features/atom/users_spec.rb
+++ b/spec/features/atom/users_spec.rb
@@ -57,7 +57,7 @@ describe "User Feed", feature: true do
end
it 'has XHTML summaries in notes' do
- expect(body).to match /Bug confirmed <img[^>]*\/>/
+ expect(body).to match /Bug confirmed <gl-emoji[^>]*>/
end
it 'has XHTML summaries in merge request descriptions' do
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index e247bfa2980..ecc356f2505 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -71,16 +71,16 @@ describe 'Issue Boards', feature: true, js: true do
let!(:list1) { create(:list, board: board, label: planning, position: 0) }
let!(:list2) { create(:list, board: board, label: development, position: 1) }
- let!(:confidential_issue) { create(:labeled_issue, :confidential, project: project, author: user, labels: [planning]) }
- let!(:issue1) { create(:labeled_issue, project: project, assignee: user, labels: [planning]) }
- let!(:issue2) { create(:labeled_issue, project: project, author: user2, labels: [planning]) }
- let!(:issue3) { create(:labeled_issue, project: project, labels: [planning]) }
- let!(:issue4) { create(:labeled_issue, project: project, labels: [planning]) }
- let!(:issue5) { create(:labeled_issue, project: project, labels: [planning], milestone: milestone) }
- let!(:issue6) { create(:labeled_issue, project: project, labels: [planning, development]) }
- let!(:issue7) { create(:labeled_issue, project: project, labels: [development]) }
+ let!(:confidential_issue) { create(:labeled_issue, :confidential, project: project, author: user, labels: [planning], relative_position: 9) }
+ let!(:issue1) { create(:labeled_issue, project: project, assignee: user, labels: [planning], relative_position: 8) }
+ let!(:issue2) { create(:labeled_issue, project: project, author: user2, labels: [planning], relative_position: 7) }
+ let!(:issue3) { create(:labeled_issue, project: project, labels: [planning], relative_position: 6) }
+ let!(:issue4) { create(:labeled_issue, project: project, labels: [planning], relative_position: 5) }
+ let!(:issue5) { create(:labeled_issue, project: project, labels: [planning], milestone: milestone, relative_position: 4) }
+ let!(:issue6) { create(:labeled_issue, project: project, labels: [planning, development], relative_position: 3) }
+ let!(:issue7) { create(:labeled_issue, project: project, labels: [development], relative_position: 2) }
let!(:issue8) { create(:closed_issue, project: project) }
- let!(:issue9) { create(:labeled_issue, project: project, labels: [planning, testing, bug, accepting]) }
+ let!(:issue9) { create(:labeled_issue, project: project, labels: [planning, testing, bug, accepting], relative_position: 1) }
before do
visit namespace_project_board_path(project.namespace, project, board)
diff --git a/spec/features/boards/issue_ordering_spec.rb b/spec/features/boards/issue_ordering_spec.rb
new file mode 100644
index 00000000000..c50155a6d14
--- /dev/null
+++ b/spec/features/boards/issue_ordering_spec.rb
@@ -0,0 +1,166 @@
+require 'rails_helper'
+
+describe 'Issue Boards', :feature, :js do
+ include WaitForVueResource
+ include DragTo
+
+ let(:project) { create(:empty_project, :public) }
+ let(:board) { create(:board, project: project) }
+ let(:user) { create(:user) }
+ let(:label) { create(:label, project: project) }
+ let!(:list1) { create(:list, board: board, label: label, position: 0) }
+ let!(:issue1) { create(:labeled_issue, project: project, title: 'testing 1', labels: [label], relative_position: 3) }
+ let!(:issue2) { create(:labeled_issue, project: project, title: 'testing 2', labels: [label], relative_position: 2) }
+ let!(:issue3) { create(:labeled_issue, project: project, title: 'testing 3', labels: [label], relative_position: 1) }
+
+ before do
+ project.team << [user, :master]
+
+ login_as(user)
+ end
+
+ context 'un-ordered issues' do
+ let!(:issue4) { create(:labeled_issue, project: project, labels: [label]) }
+
+ before do
+ visit namespace_project_board_path(project.namespace, project, board)
+ wait_for_vue_resource
+
+ expect(page).to have_selector('.board', count: 2)
+ end
+
+ it 'has un-ordered issue as last issue' do
+ page.within(first('.board')) do
+ expect(all('.card').last).to have_content(issue4.title)
+ end
+ end
+
+ it 'moves un-ordered issue to top of list' do
+ drag(from_index: 3, to_index: 0)
+
+ page.within(first('.board')) do
+ expect(first('.card')).to have_content(issue4.title)
+ end
+ end
+ end
+
+ context 'ordering in list' do
+ before do
+ visit namespace_project_board_path(project.namespace, project, board)
+ wait_for_vue_resource
+
+ expect(page).to have_selector('.board', count: 2)
+ end
+
+ it 'moves from middle to top' do
+ drag(from_index: 1, to_index: 0)
+
+ wait_for_vue_resource
+
+ expect(first('.card')).to have_content(issue2.title)
+ end
+
+ it 'moves from middle to bottom' do
+ drag(from_index: 1, to_index: 2)
+
+ wait_for_vue_resource
+
+ expect(all('.card').last).to have_content(issue2.title)
+ end
+
+ it 'moves from top to bottom' do
+ drag(from_index: 0, to_index: 2)
+
+ wait_for_vue_resource
+
+ expect(all('.card').last).to have_content(issue3.title)
+ end
+
+ it 'moves from bottom to top' do
+ drag(from_index: 2, to_index: 0)
+
+ wait_for_vue_resource
+
+ expect(first('.card')).to have_content(issue1.title)
+ end
+
+ it 'moves from top to middle' do
+ drag(from_index: 0, to_index: 1)
+
+ wait_for_vue_resource
+
+ expect(first('.card')).to have_content(issue2.title)
+ end
+
+ it 'moves from bottom to middle' do
+ drag(from_index: 2, to_index: 1)
+
+ wait_for_vue_resource
+
+ expect(all('.card').last).to have_content(issue2.title)
+ end
+ end
+
+ context 'ordering when changing list' do
+ let(:label2) { create(:label, project: project) }
+ let!(:list2) { create(:list, board: board, label: label2, position: 1) }
+ let!(:issue4) { create(:labeled_issue, project: project, title: 'testing 1', labels: [label2], relative_position: 3.0) }
+ let!(:issue5) { create(:labeled_issue, project: project, title: 'testing 2', labels: [label2], relative_position: 2.0) }
+ let!(:issue6) { create(:labeled_issue, project: project, title: 'testing 3', labels: [label2], relative_position: 1.0) }
+
+ before do
+ visit namespace_project_board_path(project.namespace, project, board)
+ wait_for_vue_resource
+
+ expect(page).to have_selector('.board', count: 3)
+ end
+
+ it 'moves to top of another list' do
+ drag(list_from_index: 0, list_to_index: 1)
+
+ wait_for_vue_resource
+
+ expect(first('.board')).to have_selector('.card', count: 2)
+ expect(all('.board')[1]).to have_selector('.card', count: 4)
+
+ page.within(all('.board')[1]) do
+ expect(first('.card')).to have_content(issue3.title)
+ end
+ end
+
+ it 'moves to bottom of another list' do
+ drag(list_from_index: 0, list_to_index: 1, to_index: 2)
+
+ wait_for_vue_resource
+
+ expect(first('.board')).to have_selector('.card', count: 2)
+ expect(all('.board')[1]).to have_selector('.card', count: 4)
+
+ page.within(all('.board')[1]) do
+ expect(all('.card').last).to have_content(issue3.title)
+ end
+ end
+
+ it 'moves to index of another list' do
+ drag(list_from_index: 0, list_to_index: 1, to_index: 1)
+
+ wait_for_vue_resource
+
+ expect(first('.board')).to have_selector('.card', count: 2)
+ expect(all('.board')[1]).to have_selector('.card', count: 4)
+
+ page.within(all('.board')[1]) do
+ expect(all('.card')[1]).to have_content(issue3.title)
+ end
+ end
+ end
+
+ def drag(selector: '.board-list', list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0)
+ drag_to(selector: selector,
+ scrollable: '#board-app',
+ list_from_index: list_from_index,
+ from_index: from_index,
+ to_index: to_index,
+ list_to_index: list_to_index)
+ end
+end
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index 59e87b3f69c..3332e07ec31 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -11,8 +11,8 @@ describe 'Issue Boards', feature: true, js: true do
let!(:bug) { create(:label, project: project, name: 'Bug') }
let!(:regression) { create(:label, project: project, name: 'Regression') }
let!(:stretch) { create(:label, project: project, name: 'Stretch') }
- let!(:issue1) { create(:labeled_issue, project: project, assignee: user, milestone: milestone, labels: [development]) }
- let!(:issue2) { create(:labeled_issue, project: project, labels: [development, stretch]) }
+ let!(:issue1) { create(:labeled_issue, project: project, assignee: user, milestone: milestone, labels: [development], relative_position: 2) }
+ let!(:issue2) { create(:labeled_issue, project: project, labels: [development, stretch], relative_position: 1) }
let(:board) { create(:board, project: project) }
let!(:list) { create(:list, board: board, label: development, position: 0) }
let(:card) { first('.board').first('.card') }
diff --git a/spec/features/copy_as_gfm_spec.rb b/spec/features/copy_as_gfm_spec.rb
index fbab4fa9c4f..4638812b2d9 100644
--- a/spec/features/copy_as_gfm_spec.rb
+++ b/spec/features/copy_as_gfm_spec.rb
@@ -252,7 +252,7 @@ describe 'Copy as GFM', feature: true, js: true do
<<-GFM.strip_heredoc
<a name="named-anchor"></a>
-
+
<sub>sub</sub>
<dl>
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index 9f173dbc99d..d243f9478bb 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -143,7 +143,7 @@ feature 'Group', feature: true do
visit path
- expect(page).to have_css('.group-home-desc > p > img')
+ expect(page).to have_css('.group-home-desc > p > gl-emoji')
end
it 'sanitizes unwanted tags' do
diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb
index 3ab3d2d4229..f424186cf30 100644
--- a/spec/features/issues/award_emoji_spec.rb
+++ b/spec/features/issues/award_emoji_spec.rb
@@ -25,14 +25,14 @@ describe 'Awards Emoji', feature: true do
end
it 'increments the thumbsdown emoji', js: true do
- find('[data-emoji="thumbsdown"]').click
+ find('[data-name="thumbsdown"]').click
wait_for_ajax
expect(thumbsdown_emoji).to have_text("1")
end
context 'click the thumbsup emoji' do
it 'increments the thumbsup emoji', js: true do
- find('[data-emoji="thumbsup"]').click
+ find('[data-name="thumbsup"]').click
wait_for_ajax
expect(thumbsup_emoji).to have_text("1")
end
@@ -44,7 +44,7 @@ describe 'Awards Emoji', feature: true do
context 'click the thumbsdown emoji' do
it 'increments the thumbsdown emoji', js: true do
- find('[data-emoji="thumbsdown"]').click
+ find('[data-name="thumbsdown"]').click
wait_for_ajax
expect(thumbsdown_emoji).to have_text("1")
end
@@ -123,9 +123,9 @@ describe 'Awards Emoji', feature: true do
end
unless status
- first('[data-emoji="smiley"]').click
+ first('[data-name="smiley"]').click
else
- find('[data-emoji="smiley"]').click
+ find('[data-name="smiley"]').click
end
wait_for_ajax
diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
index 93763f092fb..4dcc56a97d1 100644
--- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
@@ -1,6 +1,7 @@
require 'rails_helper'
-describe 'Dropdown assignee', js: true, feature: true do
+describe 'Dropdown assignee', :feature, :js do
+ include FilteredSearchHelpers
include WaitForAjax
let!(:project) { create(:empty_project) }
@@ -9,17 +10,10 @@ describe 'Dropdown assignee', js: true, feature: true do
let!(:user_jacob) { create(:user, name: 'Jacob', username: 'otter32') }
let(:filtered_search) { find('.filtered-search') }
let(:js_dropdown_assignee) { '#js-dropdown-assignee' }
-
- def send_keys_to_filtered_search(input)
- input.split("").each do |i|
- filtered_search.send_keys(i)
- sleep 5
- wait_for_ajax
- end
- end
+ let(:filter_dropdown) { find("#{js_dropdown_assignee} .filter-dropdown") }
def dropdown_assignee_size
- page.all('#js-dropdown-assignee .filter-dropdown .filter-dropdown-item').size
+ filter_dropdown.all('.filter-dropdown-item').size
end
def click_assignee(text)
@@ -56,63 +50,80 @@ describe 'Dropdown assignee', js: true, feature: true do
end
it 'should hide loading indicator when loaded' do
- send_keys_to_filtered_search('assignee:')
+ filtered_search.set('assignee:')
- expect(page).not_to have_css('#js-dropdown-assignee .filter-dropdown-loading')
+ expect(find(js_dropdown_assignee)).to have_css('.filter-dropdown-loading')
+ expect(find(js_dropdown_assignee)).not_to have_css('.filter-dropdown-loading')
end
it 'should load all the assignees when opened' do
- send_keys_to_filtered_search('assignee:')
+ filtered_search.set('assignee:')
expect(dropdown_assignee_size).to eq(3)
end
it 'shows current user at top of dropdown' do
- send_keys_to_filtered_search('assignee:')
+ filtered_search.set('assignee:')
- expect(first('#js-dropdown-assignee .filter-dropdown li')).to have_content(user.name)
+ expect(filter_dropdown.first('.filter-dropdown-item')).to have_content(user.name)
end
end
describe 'filtering' do
before do
- send_keys_to_filtered_search('assignee:')
+ filtered_search.set('assignee:')
+
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_john.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user.name)
end
it 'filters by name' do
- send_keys_to_filtered_search('j')
+ filtered_search.send_keys('j')
- expect(dropdown_assignee_size).to eq(2)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_john.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_no_content(user.name)
end
it 'filters by case insensitive name' do
- send_keys_to_filtered_search('J')
+ filtered_search.send_keys('J')
- expect(dropdown_assignee_size).to eq(2)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_john.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_no_content(user.name)
end
it 'filters by username with symbol' do
- send_keys_to_filtered_search('@ot')
+ filtered_search.send_keys('@ot')
- expect(dropdown_assignee_size).to eq(2)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_no_content(user_john.name)
end
it 'filters by case insensitive username with symbol' do
- send_keys_to_filtered_search('@OT')
+ filtered_search.send_keys('@OT')
- expect(dropdown_assignee_size).to eq(2)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_no_content(user_john.name)
end
it 'filters by username without symbol' do
- send_keys_to_filtered_search('ot')
+ filtered_search.send_keys('ot')
- expect(dropdown_assignee_size).to eq(2)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_no_content(user_john.name)
end
it 'filters by case insensitive username without symbol' do
- send_keys_to_filtered_search('OT')
+ filtered_search.send_keys('OT')
- expect(dropdown_assignee_size).to eq(2)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_no_content(user_john.name)
end
end
@@ -125,22 +136,25 @@ describe 'Dropdown assignee', js: true, feature: true do
click_assignee(user_jacob.name)
expect(page).to have_css(js_dropdown_assignee, visible: false)
- expect(filtered_search.value).to eq("assignee:@#{user_jacob.username} ")
+ expect_tokens([{ name: 'assignee', value: "@#{user_jacob.username}" }])
+ expect_filtered_search_input_empty
end
it 'fills in the assignee username when the assignee has been filtered' do
- send_keys_to_filtered_search('roo')
+ filtered_search.send_keys('roo')
click_assignee(user.name)
expect(page).to have_css(js_dropdown_assignee, visible: false)
- expect(filtered_search.value).to eq("assignee:@#{user.username} ")
+ expect_tokens([{ name: 'assignee', value: "@#{user.username}" }])
+ expect_filtered_search_input_empty
end
it 'selects `no assignee`' do
find('#js-dropdown-assignee .filter-dropdown-item', text: 'No Assignee').click
expect(page).to have_css(js_dropdown_assignee, visible: false)
- expect(filtered_search.value).to eq("assignee:none ")
+ expect_tokens([{ name: 'assignee', value: 'none' }])
+ expect_filtered_search_input_empty
end
end
@@ -173,7 +187,7 @@ describe 'Dropdown assignee', js: true, feature: true do
describe 'caching requests' do
it 'caches requests after the first load' do
filtered_search.set('assignee')
- send_keys_to_filtered_search(':')
+ filtered_search.send_keys(':')
initial_size = dropdown_assignee_size
expect(initial_size).to be > 0
@@ -182,7 +196,7 @@ describe 'Dropdown assignee', js: true, feature: true do
project.team << [new_user, :master]
find('.filtered-search-input-container .clear-search').click
filtered_search.set('assignee')
- send_keys_to_filtered_search(':')
+ filtered_search.send_keys(':')
expect(dropdown_assignee_size).to eq(initial_size)
end
diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb
index 59e302f0e2d..19a00618b12 100644
--- a/spec/features/issues/filtered_search/dropdown_author_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb
@@ -1,6 +1,7 @@
require 'rails_helper'
describe 'Dropdown author', js: true, feature: true do
+ include FilteredSearchHelpers
include WaitForAjax
let!(:project) { create(:empty_project) }
@@ -121,14 +122,16 @@ describe 'Dropdown author', js: true, feature: true do
click_author(user_jacob.name)
expect(page).to have_css(js_dropdown_author, visible: false)
- expect(filtered_search.value).to eq("author:@#{user_jacob.username} ")
+ expect_tokens([{ name: 'author', value: "@#{user_jacob.username}" }])
+ expect_filtered_search_input_empty
end
it 'fills in the author username when the author has been filtered' do
click_author(user.name)
expect(page).to have_css(js_dropdown_author, visible: false)
- expect(filtered_search.value).to eq("author:@#{user.username} ")
+ expect_tokens([{ name: 'author', value: "@#{user.username}" }])
+ expect_filtered_search_input_empty
end
end
diff --git a/spec/features/issues/filtered_search/dropdown_hint_spec.rb b/spec/features/issues/filtered_search/dropdown_hint_spec.rb
index 04dd54ab459..01b657bcada 100644
--- a/spec/features/issues/filtered_search/dropdown_hint_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_hint_spec.rb
@@ -1,6 +1,7 @@
require 'rails_helper'
describe 'Dropdown hint', js: true, feature: true do
+ include FilteredSearchHelpers
include WaitForAjax
let!(:project) { create(:empty_project) }
@@ -66,7 +67,8 @@ describe 'Dropdown hint', js: true, feature: true do
expect(page).to have_css(js_dropdown_hint, visible: false)
expect(page).to have_css('#js-dropdown-author', visible: true)
- expect(filtered_search.value).to eq('author:')
+ expect_tokens([{ name: 'author' }])
+ expect_filtered_search_input_empty
end
it 'opens the assignee dropdown when you click on assignee' do
@@ -74,7 +76,8 @@ describe 'Dropdown hint', js: true, feature: true do
expect(page).to have_css(js_dropdown_hint, visible: false)
expect(page).to have_css('#js-dropdown-assignee', visible: true)
- expect(filtered_search.value).to eq('assignee:')
+ expect_tokens([{ name: 'assignee' }])
+ expect_filtered_search_input_empty
end
it 'opens the milestone dropdown when you click on milestone' do
@@ -82,7 +85,8 @@ describe 'Dropdown hint', js: true, feature: true do
expect(page).to have_css(js_dropdown_hint, visible: false)
expect(page).to have_css('#js-dropdown-milestone', visible: true)
- expect(filtered_search.value).to eq('milestone:')
+ expect_tokens([{ name: 'milestone' }])
+ expect_filtered_search_input_empty
end
it 'opens the label dropdown when you click on label' do
@@ -90,7 +94,8 @@ describe 'Dropdown hint', js: true, feature: true do
expect(page).to have_css(js_dropdown_hint, visible: false)
expect(page).to have_css('#js-dropdown-label', visible: true)
- expect(filtered_search.value).to eq('label:')
+ expect_tokens([{ name: 'label' }])
+ expect_filtered_search_input_empty
end
end
@@ -101,7 +106,8 @@ describe 'Dropdown hint', js: true, feature: true do
expect(page).to have_css(js_dropdown_hint, visible: false)
expect(page).to have_css('#js-dropdown-author', visible: true)
- expect(filtered_search.value).to eq('author:')
+ expect_tokens([{ name: 'author' }])
+ expect_filtered_search_input_empty
end
it 'opens the assignee dropdown when you click on assignee' do
@@ -110,7 +116,8 @@ describe 'Dropdown hint', js: true, feature: true do
expect(page).to have_css(js_dropdown_hint, visible: false)
expect(page).to have_css('#js-dropdown-assignee', visible: true)
- expect(filtered_search.value).to eq('assignee:')
+ expect_tokens([{ name: 'assignee' }])
+ expect_filtered_search_input_empty
end
it 'opens the milestone dropdown when you click on milestone' do
@@ -119,7 +126,8 @@ describe 'Dropdown hint', js: true, feature: true do
expect(page).to have_css(js_dropdown_hint, visible: false)
expect(page).to have_css('#js-dropdown-milestone', visible: true)
- expect(filtered_search.value).to eq('milestone:')
+ expect_tokens([{ name: 'milestone' }])
+ expect_filtered_search_input_empty
end
it 'opens the label dropdown when you click on label' do
@@ -128,7 +136,46 @@ describe 'Dropdown hint', js: true, feature: true do
expect(page).to have_css(js_dropdown_hint, visible: false)
expect(page).to have_css('#js-dropdown-label', visible: true)
- expect(filtered_search.value).to eq('label:')
+ expect_tokens([{ name: 'label' }])
+ expect_filtered_search_input_empty
+ end
+ end
+
+ describe 'reselecting from dropdown' do
+ it 'reuses existing author text' do
+ filtered_search.send_keys('author:')
+ filtered_search.send_keys(:backspace)
+ click_hint('author')
+
+ expect_tokens([{ name: 'author' }])
+ expect_filtered_search_input_empty
+ end
+
+ it 'reuses existing assignee text' do
+ filtered_search.send_keys('assignee:')
+ filtered_search.send_keys(:backspace)
+ click_hint('assignee')
+
+ expect_tokens([{ name: 'assignee' }])
+ expect_filtered_search_input_empty
+ end
+
+ it 'reuses existing milestone text' do
+ filtered_search.send_keys('milestone:')
+ filtered_search.send_keys(:backspace)
+ click_hint('milestone')
+
+ expect_tokens([{ name: 'milestone' }])
+ expect_filtered_search_input_empty
+ end
+
+ it 'reuses existing label text' do
+ filtered_search.send_keys('label:')
+ filtered_search.send_keys(:backspace)
+ click_hint('label')
+
+ expect_tokens([{ name: 'label' }])
+ expect_filtered_search_input_empty
end
end
end
diff --git a/spec/features/issues/filtered_search/dropdown_label_spec.rb b/spec/features/issues/filtered_search/dropdown_label_spec.rb
index ab3b868fd3a..b192064b693 100644
--- a/spec/features/issues/filtered_search/dropdown_label_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_label_spec.rb
@@ -51,7 +51,8 @@ describe 'Dropdown label', js: true, feature: true do
filtered_search.native.send_keys(:down, :down, :enter)
- expect(filtered_search.value).to eq("label:~#{bug_label.title} ")
+ expect_tokens([{ name: 'label', value: "~#{bug_label.title}" }])
+ expect_filtered_search_input_empty
end
end
@@ -92,7 +93,7 @@ describe 'Dropdown label', js: true, feature: true do
end
it 'filters by case-insensitive name with or without symbol' do
- search_for_label('b')
+ filtered_search.send_keys('b')
expect(filter_dropdown.find('.filter-dropdown-item', text: bug_label.title)).to be_visible
expect(filter_dropdown.find('.filter-dropdown-item', text: uppercase_label.title)).to be_visible
@@ -101,7 +102,7 @@ describe 'Dropdown label', js: true, feature: true do
clear_search_field
init_label_search
- search_for_label('~bu')
+ filtered_search.send_keys('~bu')
expect(filter_dropdown.find('.filter-dropdown-item', text: bug_label.title)).to be_visible
expect(filter_dropdown.find('.filter-dropdown-item', text: uppercase_label.title)).to be_visible
@@ -180,7 +181,8 @@ describe 'Dropdown label', js: true, feature: true do
click_label(bug_label.title)
expect(page).not_to have_css(js_dropdown_label)
- expect(filtered_search.value).to eq("label:~#{bug_label.title} ")
+ expect_tokens([{ name: 'label', value: "~#{bug_label.title}" }])
+ expect_filtered_search_input_empty
end
it 'fills in the label name when the label is partially filled' do
@@ -188,49 +190,56 @@ describe 'Dropdown label', js: true, feature: true do
click_label(bug_label.title)
expect(page).not_to have_css(js_dropdown_label)
- expect(filtered_search.value).to eq("label:~#{bug_label.title} ")
+ expect_tokens([{ name: 'label', value: "~#{bug_label.title}" }])
+ expect_filtered_search_input_empty
end
it 'fills in the label name that contains multiple words' do
click_label(two_words_label.title)
expect(page).not_to have_css(js_dropdown_label)
- expect(filtered_search.value).to eq("label:~\"#{two_words_label.title}\" ")
+ expect_tokens([{ name: 'label', value: "\"#{two_words_label.title}\"" }])
+ expect_filtered_search_input_empty
end
it 'fills in the label name that contains multiple words and is very long' do
click_label(long_label.title)
expect(page).not_to have_css(js_dropdown_label)
- expect(filtered_search.value).to eq("label:~\"#{long_label.title}\" ")
+ expect_tokens([{ name: 'label', value: "\"#{long_label.title}\"" }])
+ expect_filtered_search_input_empty
end
it 'fills in the label name that contains double quotes' do
click_label(wont_fix_label.title)
expect(page).not_to have_css(js_dropdown_label)
- expect(filtered_search.value).to eq("label:~'#{wont_fix_label.title}' ")
+ expect_tokens([{ name: 'label', value: "~'#{wont_fix_label.title}'" }])
+ expect_filtered_search_input_empty
end
it 'fills in the label name with the correct capitalization' do
click_label(uppercase_label.title)
expect(page).not_to have_css(js_dropdown_label)
- expect(filtered_search.value).to eq("label:~#{uppercase_label.title} ")
+ expect_tokens([{ name: 'label', value: "~#{uppercase_label.title}" }])
+ expect_filtered_search_input_empty
end
it 'fills in the label name with special characters' do
click_label(special_label.title)
expect(page).not_to have_css(js_dropdown_label)
- expect(filtered_search.value).to eq("label:~#{special_label.title} ")
+ expect_tokens([{ name: 'label', value: "~#{special_label.title}" }])
+ expect_filtered_search_input_empty
end
it 'selects `no label`' do
find("#{js_dropdown_label} .filter-dropdown-item", text: 'No Label').click
expect(page).not_to have_css(js_dropdown_label)
- expect(filtered_search.value).to eq("label:none ")
+ expect_tokens([{ name: 'label', value: 'none' }])
+ expect_filtered_search_input_empty
end
end
diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
index 0ce16715b86..0324fcad0a0 100644
--- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
@@ -1,6 +1,7 @@
require 'rails_helper'
describe 'Dropdown milestone', js: true, feature: true do
+ include FilteredSearchHelpers
include WaitForAjax
let!(:project) { create(:empty_project) }
@@ -127,7 +128,8 @@ describe 'Dropdown milestone', js: true, feature: true do
click_milestone(milestone.title)
expect(page).to have_css(js_dropdown_milestone, visible: false)
- expect(filtered_search.value).to eq("milestone:%#{milestone.title} ")
+ expect_tokens([{ name: 'milestone', value: "%#{milestone.title}" }])
+ expect_filtered_search_input_empty
end
it 'fills in the milestone name when the milestone is partially filled' do
@@ -135,56 +137,64 @@ describe 'Dropdown milestone', js: true, feature: true do
click_milestone(milestone.title)
expect(page).to have_css(js_dropdown_milestone, visible: false)
- expect(filtered_search.value).to eq("milestone:%#{milestone.title} ")
+ expect_tokens([{ name: 'milestone', value: "%#{milestone.title}" }])
+ expect_filtered_search_input_empty
end
it 'fills in the milestone name that contains multiple words' do
click_milestone(two_words_milestone.title)
expect(page).to have_css(js_dropdown_milestone, visible: false)
- expect(filtered_search.value).to eq("milestone:%\"#{two_words_milestone.title}\" ")
+ expect_tokens([{ name: 'milestone', value: "%\"#{two_words_milestone.title}\"" }])
+ expect_filtered_search_input_empty
end
it 'fills in the milestone name that contains multiple words and is very long' do
click_milestone(long_milestone.title)
expect(page).to have_css(js_dropdown_milestone, visible: false)
- expect(filtered_search.value).to eq("milestone:%\"#{long_milestone.title}\" ")
+ expect_tokens([{ name: 'milestone', value: "%\"#{long_milestone.title}\"" }])
+ expect_filtered_search_input_empty
end
it 'fills in the milestone name that contains double quotes' do
click_milestone(wont_fix_milestone.title)
expect(page).to have_css(js_dropdown_milestone, visible: false)
- expect(filtered_search.value).to eq("milestone:%'#{wont_fix_milestone.title}' ")
+ expect_tokens([{ name: 'milestone', value: "%'#{wont_fix_milestone.title}'" }])
+ expect_filtered_search_input_empty
end
it 'fills in the milestone name with the correct capitalization' do
click_milestone(uppercase_milestone.title)
expect(page).to have_css(js_dropdown_milestone, visible: false)
- expect(filtered_search.value).to eq("milestone:%#{uppercase_milestone.title} ")
+ expect_tokens([{ name: 'milestone', value: "%#{uppercase_milestone.title}" }])
+ expect_filtered_search_input_empty
end
it 'fills in the milestone name with special characters' do
click_milestone(special_milestone.title)
expect(page).to have_css(js_dropdown_milestone, visible: false)
- expect(filtered_search.value).to eq("milestone:%#{special_milestone.title} ")
+ expect_tokens([{ name: 'milestone', value: "%#{special_milestone.title}" }])
+ expect_filtered_search_input_empty
end
it 'selects `no milestone`' do
click_static_milestone('No Milestone')
expect(page).to have_css(js_dropdown_milestone, visible: false)
- expect(filtered_search.value).to eq("milestone:none ")
+ expect_tokens([{ name: 'milestone', value: 'none' }])
+ expect_filtered_search_input_empty
end
it 'selects `upcoming milestone`' do
click_static_milestone('Upcoming')
expect(page).to have_css(js_dropdown_milestone, visible: false)
- expect(filtered_search.value).to eq("milestone:upcoming ")
+ expect_tokens([{ name: 'milestone', value: 'upcoming' }])
+ expect_filtered_search_input_empty
end
end
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index 0420e64d42c..f079a9627e4 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -1,4 +1,4 @@
-require 'rails_helper'
+require 'spec_helper'
describe 'Filter issues', js: true, feature: true do
include FilteredSearchHelpers
@@ -97,7 +97,9 @@ describe 'Filter issues', js: true, feature: true do
it 'filters issues by searched author' do
input_filtered_search("author:@#{user.username}")
+ expect_tokens([{ name: 'author', value: user.username }])
expect_issues_list_count(5)
+ expect_filtered_search_input_empty
end
it 'filters issues by invalid author' do
@@ -110,36 +112,50 @@ describe 'Filter issues', js: true, feature: true do
end
context 'author with other filters' do
+ let(:search_term) { 'issue' }
+
it 'filters issues by searched author and text' do
- search = "author:@#{user.username} issue"
- input_filtered_search(search)
+ input_filtered_search("author:@#{user.username} #{search_term}")
+ expect_tokens([{ name: 'author', value: user.username }])
expect_issues_list_count(3)
- expect_filtered_search_input(search)
+ expect_filtered_search_input(search_term)
end
it 'filters issues by searched author, assignee and text' do
- search = "author:@#{user.username} assignee:@#{user.username} issue"
- input_filtered_search(search)
+ input_filtered_search("author:@#{user.username} assignee:@#{user.username} #{search_term}")
+ expect_tokens([
+ { name: 'author', value: user.username },
+ { name: 'assignee', value: user.username }
+ ])
expect_issues_list_count(3)
- expect_filtered_search_input(search)
+ expect_filtered_search_input(search_term)
end
it 'filters issues by searched author, assignee, label, and text' do
- search = "author:@#{user.username} assignee:@#{user.username} label:~#{caps_sensitive_label.title} issue"
- input_filtered_search(search)
+ input_filtered_search("author:@#{user.username} assignee:@#{user.username} label:~#{caps_sensitive_label.title} #{search_term}")
+ expect_tokens([
+ { name: 'author', value: user.username },
+ { name: 'assignee', value: user.username },
+ { name: 'label', value: caps_sensitive_label.title }
+ ])
expect_issues_list_count(1)
- expect_filtered_search_input(search)
+ expect_filtered_search_input(search_term)
end
it 'filters issues by searched author, assignee, label, milestone and text' do
- search = "author:@#{user.username} assignee:@#{user.username} label:~#{caps_sensitive_label.title} milestone:%#{milestone.title} issue"
- input_filtered_search(search)
+ input_filtered_search("author:@#{user.username} assignee:@#{user.username} label:~#{caps_sensitive_label.title} milestone:%#{milestone.title} #{search_term}")
+ expect_tokens([
+ { name: 'author', value: user.username },
+ { name: 'assignee', value: user.username },
+ { name: 'label', value: caps_sensitive_label.title },
+ { name: 'milestone', value: milestone.title }
+ ])
expect_issues_list_count(1)
- expect_filtered_search_input(search)
+ expect_filtered_search_input(search_term)
end
end
@@ -151,19 +167,19 @@ describe 'Filter issues', js: true, feature: true do
describe 'filter issues by assignee' do
context 'only assignee' do
it 'filters issues by searched assignee' do
- search = "assignee:@#{user.username}"
- input_filtered_search(search)
+ input_filtered_search("assignee:@#{user.username}")
+ expect_tokens([{ name: 'assignee', value: user.username }])
expect_issues_list_count(5)
- expect_filtered_search_input(search)
+ expect_filtered_search_input_empty
end
it 'filters issues by no assignee' do
- search = "assignee:none"
- input_filtered_search(search)
+ input_filtered_search('assignee:none')
+ expect_tokens([{ name: 'assignee', value: 'none' }])
expect_issues_list_count(8, 1)
- expect_filtered_search_input(search)
+ expect_filtered_search_input_empty
end
it 'filters issues by invalid assignee' do
@@ -176,36 +192,50 @@ describe 'Filter issues', js: true, feature: true do
end
context 'assignee with other filters' do
+ let(:search_term) { 'searchTerm' }
+
it 'filters issues by searched assignee and text' do
- search = "assignee:@#{user.username} searchTerm"
- input_filtered_search(search)
+ input_filtered_search("assignee:@#{user.username} #{search_term}")
+ expect_tokens([{ name: 'assignee', value: user.username }])
expect_issues_list_count(2)
- expect_filtered_search_input(search)
+ expect_filtered_search_input(search_term)
end
it 'filters issues by searched assignee, author and text' do
- search = "assignee:@#{user.username} author:@#{user.username} searchTerm"
- input_filtered_search(search)
+ input_filtered_search("assignee:@#{user.username} author:@#{user.username} #{search_term}")
+ expect_tokens([
+ { name: 'assignee', value: user.username },
+ { name: 'author', value: user.username }
+ ])
expect_issues_list_count(2)
- expect_filtered_search_input(search)
+ expect_filtered_search_input(search_term)
end
it 'filters issues by searched assignee, author, label, text' do
- search = "assignee:@#{user.username} author:@#{user.username} label:~#{caps_sensitive_label.title} searchTerm"
- input_filtered_search(search)
+ input_filtered_search("assignee:@#{user.username} author:@#{user.username} label:~#{caps_sensitive_label.title} #{search_term}")
+ expect_tokens([
+ { name: 'assignee', value: user.username },
+ { name: 'author', value: user.username },
+ { name: 'label', value: caps_sensitive_label.title }
+ ])
expect_issues_list_count(1)
- expect_filtered_search_input(search)
+ expect_filtered_search_input(search_term)
end
it 'filters issues by searched assignee, author, label, milestone and text' do
- search = "assignee:@#{user.username} author:@#{user.username} label:~#{caps_sensitive_label.title} milestone:%#{milestone.title} searchTerm"
- input_filtered_search(search)
+ input_filtered_search("assignee:@#{user.username} author:@#{user.username} label:~#{caps_sensitive_label.title} milestone:%#{milestone.title} #{search_term}")
+ expect_tokens([
+ { name: 'assignee', value: user.username },
+ { name: 'author', value: user.username },
+ { name: 'label', value: caps_sensitive_label.title },
+ { name: 'milestone', value: milestone.title }
+ ])
expect_issues_list_count(1)
- expect_filtered_search_input(search)
+ expect_filtered_search_input(search_term)
end
end
@@ -217,21 +247,23 @@ describe 'Filter issues', js: true, feature: true do
end
describe 'filter issues by label' do
+ let(:search_term) { 'bug' }
+
context 'only label' do
it 'filters issues by searched label' do
- search = "label:~#{bug_label.title}"
- input_filtered_search(search)
+ input_filtered_search("label:~#{bug_label.title}")
+ expect_tokens([{ name: 'label', value: bug_label.title }])
expect_issues_list_count(2)
- expect_filtered_search_input(search)
+ expect_filtered_search_input_empty
end
it 'filters issues by no label' do
- search = "label:none"
- input_filtered_search(search)
+ input_filtered_search('label:none')
+ expect_tokens([{ name: 'label', value: 'none' }])
expect_issues_list_count(9, 1)
- expect_filtered_search_input(search)
+ expect_filtered_search_input_empty
end
it 'filters issues by invalid label' do
@@ -239,11 +271,14 @@ describe 'Filter issues', js: true, feature: true do
end
it 'filters issues by multiple labels' do
- search = "label:~#{bug_label.title} label:~#{caps_sensitive_label.title}"
- input_filtered_search(search)
+ input_filtered_search("label:~#{bug_label.title} label:~#{caps_sensitive_label.title}")
+ expect_tokens([
+ { name: 'label', value: bug_label.title },
+ { name: 'label', value: caps_sensitive_label.title }
+ ])
expect_issues_list_count(1)
- expect_filtered_search_input(search)
+ expect_filtered_search_input_empty
end
it 'filters issues by label containing special characters' do
@@ -251,21 +286,20 @@ describe 'Filter issues', js: true, feature: true do
special_issue = create(:issue, title: "Issue with special character label", project: project)
special_issue.labels << special_label
- search = "label:~#{special_label.title}"
- input_filtered_search(search)
-
+ input_filtered_search("label:~#{special_label.title}")
+ expect_tokens([{ name: 'label', value: special_label.title }])
expect_issues_list_count(1)
- expect_filtered_search_input(search)
+ expect_filtered_search_input_empty
end
it 'does not show issues' do
- new_label = create(:label, project: project, title: "new_label")
+ new_label = create(:label, project: project, title: 'new_label')
- search = "label:~#{new_label.title}"
- input_filtered_search(search)
+ input_filtered_search("label:~#{new_label.title}")
+ expect_tokens([{ name: 'label', value: new_label.title }])
expect_no_issues_list()
- expect_filtered_search_input(search)
+ expect_filtered_search_input_empty
end
end
@@ -275,29 +309,29 @@ describe 'Filter issues', js: true, feature: true do
special_multiple_issue = create(:issue, title: "Issue with special character multiple words label", project: project)
special_multiple_issue.labels << special_multiple_label
- search = "label:~'#{special_multiple_label.title}'"
- input_filtered_search(search)
+ input_filtered_search("label:~'#{special_multiple_label.title}'")
+ # filtered search defaults quotations to double quotes
+ expect_tokens([{ name: 'label', value: "\"#{special_multiple_label.title}\"" }])
expect_issues_list_count(1)
- # filtered search defaults quotations to double quotes
- expect_filtered_search_input("label:~\"#{special_multiple_label.title}\"")
+ expect_filtered_search_input_empty
end
it 'single quotes' do
- search = "label:~'#{multiple_words_label.title}'"
- input_filtered_search(search)
+ input_filtered_search("label:~'#{multiple_words_label.title}'")
+ expect_tokens([{ name: 'label', value: "\"#{multiple_words_label.title}\"" }])
expect_issues_list_count(1)
- expect_filtered_search_input("label:~\"#{multiple_words_label.title}\"")
+ expect_filtered_search_input_empty
end
it 'double quotes' do
- search = "label:~\"#{multiple_words_label.title}\""
- input_filtered_search(search)
+ input_filtered_search("label:~\"#{multiple_words_label.title}\"")
+ expect_tokens([{ name: 'label', value: "\"#{multiple_words_label.title}\"" }])
expect_issues_list_count(1)
- expect_filtered_search_input(search)
+ expect_filtered_search_input_empty
end
it 'single quotes containing double quotes' do
@@ -305,11 +339,11 @@ describe 'Filter issues', js: true, feature: true do
double_quotes_label_issue = create(:issue, title: "Issue with double quotes label", project: project)
double_quotes_label_issue.labels << double_quotes_label
- search = "label:~'#{double_quotes_label.title}'"
- input_filtered_search(search)
+ input_filtered_search("label:~'#{double_quotes_label.title}'")
+ expect_tokens([{ name: 'label', value: "'#{double_quotes_label.title}'" }])
expect_issues_list_count(1)
- expect_filtered_search_input(search)
+ expect_filtered_search_input_empty
end
it 'double quotes containing single quotes' do
@@ -317,86 +351,115 @@ describe 'Filter issues', js: true, feature: true do
single_quotes_label_issue = create(:issue, title: "Issue with single quotes label", project: project)
single_quotes_label_issue.labels << single_quotes_label
- search = "label:~\"#{single_quotes_label.title}\""
- input_filtered_search(search)
+ input_filtered_search("label:~\"#{single_quotes_label.title}\"")
+ expect_tokens([{ name: 'label', value: "\"#{single_quotes_label.title}\"" }])
expect_issues_list_count(1)
- expect_filtered_search_input(search)
+ expect_filtered_search_input_empty
end
end
context 'label with other filters' do
it 'filters issues by searched label and text' do
- search = "label:~#{caps_sensitive_label.title} bug"
- input_filtered_search(search)
+ input_filtered_search("label:~#{caps_sensitive_label.title} #{search_term}")
+ expect_tokens([{ name: 'label', value: caps_sensitive_label.title }])
expect_issues_list_count(1)
- expect_filtered_search_input(search)
+ expect_filtered_search_input(search_term)
end
it 'filters issues by searched label, author and text' do
- search = "label:~#{caps_sensitive_label.title} author:@#{user.username} bug"
- input_filtered_search(search)
+ input_filtered_search("label:~#{caps_sensitive_label.title} author:@#{user.username} #{search_term}")
+ expect_tokens([
+ { name: 'label', value: caps_sensitive_label.title },
+ { name: 'author', value: user.username }
+ ])
expect_issues_list_count(1)
- expect_filtered_search_input(search)
+ expect_filtered_search_input(search_term)
end
it 'filters issues by searched label, author, assignee and text' do
- search = "label:~#{caps_sensitive_label.title} author:@#{user.username} assignee:@#{user.username} bug"
- input_filtered_search(search)
+ input_filtered_search("label:~#{caps_sensitive_label.title} author:@#{user.username} assignee:@#{user.username} #{search_term}")
+ expect_tokens([
+ { name: 'label', value: caps_sensitive_label.title },
+ { name: 'author', value: user.username },
+ { name: 'assignee', value: user.username }
+ ])
expect_issues_list_count(1)
- expect_filtered_search_input(search)
+ expect_filtered_search_input(search_term)
end
it 'filters issues by searched label, author, assignee, milestone and text' do
- search = "label:~#{caps_sensitive_label.title} author:@#{user.username} assignee:@#{user.username} milestone:%#{milestone.title} bug"
- input_filtered_search(search)
+ input_filtered_search("label:~#{caps_sensitive_label.title} author:@#{user.username} assignee:@#{user.username} milestone:%#{milestone.title} #{search_term}")
+ expect_tokens([
+ { name: 'label', value: caps_sensitive_label.title },
+ { name: 'author', value: user.username },
+ { name: 'assignee', value: user.username },
+ { name: 'milestone', value: milestone.title }
+ ])
expect_issues_list_count(1)
- expect_filtered_search_input(search)
+ expect_filtered_search_input(search_term)
end
end
context 'multiple labels with other filters' do
it 'filters issues by searched label, label2, and text' do
- search = "label:~#{bug_label.title} label:~#{caps_sensitive_label.title} bug"
- input_filtered_search(search)
+ input_filtered_search("label:~#{bug_label.title} label:~#{caps_sensitive_label.title} #{search_term}")
+ expect_tokens([
+ { name: 'label', value: bug_label.title },
+ { name: 'label', value: caps_sensitive_label.title }
+ ])
expect_issues_list_count(1)
- expect_filtered_search_input(search)
+ expect_filtered_search_input(search_term)
end
it 'filters issues by searched label, label2, author and text' do
- search = "label:~#{bug_label.title} label:~#{caps_sensitive_label.title} author:@#{user.username} bug"
- input_filtered_search(search)
+ input_filtered_search("label:~#{bug_label.title} label:~#{caps_sensitive_label.title} author:@#{user.username} #{search_term}")
+ expect_tokens([
+ { name: 'label', value: bug_label.title },
+ { name: 'label', value: caps_sensitive_label.title },
+ { name: 'author', value: user.username }
+ ])
expect_issues_list_count(1)
- expect_filtered_search_input(search)
+ expect_filtered_search_input(search_term)
end
it 'filters issues by searched label, label2, author, assignee and text' do
- search = "label:~#{bug_label.title} label:~#{caps_sensitive_label.title} author:@#{user.username} assignee:@#{user.username} bug"
- input_filtered_search(search)
+ input_filtered_search("label:~#{bug_label.title} label:~#{caps_sensitive_label.title} author:@#{user.username} assignee:@#{user.username} #{search_term}")
+ expect_tokens([
+ { name: 'label', value: bug_label.title },
+ { name: 'label', value: caps_sensitive_label.title },
+ { name: 'author', value: user.username },
+ { name: 'assignee', value: user.username }
+ ])
expect_issues_list_count(1)
- expect_filtered_search_input(search)
+ expect_filtered_search_input(search_term)
end
it 'filters issues by searched label, label2, author, assignee, milestone and text' do
- search = "label:~#{bug_label.title} label:~#{caps_sensitive_label.title} author:@#{user.username} assignee:@#{user.username} milestone:%#{milestone.title} bug"
- input_filtered_search(search)
+ input_filtered_search("label:~#{bug_label.title} label:~#{caps_sensitive_label.title} author:@#{user.username} assignee:@#{user.username} milestone:%#{milestone.title} #{search_term}")
+ expect_tokens([
+ { name: 'label', value: bug_label.title },
+ { name: 'label', value: caps_sensitive_label.title },
+ { name: 'author', value: user.username },
+ { name: 'assignee', value: user.username },
+ { name: 'milestone', value: milestone.title }
+ ])
expect_issues_list_count(1)
- expect_filtered_search_input(search)
+ expect_filtered_search_input(search_term)
end
end
context 'issue label clicked' do
before do
find('.issues-list .issue .issue-info a .label', text: multiple_words_label.title).click
- sleep 1
end
it 'filters' do
@@ -404,7 +467,8 @@ describe 'Filter issues', js: true, feature: true do
end
it 'displays in search bar' do
- expect(find('.filtered-search').value).to eq("label:~\"#{multiple_words_label.title}\"")
+ expect_tokens([{ name: 'label', value: "\"#{multiple_words_label.title}\"" }])
+ expect_filtered_search_input_empty
end
end
@@ -420,19 +484,25 @@ describe 'Filter issues', js: true, feature: true do
it 'filters issues by searched milestone' do
input_filtered_search("milestone:%#{milestone.title}")
+ expect_tokens([{ name: 'milestone', value: milestone.title }])
expect_issues_list_count(5)
+ expect_filtered_search_input_empty
end
it 'filters issues by no milestone' do
input_filtered_search("milestone:none")
+ expect_tokens([{ name: 'milestone', value: 'none' }])
expect_issues_list_count(7, 1)
+ expect_filtered_search_input_empty
end
it 'filters issues by upcoming milestones' do
input_filtered_search("milestone:upcoming")
+ expect_tokens([{ name: 'milestone', value: 'upcoming' }])
expect_issues_list_count(1)
+ expect_filtered_search_input_empty
end
it 'filters issues by invalid milestones' do
@@ -447,55 +517,69 @@ describe 'Filter issues', js: true, feature: true do
special_milestone = create(:milestone, title: '!@\#{$%^&*()}', project: project)
create(:issue, title: "Issue with special character milestone", project: project, milestone: special_milestone)
- search = "milestone:%#{special_milestone.title}"
- input_filtered_search(search)
+ input_filtered_search("milestone:%#{special_milestone.title}")
+ expect_tokens([{ name: 'milestone', value: special_milestone.title }])
expect_issues_list_count(1)
- expect_filtered_search_input(search)
+ expect_filtered_search_input_empty
end
it 'does not show issues' do
new_milestone = create(:milestone, title: "new", project: project)
- search = "milestone:%#{new_milestone.title}"
- input_filtered_search(search)
+ input_filtered_search("milestone:%#{new_milestone.title}")
+ expect_tokens([{ name: 'milestone', value: new_milestone.title }])
expect_no_issues_list()
- expect_filtered_search_input(search)
+ expect_filtered_search_input_empty
end
end
context 'milestone with other filters' do
+ let(:search_term) { 'bug' }
+
it 'filters issues by searched milestone and text' do
- search = "milestone:%#{milestone.title} bug"
- input_filtered_search(search)
+ input_filtered_search("milestone:%#{milestone.title} #{search_term}")
+ expect_tokens([{ name: 'milestone', value: milestone.title }])
expect_issues_list_count(2)
- expect_filtered_search_input(search)
+ expect_filtered_search_input(search_term)
end
it 'filters issues by searched milestone, author and text' do
- search = "milestone:%#{milestone.title} author:@#{user.username} bug"
- input_filtered_search(search)
+ input_filtered_search("milestone:%#{milestone.title} author:@#{user.username} #{search_term}")
+ expect_tokens([
+ { name: 'milestone', value: milestone.title },
+ { name: 'author', value: user.username }
+ ])
expect_issues_list_count(2)
- expect_filtered_search_input(search)
+ expect_filtered_search_input(search_term)
end
it 'filters issues by searched milestone, author, assignee and text' do
- search = "milestone:%#{milestone.title} author:@#{user.username} assignee:@#{user.username} bug"
- input_filtered_search(search)
+ input_filtered_search("milestone:%#{milestone.title} author:@#{user.username} assignee:@#{user.username} #{search_term}")
+ expect_tokens([
+ { name: 'milestone', value: milestone.title },
+ { name: 'author', value: user.username },
+ { name: 'assignee', value: user.username }
+ ])
expect_issues_list_count(2)
- expect_filtered_search_input(search)
+ expect_filtered_search_input(search_term)
end
it 'filters issues by searched milestone, author, assignee, label and text' do
- search = "milestone:%#{milestone.title} author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} bug"
- input_filtered_search(search)
-
+ input_filtered_search("milestone:%#{milestone.title} author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} #{search_term}")
+
+ expect_tokens([
+ { name: 'milestone', value: milestone.title },
+ { name: 'author', value: user.username },
+ { name: 'assignee', value: user.username },
+ { name: 'label', value: bug_label.title }
+ ])
expect_issues_list_count(2)
- expect_filtered_search_input(search)
+ expect_filtered_search_input(search_term)
end
end
@@ -506,44 +590,6 @@ describe 'Filter issues', js: true, feature: true do
end
end
- describe 'overwrites selected filter' do
- it 'changes author' do
- input_filtered_search("author:@#{user.username}", submit: false)
-
- select_search_at_index(3)
-
- page.within '#js-dropdown-author' do
- click_button user2.username
- end
-
- expect(filtered_search.value).to eq("author:@#{user2.username} ")
- end
-
- it 'changes label' do
- input_filtered_search("author:@#{user.username} label:~#{bug_label.title}", submit: false)
-
- select_search_at_index(27)
-
- page.within '#js-dropdown-label' do
- click_button label.name
- end
-
- expect(filtered_search.value).to eq("author:@#{user.username} label:~#{label.name} ")
- end
-
- it 'changes label correctly space is in previous label' do
- input_filtered_search("label:~\"#{multiple_words_label.title}\"", submit: false)
-
- select_search_at_index(0)
-
- page.within '#js-dropdown-label' do
- click_button label.name
- end
-
- expect(filtered_search.value).to eq("label:~#{label.name} ")
- end
- end
-
describe 'filter issues by text' do
context 'only text' do
it 'filters issues by searched text' do
@@ -605,80 +651,81 @@ describe 'Filter issues', js: true, feature: true do
context 'searched text with other filters' do
it 'filters issues by searched text and author' do
+ # After searching, all search terms are placed at the end
input_filtered_search("bug author:@#{user.username}")
expect_issues_list_count(2)
- expect_filtered_search_input("author:@#{user.username} bug")
+ expect_filtered_search_input('bug')
end
it 'filters issues by searched text, author and more text' do
input_filtered_search("bug author:@#{user.username} report")
expect_issues_list_count(1)
- expect_filtered_search_input("author:@#{user.username} bug report")
+ expect_filtered_search_input('bug report')
end
it 'filters issues by searched text, author and assignee' do
input_filtered_search("bug author:@#{user.username} assignee:@#{user.username}")
expect_issues_list_count(2)
- expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} bug")
+ expect_filtered_search_input('bug')
end
it 'filters issues by searched text, author, more text and assignee' do
input_filtered_search("bug author:@#{user.username} report assignee:@#{user.username}")
expect_issues_list_count(1)
- expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} bug report")
+ expect_filtered_search_input('bug report')
end
it 'filters issues by searched text, author, more text, assignee and even more text' do
input_filtered_search("bug author:@#{user.username} report assignee:@#{user.username} with")
expect_issues_list_count(1)
- expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} bug report with")
+ expect_filtered_search_input('bug report with')
end
it 'filters issues by searched text, author, assignee and label' do
input_filtered_search("bug author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title}")
expect_issues_list_count(2)
- expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} bug")
+ expect_filtered_search_input('bug')
end
it 'filters issues by searched text, author, text, assignee, text, label and text' do
input_filtered_search("bug author:@#{user.username} report assignee:@#{user.username} with label:~#{bug_label.title} everything")
expect_issues_list_count(1)
- expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} bug report with everything")
+ expect_filtered_search_input('bug report with everything')
end
it 'filters issues by searched text, author, assignee, label and milestone' do
input_filtered_search("bug author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} milestone:%#{milestone.title}")
expect_issues_list_count(2)
- expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} milestone:%#{milestone.title} bug")
+ expect_filtered_search_input('bug')
end
it 'filters issues by searched text, author, text, assignee, text, label, text, milestone and text' do
input_filtered_search("bug author:@#{user.username} report assignee:@#{user.username} with label:~#{bug_label.title} everything milestone:%#{milestone.title} you")
expect_issues_list_count(1)
- expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} milestone:%#{milestone.title} bug report with everything you")
+ expect_filtered_search_input('bug report with everything you')
end
it 'filters issues by searched text, author, assignee, multiple labels and milestone' do
input_filtered_search("bug author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} label:~#{caps_sensitive_label.title} milestone:%#{milestone.title}")
expect_issues_list_count(1)
- expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} label:~#{caps_sensitive_label.title} milestone:%#{milestone.title} bug")
+ expect_filtered_search_input('bug')
end
it 'filters issues by searched text, author, text, assignee, text, label1, text, label2, text, milestone and text' do
input_filtered_search("bug author:@#{user.username} report assignee:@#{user.username} with label:~#{bug_label.title} everything label:~#{caps_sensitive_label.title} you milestone:%#{milestone.title} thought")
expect_issues_list_count(1)
- expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} label:~#{caps_sensitive_label.title} milestone:%#{milestone.title} bug report with everything you thought")
+ expect_filtered_search_input('bug report with everything you thought')
end
end
@@ -717,8 +764,8 @@ describe 'Filter issues', js: true, feature: true do
before do
input_filtered_search('bug')
- # Wait for search results to load
- sleep 2
+ # This ensures that the search is performed
+ expect_issues_list_count(4, 1)
end
it 'open state' do
diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb
index 90eb60eb337..59244d65eec 100644
--- a/spec/features/issues/filtered_search/search_bar_spec.rb
+++ b/spec/features/issues/filtered_search/search_bar_spec.rb
@@ -1,6 +1,7 @@
require 'rails_helper'
describe 'Search bar', js: true, feature: true do
+ include FilteredSearchHelpers
include WaitForAjax
let!(:project) { create(:empty_project) }
@@ -32,7 +33,8 @@ describe 'Search bar', js: true, feature: true do
it 'selects item' do
filtered_search.native.send_keys(:down, :down, :enter)
- expect(filtered_search.value).to eq('author:')
+ expect_tokens([{ name: 'author' }])
+ expect_filtered_search_input_empty
end
end
diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb
new file mode 100644
index 00000000000..a78dbaf53ed
--- /dev/null
+++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb
@@ -0,0 +1,322 @@
+require 'rails_helper'
+
+describe 'Visual tokens', js: true, feature: true do
+ include FilteredSearchHelpers
+
+ let!(:project) { create(:empty_project) }
+ let!(:user) { create(:user, name: 'administrator', username: 'root') }
+ let!(:user_rock) { create(:user, name: 'The Rock', username: 'rock') }
+ let!(:milestone_nine) { create(:milestone, title: '9.0', project: project) }
+ let!(:milestone_ten) { create(:milestone, title: '10.0', project: project) }
+ let!(:label) { create(:label, project: project, title: 'abc') }
+ let!(:cc_label) { create(:label, project: project, title: 'Community Contribution') }
+
+ let(:filtered_search) { find('.filtered-search') }
+ let(:filter_author_dropdown) { find("#js-dropdown-author .filter-dropdown") }
+ let(:filter_assignee_dropdown) { find("#js-dropdown-assignee .filter-dropdown") }
+ let(:filter_milestone_dropdown) { find("#js-dropdown-milestone .filter-dropdown") }
+ let(:filter_label_dropdown) { find("#js-dropdown-label .filter-dropdown") }
+
+ def is_input_focused
+ page.evaluate_script("document.activeElement.classList.contains('filtered-search')")
+ end
+
+ before do
+ project.add_user(user, :master)
+ project.add_user(user_rock, :master)
+ login_as(user)
+ create(:issue, project: project)
+
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ describe 'editing author token' do
+ before do
+ input_filtered_search('author:@root assignee:none', submit: false)
+ first('.tokens-container .filtered-search-token').double_click
+ end
+
+ it 'opens author dropdown' do
+ expect(page).to have_css('#js-dropdown-author', visible: true)
+ end
+
+ it 'makes value editable' do
+ expect_filtered_search_input('@root')
+ end
+
+ it 'filters value' do
+ filtered_search.send_keys(:backspace)
+
+ expect(page).to have_css('#js-dropdown-author .filter-dropdown .filter-dropdown-item', count: 1)
+ end
+
+ it 'ends editing mode when document is clicked' do
+ find('#content-body').click
+
+ expect_filtered_search_input_empty
+ expect(page).to have_css('#js-dropdown-author', visible: false)
+ end
+
+ it 'ends editing mode when scroll container is clicked' do
+ find('.scroll-container').click
+
+ expect_filtered_search_input_empty
+ expect(page).to have_css('#js-dropdown-author', visible: false)
+ end
+
+ describe 'selecting different author from dropdown' do
+ before do
+ filter_author_dropdown.find('.filter-dropdown-item .dropdown-light-content', text: "@#{user_rock.username}").click
+ end
+
+ it 'changes value in visual token' do
+ expect(first('.tokens-container .filtered-search-token .value').text).to eq("@#{user_rock.username}")
+ end
+
+ it 'moves input to the right' do
+ expect(is_input_focused).to eq(true)
+ end
+ end
+ end
+
+ describe 'editing assignee token' do
+ before do
+ input_filtered_search('assignee:@root author:none', submit: false)
+ first('.tokens-container .filtered-search-token').double_click
+ end
+
+ it 'opens assignee dropdown' do
+ expect(page).to have_css('#js-dropdown-assignee', visible: true)
+ end
+
+ it 'makes value editable' do
+ expect_filtered_search_input('@root')
+ end
+
+ it 'filters value' do
+ filtered_search.send_keys(:backspace)
+
+ expect(page).to have_css('#js-dropdown-assignee .filter-dropdown .filter-dropdown-item', count: 1)
+ end
+
+ it 'ends editing mode when document is clicked' do
+ find('#content-body').click
+
+ expect_filtered_search_input_empty
+ expect(page).to have_css('#js-dropdown-assignee', visible: false)
+ end
+
+ it 'ends editing mode when scroll container is clicked' do
+ find('.scroll-container').click
+
+ expect_filtered_search_input_empty
+ expect(page).to have_css('#js-dropdown-assignee', visible: false)
+ end
+
+ describe 'selecting static option from dropdown' do
+ before do
+ find("#js-dropdown-assignee").find('.filter-dropdown-item', text: 'No Assignee').click
+ end
+
+ it 'changes value in visual token' do
+ expect(first('.tokens-container .filtered-search-token .value').text).to eq('none')
+ end
+
+ it 'moves input to the right' do
+ expect(is_input_focused).to eq(true)
+ end
+ end
+ end
+
+ describe 'editing milestone token' do
+ before do
+ input_filtered_search('milestone:%10.0 author:none', submit: false)
+ first('.tokens-container .filtered-search-token').double_click
+ first('#js-dropdown-milestone .filter-dropdown .filter-dropdown-item')
+ end
+
+ it 'opens milestone dropdown' do
+ expect(filter_milestone_dropdown.find('.filter-dropdown-item', text: milestone_ten.title)).to be_visible
+ expect(filter_milestone_dropdown.find('.filter-dropdown-item', text: milestone_nine.title)).to be_visible
+ expect(page).to have_css('#js-dropdown-milestone', visible: true)
+ end
+
+ it 'selects static option from dropdown' do
+ find("#js-dropdown-milestone").find('.filter-dropdown-item', text: 'Upcoming').click
+
+ expect(first('.tokens-container .filtered-search-token .value').text).to eq('upcoming')
+ expect(is_input_focused).to eq(true)
+ end
+
+ it 'makes value editable' do
+ expect_filtered_search_input('%10.0')
+ end
+
+ it 'filters value' do
+ filtered_search.send_keys(:backspace)
+
+ expect(page).to have_css('#js-dropdown-milestone .filter-dropdown .filter-dropdown-item', count: 1)
+ end
+
+ it 'ends editing mode when document is clicked' do
+ find('#content-body').click
+
+ expect_filtered_search_input_empty
+ expect(page).to have_css('#js-dropdown-milestone', visible: false)
+ end
+
+ it 'ends editing mode when scroll container is clicked' do
+ find('.scroll-container').click
+
+ expect_filtered_search_input_empty
+ expect(page).to have_css('#js-dropdown-milestone', visible: false)
+ end
+ end
+
+ describe 'editing label token' do
+ before do
+ input_filtered_search("label:~#{label.title} author:none", submit: false)
+ first('.tokens-container .filtered-search-token').double_click
+ first('#js-dropdown-label .filter-dropdown .filter-dropdown-item')
+ end
+
+ it 'opens label dropdown' do
+ expect(filter_label_dropdown.find('.filter-dropdown-item', text: label.title)).to be_visible
+ expect(filter_label_dropdown.find('.filter-dropdown-item', text: cc_label.title)).to be_visible
+ expect(page).to have_css('#js-dropdown-label', visible: true)
+ end
+
+ it 'selects option from dropdown' do
+ expect(filter_label_dropdown.find('.filter-dropdown-item', text: label.title)).to be_visible
+ expect(filter_label_dropdown.find('.filter-dropdown-item', text: cc_label.title)).to be_visible
+
+ find("#js-dropdown-label").find('.filter-dropdown-item', text: cc_label.title).click
+
+ expect(first('.tokens-container .filtered-search-token .value').text).to eq("~\"#{cc_label.title}\"")
+ expect(is_input_focused).to eq(true)
+ end
+
+ it 'makes value editable' do
+ expect_filtered_search_input("~#{label.title}")
+ end
+
+ it 'filters value' do
+ expect(filter_label_dropdown.find('.filter-dropdown-item', text: label.title)).to be_visible
+ expect(filter_label_dropdown.find('.filter-dropdown-item', text: cc_label.title)).to be_visible
+
+ filtered_search.send_keys(:backspace)
+
+ filter_label_dropdown.find('.filter-dropdown-item')
+
+ expect(page.all('#js-dropdown-label .filter-dropdown .filter-dropdown-item').size).to eq(1)
+ end
+
+ it 'ends editing mode when document is clicked' do
+ find('#content-body').click
+
+ expect_filtered_search_input_empty
+ expect(page).to have_css('#js-dropdown-label', visible: false)
+ end
+
+ it 'ends editing mode when scroll container is clicked' do
+ find('.scroll-container').click
+
+ expect_filtered_search_input_empty
+ expect(page).to have_css('#js-dropdown-label', visible: false)
+ end
+ end
+
+ describe 'editing multiple tokens' do
+ before do
+ input_filtered_search('author:@root assignee:none', submit: false)
+ first('.tokens-container .filtered-search-token').double_click
+ end
+
+ it 'opens author dropdown' do
+ expect(page).to have_css('#js-dropdown-author', visible: true)
+ end
+
+ it 'opens assignee dropdown' do
+ find('.tokens-container .filtered-search-token', text: 'Assignee').double_click
+ expect(page).to have_css('#js-dropdown-assignee', visible: true)
+ end
+ end
+
+ describe 'add new token after editing existing token' do
+ before do
+ input_filtered_search('author:@root assignee:none', submit: false)
+ first('.tokens-container .filtered-search-token').double_click
+ filtered_search.send_keys(' ')
+ end
+
+ describe 'opens dropdowns' do
+ it 'opens hint dropdown' do
+ expect(page).to have_css('#js-dropdown-hint', visible: true)
+ end
+
+ it 'opens author dropdown' do
+ filtered_search.send_keys('author:')
+ expect(page).to have_css('#js-dropdown-author', visible: true)
+ end
+
+ it 'opens assignee dropdown' do
+ filtered_search.send_keys('assignee:')
+ expect(page).to have_css('#js-dropdown-assignee', visible: true)
+ end
+
+ it 'opens milestone dropdown' do
+ filtered_search.send_keys('milestone:')
+ expect(page).to have_css('#js-dropdown-milestone', visible: true)
+ end
+
+ it 'opens label dropdown' do
+ filtered_search.send_keys('label:')
+ expect(page).to have_css('#js-dropdown-label', visible: true)
+ end
+ end
+
+ describe 'creates visual tokens' do
+ it 'creates author token' do
+ filtered_search.send_keys('author:@thomas ')
+ token = page.all('.tokens-container .filtered-search-token')[1]
+
+ expect(token.find('.name').text).to eq('Author')
+ expect(token.find('.value').text).to eq('@thomas')
+ end
+
+ it 'creates assignee token' do
+ filtered_search.send_keys('assignee:@thomas ')
+ token = page.all('.tokens-container .filtered-search-token')[1]
+
+ expect(token.find('.name').text).to eq('Assignee')
+ expect(token.find('.value').text).to eq('@thomas')
+ end
+
+ it 'creates milestone token' do
+ filtered_search.send_keys('milestone:none ')
+ token = page.all('.tokens-container .filtered-search-token')[1]
+
+ expect(token.find('.name').text).to eq('Milestone')
+ expect(token.find('.value').text).to eq('none')
+ end
+
+ it 'creates label token' do
+ filtered_search.send_keys('label:~Backend ')
+ token = page.all('.tokens-container .filtered-search-token')[1]
+
+ expect(token.find('.name').text).to eq('Label')
+ expect(token.find('.value').text).to eq('~Backend')
+ end
+ end
+
+ it 'does not tokenize incomplete token' do
+ filtered_search.send_keys('author:')
+
+ find('#content-body').click
+ token = page.all('.tokens-container .js-visual-token')[1]
+
+ expect_filtered_search_input_empty
+ expect(token.find('.name').text).to eq('Author')
+ end
+ end
+end
diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb
index 741ca95f1ca..d4e0ef91856 100644
--- a/spec/features/issues/form_spec.rb
+++ b/spec/features/issues/form_spec.rb
@@ -3,6 +3,7 @@ require 'rails_helper'
describe 'New/edit issue', feature: true, js: true do
let!(:project) { create(:project) }
let!(:user) { create(:user)}
+ let!(:user2) { create(:user)}
let!(:milestone) { create(:milestone, project: project) }
let!(:label) { create(:label, project: project) }
let!(:label2) { create(:label, project: project) }
@@ -10,6 +11,7 @@ describe 'New/edit issue', feature: true, js: true do
before do
project.team << [user, :master]
+ project.team << [user2, :master]
login_as(user)
end
@@ -22,14 +24,23 @@ describe 'New/edit issue', feature: true, js: true do
fill_in 'issue_title', with: 'title'
fill_in 'issue_description', with: 'title'
+ expect(find('a', text: 'Assign to me')).to be_visible
click_button 'Assignee'
page.within '.dropdown-menu-user' do
- click_link user.name
+ click_link user2.name
end
+ expect(find('input[name="issue[assignee_id]"]', visible: false).value).to match(user2.id.to_s)
+ page.within '.js-assignee-search' do
+ expect(page).to have_content user2.name
+ end
+ expect(find('a', text: 'Assign to me')).to be_visible
+
+ click_link 'Assign to me'
expect(find('input[name="issue[assignee_id]"]', visible: false).value).to match(user.id.to_s)
page.within '.js-assignee-search' do
expect(page).to have_content user.name
end
+ expect(find('a', text: 'Assign to me', visible: false)).not_to be_visible
click_button 'Milestone'
page.within '.issue-milestone' do
@@ -94,6 +105,7 @@ describe 'New/edit issue', feature: true, js: true do
it 'allows user to update issue' do
expect(find('input[name="issue[assignee_id]"]', visible: false).value).to match(user.id.to_s)
expect(find('input[name="issue[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
+ expect(find('a', text: 'Assign to me', visible: false)).not_to be_visible
page.within '.js-user-search' do
expect(page).to have_content user.name
diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb
index 5608cda28f8..265a0cfc198 100644
--- a/spec/features/merge_requests/filter_by_milestone_spec.rb
+++ b/spec/features/merge_requests/filter_by_milestone_spec.rb
@@ -25,6 +25,9 @@ feature 'Merge Request filtering by Milestone', feature: true do
visit_merge_requests(project)
input_filtered_search('milestone:none')
+ expect_tokens([{ name: 'milestone', value: 'none' }])
+ expect_filtered_search_input_empty
+
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
expect(page).to have_css('.merge-request', count: 1)
end
diff --git a/spec/features/merge_requests/filter_merge_requests_spec.rb b/spec/features/merge_requests/filter_merge_requests_spec.rb
index 6579a88d4ab..70e3997e716 100644
--- a/spec/features/merge_requests/filter_merge_requests_spec.rb
+++ b/spec/features/merge_requests/filter_merge_requests_spec.rb
@@ -24,6 +24,11 @@ describe 'Filter merge requests', feature: true do
describe 'for assignee from mr#index' do
let(:search_query) { "assignee:@#{user.username}" }
+ def expect_assignee_visual_tokens
+ expect_tokens([{ name: 'assignee', value: "@#{user.username}" }])
+ expect_filtered_search_input_empty
+ end
+
before do
input_filtered_search(search_query)
@@ -32,25 +37,30 @@ describe 'Filter merge requests', feature: true do
context 'assignee', js: true do
it 'updates to current user' do
- expect_filtered_search_input(search_query)
+ expect_assignee_visual_tokens()
end
it 'does not change when closed link is clicked' do
find('.issues-state-filters a', text: "Closed").click
- expect_filtered_search_input(search_query)
+ expect_assignee_visual_tokens()
end
it 'does not change when all link is clicked' do
find('.issues-state-filters a', text: "All").click
- expect_filtered_search_input(search_query)
+ expect_assignee_visual_tokens()
end
end
end
describe 'for milestone from mr#index' do
- let(:search_query) { "milestone:%#{milestone.title}" }
+ let(:search_query) { "milestone:%\"#{milestone.title}\"" }
+
+ def expect_milestone_visual_tokens
+ expect_tokens([{ name: 'milestone', value: "%\"#{milestone.title}\"" }])
+ expect_filtered_search_input_empty
+ end
before do
input_filtered_search(search_query)
@@ -60,19 +70,19 @@ describe 'Filter merge requests', feature: true do
context 'milestone', js: true do
it 'updates to current milestone' do
- expect_filtered_search_input(search_query)
+ expect_milestone_visual_tokens()
end
it 'does not change when closed link is clicked' do
find('.issues-state-filters a', text: "Closed").click
- expect_filtered_search_input(search_query)
+ expect_milestone_visual_tokens()
end
it 'does not change when all link is clicked' do
find('.issues-state-filters a', text: "All").click
- expect_filtered_search_input(search_query)
+ expect_milestone_visual_tokens()
end
end
end
@@ -82,35 +92,44 @@ describe 'Filter merge requests', feature: true do
input_filtered_search('label:none')
expect_mr_list_count(1)
- expect_filtered_search_input('label:none')
+ expect_tokens([{ name: 'label', value: 'none' }])
+ expect_filtered_search_input_empty
end
it 'filters by a label' do
input_filtered_search("label:~#{label.title}")
expect_mr_list_count(0)
- expect_filtered_search_input("label:~#{label.title}")
+ expect_tokens([{ name: 'label', value: "~#{label.title}" }])
+ expect_filtered_search_input_empty
end
it "filters by `won't fix` and another label" do
input_filtered_search("label:~\"#{wontfix.title}\" label:~#{label.title}")
expect_mr_list_count(0)
- expect_filtered_search_input("label:~\"#{wontfix.title}\" label:~#{label.title}")
+ expect_tokens([
+ { name: 'label', value: "~\"#{wontfix.title}\"" },
+ { name: 'label', value: "~#{label.title}" }
+ ])
+ expect_filtered_search_input_empty
end
it "filters by `won't fix` label followed by another label after page load" do
input_filtered_search("label:~\"#{wontfix.title}\"")
expect_mr_list_count(0)
- expect_filtered_search_input("label:~\"#{wontfix.title}\"")
-
- input_filtered_search_keys(" label:~#{label.title}")
+ expect_tokens([{ name: 'label', value: "~\"#{wontfix.title}\"" }])
+ expect_filtered_search_input_empty
- expect_filtered_search_input("label:~\"#{wontfix.title}\" label:~#{label.title}")
+ input_filtered_search_keys("label:~#{label.title}")
expect_mr_list_count(0)
- expect_filtered_search_input("label:~\"#{wontfix.title}\" label:~#{label.title}")
+ expect_tokens([
+ { name: 'label', value: "~\"#{wontfix.title}\"" },
+ { name: 'label', value: "~#{label.title}" }
+ ])
+ expect_filtered_search_input_empty
end
end
@@ -121,9 +140,10 @@ describe 'Filter merge requests', feature: true do
input_filtered_search("assignee:@#{user.username}")
expect_mr_list_count(1)
- expect_filtered_search_input("assignee:@#{user.username}")
+ expect_tokens([{ name: 'assignee', value: "@#{user.username}" }])
+ expect_filtered_search_input_empty
- input_filtered_search_keys(" label:~#{label.title}")
+ input_filtered_search_keys("label:~#{label.title} ")
expect_mr_list_count(1)
@@ -131,20 +151,28 @@ describe 'Filter merge requests', feature: true do
end
context 'assignee and label', js: true do
+ def expect_assignee_label_visual_tokens
+ expect_tokens([
+ { name: 'assignee', value: "@#{user.username}" },
+ { name: 'label', value: "~#{label.title}" }
+ ])
+ expect_filtered_search_input_empty
+ end
+
it 'updates to current assignee and label' do
- expect_filtered_search_input(search_query)
+ expect_assignee_label_visual_tokens()
end
it 'does not change when closed link is clicked' do
find('.issues-state-filters a', text: "Closed").click
- expect_filtered_search_input(search_query)
+ expect_assignee_label_visual_tokens()
end
it 'does not change when all link is clicked' do
find('.issues-state-filters a', text: "All").click
- expect_filtered_search_input(search_query)
+ expect_assignee_label_visual_tokens()
end
end
end
@@ -195,6 +223,8 @@ describe 'Filter merge requests', feature: true do
input_filtered_search_keys(' label:~bug')
expect_mr_list_count(1)
+ expect_tokens([{ name: 'label', value: '~bug' }])
+ expect_filtered_search_input('Bug')
end
it 'filters by text and milestone' do
@@ -206,6 +236,8 @@ describe 'Filter merge requests', feature: true do
input_filtered_search_keys(' milestone:%8')
expect_mr_list_count(1)
+ expect_tokens([{ name: 'milestone', value: '%8' }])
+ expect_filtered_search_input('Bug')
end
it 'filters by text and assignee' do
@@ -217,6 +249,8 @@ describe 'Filter merge requests', feature: true do
input_filtered_search_keys(" assignee:@#{user.username}")
expect_mr_list_count(1)
+ expect_tokens([{ name: 'assignee', value: "@#{user.username}" }])
+ expect_filtered_search_input('Bug')
end
it 'filters by text and author' do
@@ -228,6 +262,8 @@ describe 'Filter merge requests', feature: true do
input_filtered_search_keys(" author:@#{user.username}")
expect_mr_list_count(1)
+ expect_tokens([{ name: 'author', value: "@#{user.username}" }])
+ expect_filtered_search_input('Bug')
end
end
end
@@ -266,7 +302,8 @@ describe 'Filter merge requests', feature: true do
it 'filter by current user' do
visit namespace_project_merge_requests_path(project.namespace, project, assignee_id: user.id)
- expect_filtered_search_input("assignee:@#{user.username}")
+ expect_tokens([{ name: 'assignee', value: "@#{user.username}" }])
+ expect_filtered_search_input_empty
end
it 'filter by new user' do
@@ -275,7 +312,8 @@ describe 'Filter merge requests', feature: true do
visit namespace_project_merge_requests_path(project.namespace, project, assignee_id: new_user.id)
- expect_filtered_search_input("assignee:@#{new_user.username}")
+ expect_tokens([{ name: 'assignee', value: "@#{new_user.username}" }])
+ expect_filtered_search_input_empty
end
end
@@ -283,7 +321,8 @@ describe 'Filter merge requests', feature: true do
it 'filter by current user' do
visit namespace_project_merge_requests_path(project.namespace, project, author_id: user.id)
- expect_filtered_search_input("author:@#{user.username}")
+ expect_tokens([{ name: 'author', value: "@#{user.username}" }])
+ expect_filtered_search_input_empty
end
it 'filter by new user' do
@@ -292,7 +331,8 @@ describe 'Filter merge requests', feature: true do
visit namespace_project_merge_requests_path(project.namespace, project, author_id: new_user.id)
- expect_filtered_search_input("author:@#{new_user.username}")
+ expect_tokens([{ name: 'author', value: "@#{new_user.username}" }])
+ expect_filtered_search_input_empty
end
end
end
diff --git a/spec/features/merge_requests/form_spec.rb b/spec/features/merge_requests/form_spec.rb
index 7594cbf54e8..1ecdb8b5983 100644
--- a/spec/features/merge_requests/form_spec.rb
+++ b/spec/features/merge_requests/form_spec.rb
@@ -4,12 +4,14 @@ describe 'New/edit merge request', feature: true, js: true do
let!(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
let(:fork_project) { create(:project, forked_from_project: project) }
let!(:user) { create(:user)}
+ let!(:user2) { create(:user)}
let!(:milestone) { create(:milestone, project: project) }
let!(:label) { create(:label, project: project) }
let!(:label2) { create(:label, project: project) }
before do
project.team << [user, :master]
+ project.team << [user2, :master]
end
context 'owned projects' do
@@ -33,8 +35,14 @@ describe 'New/edit merge request', feature: true, js: true do
it 'creates new merge request' do
click_button 'Assignee'
page.within '.dropdown-menu-user' do
- click_link user.name
+ click_link user2.name
+ end
+ expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user2.id.to_s)
+ page.within '.js-assignee-search' do
+ expect(page).to have_content user2.name
end
+
+ click_link 'Assign to me'
expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s)
page.within '.js-assignee-search' do
expect(page).to have_content user.name
diff --git a/spec/features/merge_requests/reset_filters_spec.rb b/spec/features/merge_requests/reset_filters_spec.rb
index 58f11499e3f..6fed1568fcf 100644
--- a/spec/features/merge_requests/reset_filters_spec.rb
+++ b/spec/features/merge_requests/reset_filters_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Issues filter reset button', feature: true, js: true do
+feature 'Merge requests filter clear button', feature: true, js: true do
include FilteredSearchHelpers
include MergeRequestHelpers
include WaitForAjax
@@ -24,67 +24,93 @@ feature 'Issues filter reset button', feature: true, js: true do
context 'when a milestone filter has been applied' do
it 'resets the milestone filter' do
visit_merge_requests(project, milestone_title: milestone.title)
+
expect(page).to have_css(merge_request_css, count: 1)
+ expect(get_filtered_search_placeholder).to eq('')
reset_filters
+
expect(page).to have_css(merge_request_css, count: 2)
+ expect(get_filtered_search_placeholder).to eq(default_placeholder)
end
end
context 'when a label filter has been applied' do
it 'resets the label filter' do
visit_merge_requests(project, label_name: bug.name)
+
expect(page).to have_css(merge_request_css, count: 1)
+ expect(get_filtered_search_placeholder).to eq('')
reset_filters
+
expect(page).to have_css(merge_request_css, count: 2)
+ expect(get_filtered_search_placeholder).to eq(default_placeholder)
end
end
context 'when a text search has been conducted' do
it 'resets the text search filter' do
visit_merge_requests(project, search: 'Bug')
+
expect(page).to have_css(merge_request_css, count: 1)
+ expect(get_filtered_search_placeholder).to eq('')
reset_filters
+
expect(page).to have_css(merge_request_css, count: 2)
+ expect(get_filtered_search_placeholder).to eq(default_placeholder)
end
end
context 'when author filter has been applied' do
it 'resets the author filter' do
visit_merge_requests(project, author_username: user.username)
+
expect(page).to have_css(merge_request_css, count: 1)
+ expect(get_filtered_search_placeholder).to eq('')
reset_filters
+
expect(page).to have_css(merge_request_css, count: 2)
+ expect(get_filtered_search_placeholder).to eq(default_placeholder)
end
end
context 'when assignee filter has been applied' do
it 'resets the assignee filter' do
visit_merge_requests(project, assignee_username: user.username)
+
expect(page).to have_css(merge_request_css, count: 1)
+ expect(get_filtered_search_placeholder).to eq('')
reset_filters
+
expect(page).to have_css(merge_request_css, count: 2)
+ expect(get_filtered_search_placeholder).to eq(default_placeholder)
end
end
context 'when all filters have been applied' do
- it 'resets all filters' do
+ it 'clears all filters' do
visit_merge_requests(project, assignee_username: user.username, author_username: user.username, milestone_title: milestone.title, label_name: bug.name, search: 'Bug')
+
expect(page).to have_css(merge_request_css, count: 0)
+ expect(get_filtered_search_placeholder).to eq('')
reset_filters
+
expect(page).to have_css(merge_request_css, count: 2)
+ expect(get_filtered_search_placeholder).to eq(default_placeholder)
end
end
context 'when no filters have been applied' do
- it 'the reset link should not be visible' do
+ it 'the clear button should not be visible' do
visit_merge_requests(project)
+
expect(page).to have_css(merge_request_css, count: 2)
+ expect(get_filtered_search_placeholder).to eq(default_placeholder)
expect(page).not_to have_css(clear_search_css)
end
end
diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb
index b575aeff0d8..c2db7d8da3c 100644
--- a/spec/features/merge_requests/widget_spec.rb
+++ b/spec/features/merge_requests/widget_spec.rb
@@ -37,7 +37,12 @@ describe 'Merge request', :feature, :js do
context 'view merge request' do
let!(:environment) { create(:environment, project: project) }
- let!(:deployment) { create(:deployment, environment: environment, ref: 'feature', sha: merge_request.diff_head_sha) }
+
+ let!(:deployment) do
+ create(:deployment, environment: environment,
+ ref: 'feature',
+ sha: merge_request.diff_head_sha)
+ end
before do
visit namespace_project_merge_request_path(project.namespace, project, merge_request)
@@ -96,6 +101,26 @@ describe 'Merge request', :feature, :js do
end
end
+ context 'when merge request is in the blocked pipeline state' do
+ before do
+ create(:ci_pipeline, project: project,
+ sha: merge_request.diff_head_sha,
+ ref: merge_request.source_branch,
+ status: :manual)
+
+ visit namespace_project_merge_request_path(project.namespace,
+ project,
+ merge_request)
+ end
+
+ it 'shows information about blocked pipeline' do
+ expect(page).to have_content("Pipeline blocked")
+ expect(page).to have_content(
+ "The pipeline for this merge request requires a manual action")
+ expect(page).to have_css('.ci-status-icon-manual')
+ end
+ end
+
context 'view merge request with MWBS button' do
before do
commit_status = create(:commit_status, project: project, status: 'pending')
diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb
index eb7b8a24669..0917d4dc3ef 100644
--- a/spec/features/profiles/personal_access_tokens_spec.rb
+++ b/spec/features/profiles/personal_access_tokens_spec.rb
@@ -4,11 +4,11 @@ describe 'Profile > Personal Access Tokens', feature: true, js: true do
let(:user) { create(:user) }
def active_personal_access_tokens
- find(".table.active-personal-access-tokens")
+ find(".table.active-tokens")
end
def inactive_personal_access_tokens
- find(".table.inactive-personal-access-tokens")
+ find(".table.inactive-tokens")
end
def created_personal_access_token
@@ -26,7 +26,7 @@ describe 'Profile > Personal Access Tokens', feature: true, js: true do
end
describe "token creation" do
- it "allows creation of a token" do
+ it "allows creation of a personal access token" do
name = FFaker::Product.brand
visit profile_personal_access_tokens_path
@@ -43,7 +43,7 @@ describe 'Profile > Personal Access Tokens', feature: true, js: true do
click_on "Create Personal Access Token"
expect(active_personal_access_tokens).to have_text(name)
- expect(active_personal_access_tokens).to have_text(Date.today.next_month.at_beginning_of_month.to_s(:medium))
+ expect(active_personal_access_tokens).to have_text('In')
expect(active_personal_access_tokens).to have_text('api')
expect(active_personal_access_tokens).to have_text('read_user')
end
@@ -60,6 +60,18 @@ describe 'Profile > Personal Access Tokens', feature: true, js: true do
end
end
+ describe 'active tokens' do
+ let!(:impersonation_token) { create(:personal_access_token, :impersonation, user: user) }
+ let!(:personal_access_token) { create(:personal_access_token, user: user) }
+
+ it 'only shows personal access tokens' do
+ visit profile_personal_access_tokens_path
+
+ expect(active_personal_access_tokens).to have_text(personal_access_token.name)
+ expect(active_personal_access_tokens).not_to have_text(impersonation_token.name)
+ end
+ end
+
describe "inactive tokens" do
let!(:personal_access_token) { create(:personal_access_token, user: user) }
diff --git a/spec/features/projects/environments/environment_metrics_spec.rb b/spec/features/projects/environments/environment_metrics_spec.rb
new file mode 100644
index 00000000000..ee925e811e1
--- /dev/null
+++ b/spec/features/projects/environments/environment_metrics_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+feature 'Environment > Metrics', :feature do
+ include PrometheusHelpers
+
+ given(:user) { create(:user) }
+ given(:project) { create(:prometheus_project) }
+ given(:pipeline) { create(:ci_pipeline, project: project) }
+ given(:build) { create(:ci_build, pipeline: pipeline) }
+ given(:environment) { create(:environment, project: project) }
+ given(:current_time) { Time.now.utc }
+
+ background do
+ project.add_developer(user)
+ create(:deployment, environment: environment, deployable: build)
+ stub_all_prometheus_requests(environment.slug)
+
+ login_as(user)
+ visit_environment(environment)
+ end
+
+ around do |example|
+ Timecop.freeze(current_time) { example.run }
+ end
+
+ context 'with deployments and related deployable present' do
+ scenario 'shows metrics' do
+ click_link('See metrics')
+
+ expect(page).to have_css('svg.prometheus-graph')
+ end
+ end
+
+ def visit_environment(environment)
+ visit namespace_project_environment_path(environment.project.namespace,
+ environment.project,
+ environment)
+ end
+end
diff --git a/spec/features/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb
index 65373e3f77d..e2d16e0830a 100644
--- a/spec/features/environment_spec.rb
+++ b/spec/features/projects/environments/environment_spec.rb
@@ -37,13 +37,7 @@ feature 'Environment', :feature do
scenario 'does show deployment SHA' do
expect(page).to have_link(deployment.short_sha)
- end
-
- scenario 'does not show a re-deploy button for deployment without build' do
expect(page).not_to have_link('Re-deploy')
- end
-
- scenario 'does not show terminal button' do
expect(page).not_to have_terminal_button
end
end
@@ -58,13 +52,7 @@ feature 'Environment', :feature do
scenario 'does show build name' do
expect(page).to have_link("#{build.name} (##{build.id})")
- end
-
- scenario 'does show re-deploy button' do
expect(page).to have_link('Re-deploy')
- end
-
- scenario 'does not show terminal button' do
expect(page).not_to have_terminal_button
end
@@ -117,9 +105,6 @@ feature 'Environment', :feature do
it 'displays a web terminal' do
expect(page).to have_selector('#terminal')
- end
-
- it 'displays a link to the environment external url' do
expect(page).to have_link(nil, href: environment.external_url)
end
end
@@ -147,10 +132,6 @@ feature 'Environment', :feature do
on_stop: 'close_app')
end
- scenario 'does show stop button' do
- expect(page).to have_link('Stop')
- end
-
scenario 'does allow to stop environment' do
click_link('Stop')
diff --git a/spec/features/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb
index 169695b0759..169695b0759 100644
--- a/spec/features/environments_spec.rb
+++ b/spec/features/projects/environments/environments_spec.rb
diff --git a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
index 7414ce21f59..de3c6eceb82 100644
--- a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
+++ b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
@@ -32,7 +32,7 @@ feature 'Issue prioritization', feature: true do
visit namespace_project_issues_path(project.namespace, project, sort: 'priority')
# Ensure we are indicating that issues are sorted by priority
- expect(page).to have_selector('.dropdown-toggle', text: 'Priority')
+ expect(page).to have_selector('.dropdown-toggle', text: 'Label priority')
page.within('.issues-holder') do
issue_titles = all('.issues-list .issue-title-text').map(&:text)
@@ -70,7 +70,7 @@ feature 'Issue prioritization', feature: true do
login_as user
visit namespace_project_issues_path(project.namespace, project, sort: 'priority')
- expect(page).to have_selector('.dropdown-toggle', text: 'Priority')
+ expect(page).to have_selector('.dropdown-toggle', text: 'Label priority')
page.within('.issues-holder') do
issue_titles = all('.issues-list .issue-title-text').map(&:text)
diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb
index 0b4dcaa39c6..b64c15e0adc 100644
--- a/spec/features/projects/members/user_requests_access_spec.rb
+++ b/spec/features/projects/members/user_requests_access_spec.rb
@@ -57,6 +57,12 @@ feature 'Projects > Members > User requests access', feature: true do
end
def open_project_settings_menu
- find('#project-settings-button').click
+ page.within('.layout-nav .nav-links') do
+ click_link('Settings')
+ end
+
+ page.within('.page-with-layout-nav .sub-nav') do
+ click_link('Members')
+ end
end
end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index c30d38b6508..3a1240f95b5 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -18,7 +18,7 @@ feature 'Project', feature: true do
it 'passes through html-pipeline' do
project.update_attribute(:description, 'This project is the :poop:')
visit path
- expect(page).to have_css('.project-home-desc > p > img')
+ expect(page).to have_css('.project-home-desc > p > gl-emoji')
end
it 'sanitizes unwanted tags' do
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
index 7da05defa81..a6560a81096 100644
--- a/spec/features/search_spec.rb
+++ b/spec/features/search_spec.rb
@@ -1,6 +1,7 @@
require 'spec_helper'
describe "Search", feature: true do
+ include FilteredSearchHelpers
include WaitForAjax
let(:user) { create(:user) }
@@ -170,7 +171,8 @@ describe "Search", feature: true do
sleep 2
expect(page).to have_selector('.filtered-search')
- expect(find('.filtered-search').value).to eq("assignee:@#{user.username}")
+ expect_tokens([{ name: 'assignee', value: "@#{user.username}" }])
+ expect_filtered_search_input_empty
end
it 'takes user to her issues page when issues authored is clicked' do
@@ -178,7 +180,8 @@ describe "Search", feature: true do
sleep 2
expect(page).to have_selector('.filtered-search')
- expect(find('.filtered-search').value).to eq("author:@#{user.username}")
+ expect_tokens([{ name: 'author', value: "@#{user.username}" }])
+ expect_filtered_search_input_empty
end
it 'takes user to her MR page when MR assigned is clicked' do
@@ -186,7 +189,8 @@ describe "Search", feature: true do
sleep 2
expect(page).to have_selector('.merge-requests-holder')
- expect(find('.filtered-search').value).to eq("assignee:@#{user.username}")
+ expect_tokens([{ name: 'assignee', value: "@#{user.username}" }])
+ expect_filtered_search_input_empty
end
it 'takes user to her MR page when MR authored is clicked' do
@@ -194,7 +198,8 @@ describe "Search", feature: true do
sleep 2
expect(page).to have_selector('.merge-requests-holder')
- expect(find('.filtered-search').value).to eq("author:@#{user.username}")
+ expect_tokens([{ name: 'author', value: "@#{user.username}" }])
+ expect_filtered_search_input_empty
end
end
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index 24af062d763..1a66d1a6a1e 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -110,6 +110,20 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_denied_for(:external) }
end
+ describe "GET /:project_path/settings/repository" do
+ subject { namespace_project_settings_repository_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for(:admin) }
+ it { is_expected.to be_allowed_for(:owner).of(project) }
+ it { is_expected.to be_allowed_for(:master).of(project) }
+ it { is_expected.to be_denied_for(:developer).of(project) }
+ it { is_expected.to be_denied_for(:reporter).of(project) }
+ it { is_expected.to be_denied_for(:guest).of(project) }
+ it { is_expected.to be_denied_for(:user) }
+ it { is_expected.to be_denied_for(:visitor) }
+ it { is_expected.to be_denied_for(:external) }
+ end
+
describe "GET /:project_path/blob" do
let(:commit) { project.repository.commit }
subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore')) }
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index c511dcfa18e..ad3bd60a313 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -110,6 +110,20 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_denied_for(:external) }
end
+ describe "GET /:project_path/settings/repository" do
+ subject { namespace_project_settings_repository_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for(:admin) }
+ it { is_expected.to be_allowed_for(:owner).of(project) }
+ it { is_expected.to be_allowed_for(:master).of(project) }
+ it { is_expected.to be_denied_for(:developer).of(project) }
+ it { is_expected.to be_denied_for(:reporter).of(project) }
+ it { is_expected.to be_denied_for(:guest).of(project) }
+ it { is_expected.to be_denied_for(:user) }
+ it { is_expected.to be_denied_for(:external) }
+ it { is_expected.to be_denied_for(:visitor) }
+ end
+
describe "GET /:project_path/blob" do
let(:commit) { project.repository.commit }
subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore'))}
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index d8cc012c27e..e06aab4e0b2 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -110,6 +110,20 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_denied_for(:external) }
end
+ describe "GET /:project_path/settings/repository" do
+ subject { namespace_project_settings_repository_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for(:admin) }
+ it { is_expected.to be_allowed_for(:owner).of(project) }
+ it { is_expected.to be_allowed_for(:master).of(project) }
+ it { is_expected.to be_denied_for(:developer).of(project) }
+ it { is_expected.to be_denied_for(:reporter).of(project) }
+ it { is_expected.to be_denied_for(:guest).of(project) }
+ it { is_expected.to be_denied_for(:user) }
+ it { is_expected.to be_denied_for(:visitor) }
+ it { is_expected.to be_denied_for(:external) }
+ end
+
describe "GET /:project_path/pipelines" do
subject { namespace_project_pipelines_path(project.namespace, project) }
diff --git a/spec/features/todos/todos_sorting_spec.rb b/spec/features/todos/todos_sorting_spec.rb
index fec28c55d30..4d5bd476301 100644
--- a/spec/features/todos/todos_sorting_spec.rb
+++ b/spec/features/todos/todos_sorting_spec.rb
@@ -56,8 +56,8 @@ describe "Dashboard > User sorts todos", feature: true do
expect(results_list.all('p')[4]).to have_content("merge_request_1")
end
- it "sorts by priority" do
- click_link "Priority"
+ it "sorts by label priority" do
+ click_link "Label priority"
results_list = page.find('.todos-list')
expect(results_list.all('p')[0]).to have_content("issue_3")
@@ -85,8 +85,8 @@ describe "Dashboard > User sorts todos", feature: true do
visit dashboard_todos_path
end
- it "doesn't mix issues and merge requests priorities" do
- click_link "Priority"
+ it "doesn't mix issues and merge requests label priorities" do
+ click_link "Label priority"
results_list = page.find('.todos-list')
expect(results_list.all('p')[0]).to have_content("issue_1")
diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb
index 4a7511589d6..c1ae6db00c6 100644
--- a/spec/features/triggers_spec.rb
+++ b/spec/features/triggers_spec.rb
@@ -1,28 +1,175 @@
require 'spec_helper'
-describe 'Triggers' do
+feature 'Triggers', feature: true, js: true do
+ let(:trigger_title) { 'trigger desc' }
let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+ let(:guest_user) { create(:user) }
before { login_as(user) }
before do
- @project = FactoryGirl.create :empty_project
+ @project = create(:empty_project)
@project.team << [user, :master]
+ @project.team << [user2, :master]
+ @project.team << [guest_user, :guest]
visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
end
- context 'create a trigger' do
- before do
- click_on 'Add trigger'
- expect(@project.triggers.count).to eq(1)
+ describe 'create trigger workflow' do
+ scenario 'prevents adding new trigger with no description' do
+ fill_in 'trigger_description', with: ''
+ click_button 'Add trigger'
+
+ # See if input has error due to empty value
+ expect(page.find('form.gl-show-field-errors .gl-field-error')['style']).to eq 'display: block;'
+ end
+
+ scenario 'adds new trigger with description' do
+ fill_in 'trigger_description', with: 'trigger desc'
+ click_button 'Add trigger'
+
+ # See if "trigger creation successful" message displayed and description and owner are correct
+ expect(page.find('.flash-notice')).to have_content 'Trigger was created successfully.'
+ expect(page.find('.triggers-list')).to have_content 'trigger desc'
+ expect(page.find('.triggers-list .trigger-owner')).to have_content @user.name
+ end
+ end
+
+ describe 'edit trigger workflow' do
+ let(:new_trigger_title) { 'new trigger' }
+
+ scenario 'click on edit trigger opens edit trigger page' do
+ create(:ci_trigger, owner: user, project: @project, description: trigger_title)
+ visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+
+ # See if edit page has correct descrption
+ find('a[title="Edit"]').click
+ expect(page.find('#trigger_description').value).to have_content 'trigger desc'
+ end
+
+ scenario 'edit trigger and save' do
+ create(:ci_trigger, owner: user, project: @project, description: trigger_title)
+ visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+
+ # See if edit page opens, then fill in new description and save
+ find('a[title="Edit"]').click
+ fill_in 'trigger_description', with: new_trigger_title
+ click_button 'Save trigger'
+
+ # See if "trigger updated successfully" message displayed and description and owner are correct
+ expect(page.find('.flash-notice')).to have_content 'Trigger was successfully updated.'
+ expect(page.find('.triggers-list')).to have_content new_trigger_title
+ expect(page.find('.triggers-list .trigger-owner')).to have_content @user.name
+ end
+
+ scenario 'edit "legacy" trigger and save' do
+ # Create new trigger without owner association, i.e. Legacy trigger
+ create(:ci_trigger, owner: nil, project: @project)
+ visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+
+ # See if the trigger can be edited and description is blank
+ find('a[title="Edit"]').click
+ expect(page.find('#trigger_description').value).to have_content ''
+
+ # See if trigger can be updated with description and saved successfully
+ fill_in 'trigger_description', with: new_trigger_title
+ click_button 'Save trigger'
+ expect(page.find('.flash-notice')).to have_content 'Trigger was successfully updated.'
+ expect(page.find('.triggers-list')).to have_content new_trigger_title
+ end
+ end
+
+ describe 'trigger "Take ownership" workflow' do
+ before(:each) do
+ create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
+ visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ end
+
+ scenario 'button "Take ownership" has correct alert' do
+ expected_alert = 'By taking ownership you will bind this trigger to your user account. With this the trigger will have access to all your projects as if it was you. Are you sure?'
+ expect(page.find('a.btn-trigger-take-ownership')['data-confirm']).to eq expected_alert
end
- it 'contains trigger token' do
- expect(page).to have_content(@project.triggers.first.token)
+ scenario 'take trigger ownership' do
+ # See if "Take ownership" on trigger works post trigger creation
+ find('a.btn-trigger-take-ownership').click
+ page.accept_confirm do
+ expect(page.find('.flash-notice')).to have_content 'Trigger was re-assigned.'
+ expect(page.find('.triggers-list')).to have_content trigger_title
+ expect(page.find('.triggers-list .trigger-owner')).to have_content @user.name
+ end
end
+ end
+
+ describe 'trigger "Revoke" workflow' do
+ before(:each) do
+ create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
+ visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ end
+
+ scenario 'button "Revoke" has correct alert' do
+ expected_alert = 'By revoking a trigger you will break any processes making use of it. Are you sure?'
+ expect(page.find('a.btn-trigger-revoke')['data-confirm']).to eq expected_alert
+ end
+
+ scenario 'revoke trigger' do
+ # See if "Revoke" on trigger works post trigger creation
+ find('a.btn-trigger-revoke').click
+ page.accept_confirm do
+ expect(page.find('.flash-notice')).to have_content 'Trigger removed'
+ expect(page).to have_selector('p.settings-message.text-center.append-bottom-default')
+ end
+ end
+ end
+
+ describe 'show triggers workflow' do
+ scenario 'contains trigger description placeholder' do
+ expect(page.find('#trigger_description')['placeholder']).to eq 'Trigger description'
+ end
+
+ scenario 'show "legacy" badge for legacy trigger' do
+ create(:ci_trigger, owner: nil, project: @project)
+ visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+
+ # See if trigger without owner (i.e. legacy) shows "legacy" badge and is editable
+ expect(page.find('.triggers-list')).to have_content 'legacy'
+ expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]')
+ end
+
+ scenario 'show "invalid" badge for trigger with owner having insufficient permissions' do
+ create(:ci_trigger, owner: guest_user, project: @project, description: trigger_title)
+ visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+
+ # See if trigger without owner (i.e. legacy) shows "legacy" badge and is non-editable
+ expect(page.find('.triggers-list')).to have_content 'invalid'
+ expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]')
+ end
+
+ scenario 'do not show "Edit" or full token for not owned trigger' do
+ # Create trigger with user different from current_user
+ create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
+ visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+
+ # See if trigger not owned by current_user shows only first few token chars and doesn't have copy-to-clipboard button
+ expect(page.find('.triggers-list')).to have_content(@project.triggers.first.token[0..3])
+ expect(page.find('.triggers-list')).not_to have_selector('button.btn-clipboard')
+
+ # See if trigger owner name doesn't match with current_user and trigger is non-editable
+ expect(page.find('.triggers-list .trigger-owner')).not_to have_content @user.name
+ expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]')
+ end
+
+ scenario 'show "Edit" and full token for owned trigger' do
+ create(:ci_trigger, owner: user, project: @project, description: trigger_title)
+ visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+
+ # See if trigger shows full token and has copy-to-clipboard button
+ expect(page.find('.triggers-list')).to have_content @project.triggers.first.token
+ expect(page.find('.triggers-list')).to have_selector('button.btn-clipboard')
- it 'revokes the trigger' do
- click_on 'Revoke'
- expect(@project.triggers.count).to eq(0)
+ # See if trigger owner name matches with current_user and is editable
+ expect(page.find('.triggers-list .trigger-owner')).to have_content @user.name
+ expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]')
end
end
end
diff --git a/spec/finders/personal_access_tokens_finder_spec.rb b/spec/finders/personal_access_tokens_finder_spec.rb
new file mode 100644
index 00000000000..fd92664ca24
--- /dev/null
+++ b/spec/finders/personal_access_tokens_finder_spec.rb
@@ -0,0 +1,196 @@
+require 'spec_helper'
+
+describe PersonalAccessTokensFinder do
+ def finder(options = {})
+ described_class.new(options)
+ end
+
+ describe '#execute' do
+ let(:user) { create(:user) }
+ let(:params) { {} }
+ let!(:active_personal_access_token) { create(:personal_access_token, user: user) }
+ let!(:expired_personal_access_token) { create(:personal_access_token, :expired, user: user) }
+ let!(:revoked_personal_access_token) { create(:personal_access_token, :revoked, user: user) }
+ let!(:active_impersonation_token) { create(:personal_access_token, :impersonation, user: user) }
+ let!(:expired_impersonation_token) { create(:personal_access_token, :expired, :impersonation, user: user) }
+ let!(:revoked_impersonation_token) { create(:personal_access_token, :revoked, :impersonation, user: user) }
+
+ subject { finder(params).execute }
+
+ describe 'without user' do
+ it do
+ is_expected.to contain_exactly(active_personal_access_token, active_impersonation_token,
+ revoked_personal_access_token, expired_personal_access_token,
+ revoked_impersonation_token, expired_impersonation_token)
+ end
+
+ describe 'without impersonation' do
+ before { params[:impersonation] = false }
+
+ it { is_expected.to contain_exactly(active_personal_access_token, revoked_personal_access_token, expired_personal_access_token) }
+
+ describe 'with active state' do
+ before { params[:state] = 'active' }
+
+ it { is_expected.to contain_exactly(active_personal_access_token) }
+ end
+
+ describe 'with inactive state' do
+ before { params[:state] = 'inactive' }
+
+ it { is_expected.to contain_exactly(revoked_personal_access_token, expired_personal_access_token) }
+ end
+ end
+
+ describe 'with impersonation' do
+ before { params[:impersonation] = true }
+
+ it { is_expected.to contain_exactly(active_impersonation_token, revoked_impersonation_token, expired_impersonation_token) }
+
+ describe 'with active state' do
+ before { params[:state] = 'active' }
+
+ it { is_expected.to contain_exactly(active_impersonation_token) }
+ end
+
+ describe 'with inactive state' do
+ before { params[:state] = 'inactive' }
+
+ it { is_expected.to contain_exactly(revoked_impersonation_token, expired_impersonation_token) }
+ end
+ end
+
+ describe 'with active state' do
+ before { params[:state] = 'active' }
+
+ it { is_expected.to contain_exactly(active_personal_access_token, active_impersonation_token) }
+ end
+
+ describe 'with inactive state' do
+ before { params[:state] = 'inactive' }
+
+ it do
+ is_expected.to contain_exactly(expired_personal_access_token, revoked_personal_access_token,
+ expired_impersonation_token, revoked_impersonation_token)
+ end
+ end
+
+ describe 'with id' do
+ subject { finder(params).find_by(id: active_personal_access_token.id) }
+
+ it { is_expected.to eq(active_personal_access_token) }
+
+ describe 'with impersonation' do
+ before { params[:impersonation] = true }
+
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe 'with token' do
+ subject { finder(params).find_by(token: active_personal_access_token.token) }
+
+ it { is_expected.to eq(active_personal_access_token) }
+
+ describe 'with impersonation' do
+ before { params[:impersonation] = true }
+
+ it { is_expected.to be_nil }
+ end
+ end
+ end
+
+ describe 'with user' do
+ let(:user2) { create(:user) }
+ let!(:other_user_active_personal_access_token) { create(:personal_access_token, user: user2) }
+ let!(:other_user_expired_personal_access_token) { create(:personal_access_token, :expired, user: user2) }
+ let!(:other_user_revoked_personal_access_token) { create(:personal_access_token, :revoked, user: user2) }
+ let!(:other_user_active_impersonation_token) { create(:personal_access_token, :impersonation, user: user2) }
+ let!(:other_user_expired_impersonation_token) { create(:personal_access_token, :expired, :impersonation, user: user2) }
+ let!(:other_user_revoked_impersonation_token) { create(:personal_access_token, :revoked, :impersonation, user: user2) }
+
+ before { params[:user] = user }
+
+ it do
+ is_expected.to contain_exactly(active_personal_access_token, active_impersonation_token,
+ revoked_personal_access_token, expired_personal_access_token,
+ revoked_impersonation_token, expired_impersonation_token)
+ end
+
+ describe 'without impersonation' do
+ before { params[:impersonation] = false }
+
+ it { is_expected.to contain_exactly(active_personal_access_token, revoked_personal_access_token, expired_personal_access_token) }
+
+ describe 'with active state' do
+ before { params[:state] = 'active' }
+
+ it { is_expected.to contain_exactly(active_personal_access_token) }
+ end
+
+ describe 'with inactive state' do
+ before { params[:state] = 'inactive' }
+
+ it { is_expected.to contain_exactly(revoked_personal_access_token, expired_personal_access_token) }
+ end
+ end
+
+ describe 'with impersonation' do
+ before { params[:impersonation] = true }
+
+ it { is_expected.to contain_exactly(active_impersonation_token, revoked_impersonation_token, expired_impersonation_token) }
+
+ describe 'with active state' do
+ before { params[:state] = 'active' }
+
+ it { is_expected.to contain_exactly(active_impersonation_token) }
+ end
+
+ describe 'with inactive state' do
+ before { params[:state] = 'inactive' }
+
+ it { is_expected.to contain_exactly(revoked_impersonation_token, expired_impersonation_token) }
+ end
+ end
+
+ describe 'with active state' do
+ before { params[:state] = 'active' }
+
+ it { is_expected.to contain_exactly(active_personal_access_token, active_impersonation_token) }
+ end
+
+ describe 'with inactive state' do
+ before { params[:state] = 'inactive' }
+
+ it do
+ is_expected.to contain_exactly(expired_personal_access_token, revoked_personal_access_token,
+ expired_impersonation_token, revoked_impersonation_token)
+ end
+ end
+
+ describe 'with id' do
+ subject { finder(params).find_by(id: active_personal_access_token.id) }
+
+ it { is_expected.to eq(active_personal_access_token) }
+
+ describe 'with impersonation' do
+ before { params[:impersonation] = true }
+
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe 'with token' do
+ subject { finder(params).find_by(token: active_personal_access_token.token) }
+
+ it { is_expected.to eq(active_personal_access_token) }
+
+ describe 'with impersonation' do
+ before { params[:impersonation] = true }
+
+ it { is_expected.to be_nil }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/fixtures/api/schemas/issue.json b/spec/fixtures/api/schemas/issue.json
index 8e19cee5440..21c078e0f44 100644
--- a/spec/fixtures/api/schemas/issue.json
+++ b/spec/fixtures/api/schemas/issue.json
@@ -11,6 +11,7 @@
"title": { "type": "string" },
"confidential": { "type": "boolean" },
"due_date": { "type": ["date", "null"] },
+ "relative_position": { "type": "integer" },
"labels": {
"type": "array",
"items": {
diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb
index 594b40303bc..81ba693f2f3 100644
--- a/spec/helpers/events_helper_spec.rb
+++ b/spec/helpers/events_helper_spec.rb
@@ -61,6 +61,13 @@ describe EventsHelper do
'</code></pre>'
expect(helper.event_note(input)).to eq(expected)
end
+
+ it 'preserves style attribute within a tag' do
+ input = '<span class="" style="background-color: #44ad8e; color: #FFFFFF;"></span>'
+ expected = '<p><span style="background-color: #44ad8e; color: #FFFFFF;"></span></p>'
+
+ expect(helper.event_note(input)).to eq(expected)
+ end
end
describe '#event_commit_title' do
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index b8ec3521edb..9ffd4b9371c 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -113,7 +113,7 @@ describe GitlabMarkdownHelper do
it 'replaces commit message with emoji to link' do
actual = link_to_gfm(':book:Book', '/foo')
expect(actual).
- to eq %Q(<img class="emoji" title=":book:" alt=":book:" src="http://#{Gitlab.config.gitlab.host}/assets/1F4D6.png" height="20" width="20" align="absmiddle"><a href="/foo">Book</a>)
+ to eq '<gl-emoji data-name="book" data-unicode-version="6.0">📖</gl-emoji><a href="/foo">Book</a>'
end
end
diff --git a/spec/initializers/6_validations_spec.rb b/spec/initializers/6_validations_spec.rb
index baab30f482f..cf182e6d221 100644
--- a/spec/initializers/6_validations_spec.rb
+++ b/spec/initializers/6_validations_spec.rb
@@ -14,7 +14,7 @@ describe '6_validations', lib: true do
context 'with correct settings' do
before do
- mock_storages('foo' => 'tmp/tests/paths/a/b/c', 'bar' => 'tmp/tests/paths/a/b/d')
+ mock_storages('foo' => { 'path' => 'tmp/tests/paths/a/b/c' }, 'bar' => { 'path' => 'tmp/tests/paths/a/b/d' })
end
it 'passes through' do
@@ -24,7 +24,7 @@ describe '6_validations', lib: true do
context 'with invalid storage names' do
before do
- mock_storages('name with spaces' => 'tmp/tests/paths/a/b/c')
+ mock_storages('name with spaces' => { 'path' => 'tmp/tests/paths/a/b/c' })
end
it 'throws an error' do
@@ -34,7 +34,7 @@ describe '6_validations', lib: true do
context 'with nested storage paths' do
before do
- mock_storages('foo' => 'tmp/tests/paths/a/b/c', 'bar' => 'tmp/tests/paths/a/b/c/d')
+ mock_storages('foo' => { 'path' => 'tmp/tests/paths/a/b/c' }, 'bar' => { 'path' => 'tmp/tests/paths/a/b/c/d' })
end
it 'throws an error' do
@@ -44,7 +44,7 @@ describe '6_validations', lib: true do
context 'with similar but un-nested storage paths' do
before do
- mock_storages('foo' => 'tmp/tests/paths/a/b/c', 'bar' => 'tmp/tests/paths/a/b/c2')
+ mock_storages('foo' => { 'path' => 'tmp/tests/paths/a/b/c' }, 'bar' => { 'path' => 'tmp/tests/paths/a/b/c2' })
end
it 'passes through' do
@@ -52,6 +52,26 @@ describe '6_validations', lib: true do
end
end
+ context 'with incomplete settings' do
+ before do
+ mock_storages('foo' => {})
+ end
+
+ it 'throws an error suggesting the user to update its settings' do
+ expect { validate_storages }.to raise_error('foo is not a valid storage, because it has no `path` key. Refer to gitlab.yml.example for an updated example. Please fix this in your gitlab.yml before starting GitLab.')
+ end
+ end
+
+ context 'with deprecated settings structure' do
+ before do
+ mock_storages('foo' => 'tmp/tests/paths/a/b/c')
+ end
+
+ it 'throws an error suggesting the user to update its settings' do
+ expect { validate_storages }.to raise_error("foo is not a valid storage, because it has no `path` key. It may be configured as:\n\nfoo:\n path: tmp/tests/paths/a/b/c\n\nRefer to gitlab.yml.example for an updated example. Please fix this in your gitlab.yml before starting GitLab.")
+ end
+ end
+
def mock_storages(storages)
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
end
diff --git a/spec/initializers/doorkeeper_spec.rb b/spec/initializers/doorkeeper_spec.rb
new file mode 100644
index 00000000000..74bdbb01166
--- /dev/null
+++ b/spec/initializers/doorkeeper_spec.rb
@@ -0,0 +1,71 @@
+require 'spec_helper'
+require_relative '../../config/initializers/doorkeeper'
+
+describe Doorkeeper.configuration do
+ describe '#default_scopes' do
+ it 'matches Gitlab::Auth::DEFAULT_SCOPES' do
+ expect(subject.default_scopes).to eq Gitlab::Auth::DEFAULT_SCOPES
+ end
+ end
+
+ describe '#optional_scopes' do
+ it 'matches Gitlab::Auth::OPTIONAL_SCOPES' do
+ expect(subject.optional_scopes).to eq Gitlab::Auth::OPTIONAL_SCOPES
+ end
+ end
+
+ describe '#resource_owner_authenticator' do
+ subject { controller.instance_exec(&Doorkeeper.configuration.authenticate_resource_owner) }
+
+ let(:controller) { double }
+
+ before do
+ allow(controller).to receive(:current_user).and_return(current_user)
+ allow(controller).to receive(:session).and_return({})
+ allow(controller).to receive(:request).and_return(OpenStruct.new(fullpath: '/return-path'))
+ allow(controller).to receive(:redirect_to)
+ allow(controller).to receive(:new_user_session_url).and_return('/login')
+ end
+
+ context 'with a user present' do
+ let(:current_user) { create(:user) }
+
+ it 'returns the user' do
+ expect(subject).to eq current_user
+ end
+
+ it 'does not redirect' do
+ expect(controller).not_to receive(:redirect_to)
+
+ subject
+ end
+
+ it 'does not store the return path' do
+ subject
+
+ expect(controller.session).not_to include :user_return_to
+ end
+ end
+
+ context 'without a user present' do
+ let(:current_user) { nil }
+
+ # NOTE: this is required for doorkeeper-openid_connect
+ it 'returns nil' do
+ expect(subject).to eq nil
+ end
+
+ it 'redirects to the login form' do
+ expect(controller).to receive(:redirect_to).with('/login')
+
+ subject
+ end
+
+ it 'stores the return path' do
+ subject
+
+ expect(controller.session[:user_return_to]).to eq '/return-path'
+ end
+ end
+ end
+end
diff --git a/spec/initializers/secret_token_spec.rb b/spec/initializers/secret_token_spec.rb
index ad7f032d1e5..65c97da2efd 100644
--- a/spec/initializers/secret_token_spec.rb
+++ b/spec/initializers/secret_token_spec.rb
@@ -6,6 +6,9 @@ describe 'create_tokens', lib: true do
let(:secrets) { ActiveSupport::OrderedOptions.new }
+ HEX_KEY = /\h{128}/
+ RSA_KEY = /\A-----BEGIN RSA PRIVATE KEY-----\n.+\n-----END RSA PRIVATE KEY-----\n\Z/m
+
before do
allow(File).to receive(:write)
allow(File).to receive(:delete)
@@ -15,7 +18,7 @@ describe 'create_tokens', lib: true do
allow(self).to receive(:exit)
end
- context 'setting secret_key_base and otp_key_base' do
+ context 'setting secret keys' do
context 'when none of the secrets exist' do
before do
stub_env('SECRET_KEY_BASE', nil)
@@ -24,19 +27,29 @@ describe 'create_tokens', lib: true do
allow(self).to receive(:warn_missing_secret)
end
- it 'generates different secrets for secret_key_base, otp_key_base, and db_key_base' do
+ it 'generates different hashes for secret_key_base, otp_key_base, and db_key_base' do
create_tokens
keys = secrets.values_at(:secret_key_base, :otp_key_base, :db_key_base)
expect(keys.uniq).to eq(keys)
- expect(keys.map(&:length)).to all(eq(128))
+ expect(keys).to all(match(HEX_KEY))
+ end
+
+ it 'generates an RSA key for jws_private_key' do
+ create_tokens
+
+ keys = secrets.values_at(:jws_private_key)
+
+ expect(keys.uniq).to eq(keys)
+ expect(keys).to all(match(RSA_KEY))
end
it 'warns about the secrets to add to secrets.yml' do
expect(self).to receive(:warn_missing_secret).with('secret_key_base')
expect(self).to receive(:warn_missing_secret).with('otp_key_base')
expect(self).to receive(:warn_missing_secret).with('db_key_base')
+ expect(self).to receive(:warn_missing_secret).with('jws_private_key')
create_tokens
end
@@ -48,6 +61,7 @@ describe 'create_tokens', lib: true do
expect(new_secrets['secret_key_base']).to eq(secrets.secret_key_base)
expect(new_secrets['otp_key_base']).to eq(secrets.otp_key_base)
expect(new_secrets['db_key_base']).to eq(secrets.db_key_base)
+ expect(new_secrets['jws_private_key']).to eq(secrets.jws_private_key)
end
create_tokens
@@ -63,6 +77,7 @@ describe 'create_tokens', lib: true do
context 'when the other secrets all exist' do
before do
secrets.db_key_base = 'db_key_base'
+ secrets.jws_private_key = 'jws_private_key'
allow(File).to receive(:exist?).with('.secret').and_return(true)
allow(File).to receive(:read).with('.secret').and_return('file_key')
@@ -73,6 +88,7 @@ describe 'create_tokens', lib: true do
stub_env('SECRET_KEY_BASE', 'env_key')
secrets.secret_key_base = 'secret_key_base'
secrets.otp_key_base = 'otp_key_base'
+ secrets.jws_private_key = 'jws_private_key'
end
it 'does not issue a warning' do
@@ -98,6 +114,7 @@ describe 'create_tokens', lib: true do
before do
secrets.secret_key_base = 'secret_key_base'
secrets.otp_key_base = 'otp_key_base'
+ secrets.jws_private_key = 'jws_private_key'
end
it 'does not write any files' do
@@ -112,6 +129,7 @@ describe 'create_tokens', lib: true do
expect(secrets.secret_key_base).to eq('secret_key_base')
expect(secrets.otp_key_base).to eq('otp_key_base')
expect(secrets.db_key_base).to eq('db_key_base')
+ expect(secrets.jws_private_key).to eq('jws_private_key')
end
it 'deletes the .secret file' do
@@ -135,6 +153,7 @@ describe 'create_tokens', lib: true do
expect(new_secrets['secret_key_base']).to eq('file_key')
expect(new_secrets['otp_key_base']).to eq('file_key')
expect(new_secrets['db_key_base']).to eq('db_key_base')
+ expect(new_secrets['jws_private_key']).to eq('jws_private_key')
end
create_tokens
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
index e5826f9c29f..f4b1d777203 100644
--- a/spec/javascripts/awards_handler_spec.js
+++ b/spec/javascripts/awards_handler_spec.js
@@ -1,11 +1,11 @@
/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, comma-dangle, new-parens, no-unused-vars, quotes, jasmine/no-spec-dupes, prefer-template, max-len */
-/* global AwardsHandler */
-require('~/awards_handler');
-require('./fixtures/emoji_menu');
+require('es6-promise').polyfill();
+
+const AwardsHandler = require('~/awards_handler');
(function() {
- var awardsHandler, lazyAssert, urlRoot;
+ var awardsHandler, lazyAssert, urlRoot, openAndWaitForEmojiMenu;
awardsHandler = null;
@@ -13,14 +13,6 @@ require('./fixtures/emoji_menu');
window.gon || (window.gon = {});
- gl.emojiAliases = function() {
- return {
- '+1': 'thumbsup',
- '-1': 'thumbsdown'
- };
- };
-
- gon.award_menu_url = '/emojis';
urlRoot = gon.relative_url_root;
lazyAssert = function(done, assertFn) {
@@ -32,22 +24,40 @@ require('./fixtures/emoji_menu');
};
describe('AwardsHandler', function() {
- preloadFixtures('issues/open-issue.html.raw');
+ preloadFixtures('issues/issue_with_comment.html.raw');
beforeEach(function() {
- loadFixtures('issues/open-issue.html.raw');
+ loadFixtures('issues/issue_with_comment.html.raw');
awardsHandler = new AwardsHandler;
spyOn(awardsHandler, 'postEmoji').and.callFake((function(_this) {
return function(url, emoji, cb) {
return cb();
};
})(this));
- spyOn(jQuery, 'get').and.callFake(function(req, cb) {
- return cb(window.emojiMenu);
- });
+
+ let isEmojiMenuBuilt = false;
+ openAndWaitForEmojiMenu = function() {
+ return new Promise((resolve, reject) => {
+ if (isEmojiMenuBuilt) {
+ resolve();
+ } else {
+ $('.js-add-award').eq(0).click();
+ const $menu = $('.emoji-menu');
+ $menu.one('build-emoji-menu-finish', () => {
+ isEmojiMenuBuilt = true;
+ resolve();
+ });
+
+ // Fail after 1 second
+ setTimeout(reject, 1000);
+ }
+ });
+ };
});
afterEach(function() {
// restore original url root value
gon.relative_url_root = urlRoot;
+
+ awardsHandler.destroy();
});
describe('::showEmojiMenu', function() {
it('should show emoji menu when Add emoji button clicked', function(done) {
@@ -62,10 +72,9 @@ require('./fixtures/emoji_menu');
});
});
it('should also show emoji menu for the smiley icon in notes', function(done) {
- $('.note-action-button').click();
+ $('.js-add-award.note-action-button').click();
return lazyAssert(done, function() {
- var $emojiMenu;
- $emojiMenu = $('.emoji-menu');
+ var $emojiMenu = $('.emoji-menu');
return expect($emojiMenu.length).toBe(1);
});
});
@@ -86,7 +95,7 @@ require('./fixtures/emoji_menu');
var $emojiButton, $votesBlock;
$votesBlock = $('.js-awards-block').eq(0);
awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false);
- $emojiButton = $votesBlock.find('[data-emoji=heart]');
+ $emojiButton = $votesBlock.find('[data-name=heart]');
expect($emojiButton.length).toBe(1);
expect($emojiButton.next('.js-counter').text()).toBe('1');
return expect($votesBlock.hasClass('hidden')).toBe(false);
@@ -96,14 +105,14 @@ require('./fixtures/emoji_menu');
$votesBlock = $('.js-awards-block').eq(0);
awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false);
awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false);
- $emojiButton = $votesBlock.find('[data-emoji=heart]');
+ $emojiButton = $votesBlock.find('[data-name=heart]');
return expect($emojiButton.length).toBe(0);
});
return it('should decrement the emoji counter', function() {
var $emojiButton, $votesBlock;
$votesBlock = $('.js-awards-block').eq(0);
awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false);
- $emojiButton = $votesBlock.find('[data-emoji=heart]');
+ $emojiButton = $votesBlock.find('[data-name=heart]');
$emojiButton.next('.js-counter').text(5);
awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false);
expect($emojiButton.length).toBe(1);
@@ -120,8 +129,8 @@ require('./fixtures/emoji_menu');
var $thumbsDownEmoji, $thumbsUpEmoji, $votesBlock, awardUrl;
awardUrl = awardsHandler.getAwardUrl();
$votesBlock = $('.js-awards-block').eq(0);
- $thumbsUpEmoji = $votesBlock.find('[data-emoji=thumbsup]').parent();
- $thumbsDownEmoji = $votesBlock.find('[data-emoji=thumbsdown]').parent();
+ $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
+ $thumbsDownEmoji = $votesBlock.find('[data-name=thumbsdown]').parent();
awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false);
expect($thumbsUpEmoji.hasClass('active')).toBe(true);
expect($thumbsDownEmoji.hasClass('active')).toBe(false);
@@ -138,9 +147,9 @@ require('./fixtures/emoji_menu');
awardUrl = awardsHandler.getAwardUrl();
$votesBlock = $('.js-awards-block').eq(0);
awardsHandler.addAward($votesBlock, awardUrl, 'fire', false);
- expect($votesBlock.find('[data-emoji=fire]').length).toBe(1);
- awardsHandler.removeEmoji($votesBlock.find('[data-emoji=fire]').closest('button'));
- return expect($votesBlock.find('[data-emoji=fire]').length).toBe(0);
+ expect($votesBlock.find('[data-name=fire]').length).toBe(1);
+ awardsHandler.removeEmoji($votesBlock.find('[data-name=fire]').closest('button'));
+ return expect($votesBlock.find('[data-name=fire]').length).toBe(0);
});
});
describe('::addYouToUserList', function() {
@@ -148,7 +157,7 @@ require('./fixtures/emoji_menu');
var $thumbsUpEmoji, $votesBlock, awardUrl;
awardUrl = awardsHandler.getAwardUrl();
$votesBlock = $('.js-awards-block').eq(0);
- $thumbsUpEmoji = $votesBlock.find('[data-emoji=thumbsup]').parent();
+ $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
$thumbsUpEmoji.attr('data-title', 'sam, jerry, max, and andy');
awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false);
$thumbsUpEmoji.tooltip();
@@ -158,7 +167,7 @@ require('./fixtures/emoji_menu');
var $thumbsUpEmoji, $votesBlock, awardUrl;
awardUrl = awardsHandler.getAwardUrl();
$votesBlock = $('.js-awards-block').eq(0);
- $thumbsUpEmoji = $votesBlock.find('[data-emoji=thumbsup]').parent();
+ $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
$thumbsUpEmoji.attr('data-title', 'sam');
awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false);
$thumbsUpEmoji.tooltip();
@@ -170,7 +179,7 @@ require('./fixtures/emoji_menu');
var $thumbsUpEmoji, $votesBlock, awardUrl;
awardUrl = awardsHandler.getAwardUrl();
$votesBlock = $('.js-awards-block').eq(0);
- $thumbsUpEmoji = $votesBlock.find('[data-emoji=thumbsup]').parent();
+ $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
$thumbsUpEmoji.attr('data-title', 'You, sam, jerry, max, and andy');
$thumbsUpEmoji.addClass('active');
awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false);
@@ -181,7 +190,7 @@ require('./fixtures/emoji_menu');
var $thumbsUpEmoji, $votesBlock, awardUrl;
awardUrl = awardsHandler.getAwardUrl();
$votesBlock = $('.js-awards-block').eq(0);
- $thumbsUpEmoji = $votesBlock.find('[data-emoji=thumbsup]').parent();
+ $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
$thumbsUpEmoji.attr('data-title', 'You and sam');
$thumbsUpEmoji.addClass('active');
awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false);
@@ -190,42 +199,58 @@ require('./fixtures/emoji_menu');
});
});
describe('search', function() {
- return it('should filter the emoji', function() {
- $('.js-add-award').eq(0).click();
- expect($('[data-emoji=angel]').is(':visible')).toBe(true);
- expect($('[data-emoji=anger]').is(':visible')).toBe(true);
- $('#emoji_search').val('ali').trigger('keyup');
- expect($('[data-emoji=angel]').is(':visible')).toBe(false);
- expect($('[data-emoji=anger]').is(':visible')).toBe(false);
- return expect($('[data-emoji=alien]').is(':visible')).toBe(true);
+ return it('should filter the emoji', function(done) {
+ return openAndWaitForEmojiMenu()
+ .then(() => {
+ expect($('[data-name=angel]').is(':visible')).toBe(true);
+ expect($('[data-name=anger]').is(':visible')).toBe(true);
+ $('#emoji_search').val('ali').trigger('input');
+ expect($('[data-name=angel]').is(':visible')).toBe(false);
+ expect($('[data-name=anger]').is(':visible')).toBe(false);
+ expect($('[data-name=alien]').is(':visible')).toBe(true);
+ })
+ .then(done)
+ .catch(() => {
+ done.fail('Failed to open and build emoji menu');
+ });
});
});
- return describe('emoji menu', function() {
- var openEmojiMenuAndAddEmoji, selector;
- selector = '[data-emoji=sunglasses]';
- openEmojiMenuAndAddEmoji = function() {
- var $block, $emoji, $menu;
- $('.js-add-award').eq(0).click();
- $menu = $('.emoji-menu');
- $block = $('.js-awards-block');
- $emoji = $menu.find('.emoji-menu-list:not(.frequent-emojis) ' + selector);
- expect($emoji.length).toBe(1);
- expect($block.find(selector).length).toBe(0);
- $emoji.click();
- expect($menu.hasClass('.is-visible')).toBe(false);
- return expect($block.find(selector).length).toBe(1);
+ describe('emoji menu', function() {
+ const emojiSelector = '[data-name="sunglasses"]';
+ const openEmojiMenuAndAddEmoji = function() {
+ return openAndWaitForEmojiMenu()
+ .then(() => {
+ const $menu = $('.emoji-menu');
+ const $block = $('.js-awards-block');
+ const $emoji = $menu.find('.emoji-menu-list:not(.frequent-emojis) ' + emojiSelector);
+
+ expect($emoji.length).toBe(1);
+ expect($block.find(emojiSelector).length).toBe(0);
+ $emoji.click();
+ expect($menu.hasClass('.is-visible')).toBe(false);
+ expect($block.find(emojiSelector).length).toBe(1);
+ });
};
- it('should add selected emoji to awards block', function() {
- return openEmojiMenuAndAddEmoji();
+ it('should add selected emoji to awards block', function(done) {
+ return openEmojiMenuAndAddEmoji()
+ .then(done)
+ .catch(() => {
+ done.fail('Failed to open and build emoji menu');
+ });
});
- return it('should remove already selected emoji', function() {
- var $block, $emoji;
- openEmojiMenuAndAddEmoji();
- $('.js-add-award').eq(0).click();
- $block = $('.js-awards-block');
- $emoji = $('.emoji-menu').find(".emoji-menu-list:not(.frequent-emojis) " + selector);
- $emoji.click();
- return expect($block.find(selector).length).toBe(0);
+ it('should remove already selected emoji', function(done) {
+ return openEmojiMenuAndAddEmoji()
+ .then(() => {
+ $('.js-add-award').eq(0).click();
+ const $block = $('.js-awards-block');
+ const $emoji = $('.emoji-menu').find(`.emoji-menu-list:not(.frequent-emojis) ${emojiSelector}`);
+ $emoji.click();
+ expect($block.find(emojiSelector).length).toBe(0);
+ })
+ .then(done)
+ .catch((err) => {
+ done.fail('Failed to open and build emoji menu');
+ });
});
});
});
diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js
index 9dd741a680b..49a2ca4a78f 100644
--- a/spec/javascripts/boards/boards_store_spec.js
+++ b/spec/javascripts/boards/boards_store_spec.js
@@ -5,6 +5,7 @@
/* global Cookies */
/* global listObj */
/* global listObjDuplicate */
+/* global ListIssue */
require('~/lib/utils/url_utility');
require('~/boards/models/issue');
@@ -14,6 +15,7 @@ require('~/boards/models/user');
require('~/boards/services/board_service');
require('~/boards/stores/boards_store');
require('./mock_data');
+require('es6-promise').polyfill();
describe('Store', () => {
beforeEach(() => {
@@ -21,6 +23,10 @@ describe('Store', () => {
gl.boardService = new BoardService('/test/issue-boards/board', '', '1');
gl.issueBoards.BoardsStore.create();
+ spyOn(gl.boardService, 'moveIssue').and.callFake(() => new Promise((resolve) => {
+ resolve();
+ }));
+
Cookies.set('issue_board_welcome_hidden', 'false', {
expires: 365 * 10,
path: ''
@@ -154,5 +160,74 @@ describe('Store', () => {
done();
}, 0);
});
+
+ it('moves issue to top of another list', (done) => {
+ const listOne = gl.issueBoards.BoardsStore.addList(listObj);
+ const listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate);
+
+ expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(2);
+
+ setTimeout(() => {
+ listOne.issues[0].id = 2;
+
+ expect(listOne.issues.length).toBe(1);
+ expect(listTwo.issues.length).toBe(1);
+
+ gl.issueBoards.BoardsStore.moveIssueToList(listOne, listTwo, listOne.findIssue(2), 0);
+
+ expect(listOne.issues.length).toBe(0);
+ expect(listTwo.issues.length).toBe(2);
+ expect(listTwo.issues[0].id).toBe(2);
+ expect(gl.boardService.moveIssue).toHaveBeenCalledWith(2, listOne.id, listTwo.id, null, 1);
+
+ done();
+ }, 0);
+ });
+
+ it('moves issue to bottom of another list', (done) => {
+ const listOne = gl.issueBoards.BoardsStore.addList(listObj);
+ const listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate);
+
+ expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(2);
+
+ setTimeout(() => {
+ listOne.issues[0].id = 2;
+
+ expect(listOne.issues.length).toBe(1);
+ expect(listTwo.issues.length).toBe(1);
+
+ gl.issueBoards.BoardsStore.moveIssueToList(listOne, listTwo, listOne.findIssue(2), 1);
+
+ expect(listOne.issues.length).toBe(0);
+ expect(listTwo.issues.length).toBe(2);
+ expect(listTwo.issues[1].id).toBe(2);
+ expect(gl.boardService.moveIssue).toHaveBeenCalledWith(2, listOne.id, listTwo.id, 1, null);
+
+ done();
+ }, 0);
+ });
+
+ it('moves issue in list', (done) => {
+ const issue = new ListIssue({
+ title: 'Testing',
+ iid: 2,
+ confidential: false,
+ labels: []
+ });
+ const list = gl.issueBoards.BoardsStore.addList(listObj);
+
+ setTimeout(() => {
+ list.addIssue(issue);
+
+ expect(list.issues.length).toBe(2);
+
+ gl.issueBoards.BoardsStore.moveIssueInList(list, issue, 0, 1, [1, 2]);
+
+ expect(list.issues[0].id).toBe(2);
+ expect(gl.boardService.moveIssue).toHaveBeenCalledWith(2, null, null, 1, null);
+
+ done();
+ });
+ });
});
});
diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js
index aab4d9c501e..c96dfe94a4a 100644
--- a/spec/javascripts/boards/issue_spec.js
+++ b/spec/javascripts/boards/issue_spec.js
@@ -79,4 +79,20 @@ describe('Issue model', () => {
issue.removeLabels([issue.labels[0], issue.labels[1]]);
expect(issue.labels.length).toBe(0);
});
+
+ it('sets position to infinity if no position is stored', () => {
+ expect(issue.position).toBe(Infinity);
+ });
+
+ it('sets position', () => {
+ const relativePositionIssue = new ListIssue({
+ title: 'Testing',
+ iid: 1,
+ confidential: false,
+ relative_position: 1,
+ labels: []
+ });
+
+ expect(relativePositionIssue.position).toBe(1);
+ });
});
diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js
index c8a18af7198..d49d3af33d9 100644
--- a/spec/javascripts/boards/list_spec.js
+++ b/spec/javascripts/boards/list_spec.js
@@ -103,6 +103,7 @@ describe('List model', () => {
listDup.updateIssueLabel(list, issue);
- expect(gl.boardService.moveIssue).toHaveBeenCalledWith(issue.id, list.id, listDup.id);
+ expect(gl.boardService.moveIssue)
+ .toHaveBeenCalledWith(issue.id, list.id, listDup.id, undefined, undefined);
});
});
diff --git a/spec/javascripts/build_spec.js b/spec/javascripts/build_spec.js
index 0bd50588f5a..fe7f3d2e9c4 100644
--- a/spec/javascripts/build_spec.js
+++ b/spec/javascripts/build_spec.js
@@ -9,12 +9,6 @@ require('vendor/jquery.nicescroll');
describe('Build', () => {
const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/builds/1`;
- // see spec/factories/ci/builds.rb
- const BUILD_TRACE = 'BUILD TRACE';
- // see lib/ci/ansi2html.rb
- const INITIAL_BUILD_TRACE_STATE = window.btoa(JSON.stringify({
- offset: BUILD_TRACE.length, n_open_tags: 0, fg_color: null, bg_color: null, style_mask: 0,
- }));
preloadFixtures('builds/build-with-artifacts.html.raw');
@@ -42,7 +36,7 @@ describe('Build', () => {
expect(this.build.buildUrl).toBe(`${BUILD_URL}.json`);
expect(this.build.buildStatus).toBe('success');
expect(this.build.buildStage).toBe('test');
- expect(this.build.state).toBe(INITIAL_BUILD_TRACE_STATE);
+ expect(this.build.state).toBe('');
});
it('only shows the jobs matching the current stage', () => {
@@ -108,7 +102,7 @@ describe('Build', () => {
expect($.ajax.calls.count()).toBe(2);
let [{ url, dataType, success, context }] = $.ajax.calls.argsFor(1);
expect(url).toBe(
- `${BUILD_URL}/trace.json?state=${encodeURIComponent(INITIAL_BUILD_TRACE_STATE)}`,
+ `${BUILD_URL}/trace.json?state=`,
);
expect(dataType).toBe('json');
expect(success).toEqual(jasmine.any(Function));
diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js b/spec/javascripts/filtered_search/dropdown_user_spec.js
index fa9d03c8a9a..c16f77c53a2 100644
--- a/spec/javascripts/filtered_search/dropdown_user_spec.js
+++ b/spec/javascripts/filtered_search/dropdown_user_spec.js
@@ -18,9 +18,7 @@ require('~/filtered_search/dropdown_user');
it('should not return the double quote found in value', () => {
spyOn(gl.FilteredSearchTokenizer, 'processTokens').and.returnValue({
- lastToken: {
- value: '"johnny appleseed',
- },
+ lastToken: '"johnny appleseed',
});
expect(dropdownUser.getSearchInput()).toBe('johnny appleseed');
@@ -28,9 +26,7 @@ require('~/filtered_search/dropdown_user');
it('should not return the single quote found in value', () => {
spyOn(gl.FilteredSearchTokenizer, 'processTokens').and.returnValue({
- lastToken: {
- value: '\'larry boy',
- },
+ lastToken: '\'larry boy',
});
expect(dropdownUser.getSearchInput()).toBe('larry boy');
diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js
index 1e2d7582d5b..5c65903701b 100644
--- a/spec/javascripts/filtered_search/dropdown_utils_spec.js
+++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js
@@ -45,7 +45,7 @@ require('~/filtered_search/filtered_search_dropdown_manager');
});
it('should filter without symbol', () => {
- input.value = ':roo';
+ input.value = 'roo';
const updatedItem = gl.DropdownUtils.filterWithSymbol('@', input, item);
expect(updatedItem.droplab_hidden).toBe(false);
@@ -58,69 +58,62 @@ require('~/filtered_search/filtered_search_dropdown_manager');
expect(updatedItem.droplab_hidden).toBe(false);
});
- it('should filter with colon', () => {
- input.value = 'roo';
-
- const updatedItem = gl.DropdownUtils.filterWithSymbol('@', input, item);
- expect(updatedItem.droplab_hidden).toBe(false);
- });
-
describe('filters multiple word title', () => {
const multipleWordItem = {
title: 'Community Contributions',
};
it('should filter with double quote', () => {
- input.value = 'label:"';
+ input.value = '"';
const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
expect(updatedItem.droplab_hidden).toBe(false);
});
it('should filter with double quote and symbol', () => {
- input.value = 'label:~"';
+ input.value = '~"';
const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
expect(updatedItem.droplab_hidden).toBe(false);
});
it('should filter with double quote and multiple words', () => {
- input.value = 'label:"community con';
+ input.value = '"community con';
const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
expect(updatedItem.droplab_hidden).toBe(false);
});
it('should filter with double quote, symbol and multiple words', () => {
- input.value = 'label:~"community con';
+ input.value = '~"community con';
const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
expect(updatedItem.droplab_hidden).toBe(false);
});
it('should filter with single quote', () => {
- input.value = 'label:\'';
+ input.value = '\'';
const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
expect(updatedItem.droplab_hidden).toBe(false);
});
it('should filter with single quote and symbol', () => {
- input.value = 'label:~\'';
+ input.value = '~\'';
const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
expect(updatedItem.droplab_hidden).toBe(false);
});
it('should filter with single quote and multiple words', () => {
- input.value = 'label:\'community con';
+ input.value = '\'community con';
const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
expect(updatedItem.droplab_hidden).toBe(false);
});
it('should filter with single quote, symbol and multiple words', () => {
- input.value = 'label:~\'community con';
+ input.value = '~\'community con';
const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
expect(updatedItem.droplab_hidden).toBe(false);
diff --git a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js
index ed0b0196ec4..a1da3396d7b 100644
--- a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js
@@ -1,4 +1,5 @@
require('~/extensions/array');
+require('~/filtered_search/filtered_search_visual_tokens');
require('~/filtered_search/filtered_search_tokenizer');
require('~/filtered_search/filtered_search_dropdown_manager');
@@ -14,24 +15,44 @@ require('~/filtered_search/filtered_search_dropdown_manager');
}
beforeEach(() => {
- const input = document.createElement('input');
- input.classList.add('filtered-search');
- document.body.appendChild(input);
- });
-
- afterEach(() => {
- document.querySelector('.filtered-search').outerHTML = '';
+ setFixtures(`
+ <ul class="tokens-container">
+ <li class="input-token">
+ <input class="filtered-search">
+ </li>
+ </ul>
+ `);
});
describe('input has no existing value', () => {
it('should add just tokenName', () => {
gl.FilteredSearchDropdownManager.addWordToInput('milestone');
- expect(getInputValue()).toBe('milestone:');
+
+ const token = document.querySelector('.tokens-container .js-visual-token');
+
+ expect(token.classList.contains('filtered-search-token')).toEqual(true);
+ expect(token.querySelector('.name').innerText).toBe('milestone');
+ expect(getInputValue()).toBe('');
});
it('should add tokenName and tokenValue', () => {
+ gl.FilteredSearchDropdownManager.addWordToInput('label');
+
+ let token = document.querySelector('.tokens-container .js-visual-token');
+
+ expect(token.classList.contains('filtered-search-token')).toEqual(true);
+ expect(token.querySelector('.name').innerText).toBe('label');
+ expect(getInputValue()).toBe('');
+
gl.FilteredSearchDropdownManager.addWordToInput('label', 'none');
- expect(getInputValue()).toBe('label:none ');
+ // We have to get that reference again
+ // Because gl.FilteredSearchDropdownManager deletes the previous token
+ token = document.querySelector('.tokens-container .js-visual-token');
+
+ expect(token.classList.contains('filtered-search-token')).toEqual(true);
+ expect(token.querySelector('.name').innerText).toBe('label');
+ expect(token.querySelector('.value').innerText).toBe('none');
+ expect(getInputValue()).toBe('');
});
});
@@ -39,19 +60,40 @@ require('~/filtered_search/filtered_search_dropdown_manager');
it('should be able to just add tokenName', () => {
setInputValue('a');
gl.FilteredSearchDropdownManager.addWordToInput('author');
- expect(getInputValue()).toBe('author:');
+
+ const token = document.querySelector('.tokens-container .js-visual-token');
+
+ expect(token.classList.contains('filtered-search-token')).toEqual(true);
+ expect(token.querySelector('.name').innerText).toBe('author');
+ expect(getInputValue()).toBe('');
});
it('should replace tokenValue', () => {
- setInputValue('author:roo');
- gl.FilteredSearchDropdownManager.addWordToInput('author', '@root');
- expect(getInputValue()).toBe('author:@root ');
+ gl.FilteredSearchDropdownManager.addWordToInput('author');
+
+ setInputValue('roo');
+ gl.FilteredSearchDropdownManager.addWordToInput(null, '@root');
+
+ const token = document.querySelector('.tokens-container .js-visual-token');
+
+ expect(token.classList.contains('filtered-search-token')).toEqual(true);
+ expect(token.querySelector('.name').innerText).toBe('author');
+ expect(token.querySelector('.value').innerText).toBe('@root');
+ expect(getInputValue()).toBe('');
});
it('should add tokenValues containing spaces', () => {
- setInputValue('label:~"test');
+ gl.FilteredSearchDropdownManager.addWordToInput('label');
+
+ setInputValue('"test ');
gl.FilteredSearchDropdownManager.addWordToInput('label', '~\'"test me"\'');
- expect(getInputValue()).toBe('label:~\'"test me"\' ');
+
+ const token = document.querySelector('.tokens-container .js-visual-token');
+
+ expect(token.classList.contains('filtered-search-token')).toEqual(true);
+ expect(token.querySelector('.name').innerText).toBe('label');
+ expect(token.querySelector('.value').innerText).toBe('~\'"test me"\'');
+ expect(getInputValue()).toBe('');
});
});
});
diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_manager_spec.js
index 98959dda242..81c1d81d181 100644
--- a/spec/javascripts/filtered_search/filtered_search_manager_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js
@@ -4,64 +4,244 @@ require('~/filtered_search/filtered_search_token_keys');
require('~/filtered_search/filtered_search_tokenizer');
require('~/filtered_search/filtered_search_dropdown_manager');
require('~/filtered_search/filtered_search_manager');
+const FilteredSearchSpecHelper = require('../helpers/filtered_search_spec_helper');
(() => {
describe('Filtered Search Manager', () => {
- describe('search', () => {
- let manager;
- const defaultParams = '?scope=all&utf8=✓&state=opened';
+ let input;
+ let manager;
+ let tokensContainer;
+ const placeholder = 'Search or filter results...';
- function getInput() {
- return document.querySelector('.filtered-search');
- }
+ function dispatchBackspaceEvent(element, eventType) {
+ const backspaceKey = 8;
+ const event = new Event(eventType);
+ event.keyCode = backspaceKey;
+ element.dispatchEvent(event);
+ }
- beforeEach(() => {
- setFixtures(`
- <input type='text' class='filtered-search' />
- `);
+ function dispatchDeleteEvent(element, eventType) {
+ const deleteKey = 46;
+ const event = new Event(eventType);
+ event.keyCode = deleteKey;
+ element.dispatchEvent(event);
+ }
- spyOn(gl.FilteredSearchManager.prototype, 'bindEvents').and.callFake(() => {});
- spyOn(gl.FilteredSearchManager.prototype, 'cleanup').and.callFake(() => {});
- spyOn(gl.FilteredSearchManager.prototype, 'loadSearchParamsFromURL').and.callFake(() => {});
- spyOn(gl.FilteredSearchDropdownManager.prototype, 'setDropdown').and.callFake(() => {});
- spyOn(gl.utils, 'getParameterByName').and.returnValue(null);
+ beforeEach(() => {
+ setFixtures(`
+ <div class="filtered-search-input-container">
+ <form>
+ <ul class="tokens-container list-unstyled">
+ ${FilteredSearchSpecHelper.createInputHTML(placeholder)}
+ </ul>
+ <button class="clear-search" type="button">
+ <i class="fa fa-times"></i>
+ </button>
+ </form>
+ </div>
+ `);
- manager = new gl.FilteredSearchManager();
- });
+ spyOn(gl.FilteredSearchManager.prototype, 'cleanup').and.callFake(() => {});
+ spyOn(gl.FilteredSearchManager.prototype, 'loadSearchParamsFromURL').and.callFake(() => {});
+ spyOn(gl.FilteredSearchManager.prototype, 'tokenChange').and.callFake(() => {});
+ spyOn(gl.FilteredSearchDropdownManager.prototype, 'setDropdown').and.callFake(() => {});
+ spyOn(gl.FilteredSearchDropdownManager.prototype, 'updateDropdownOffset').and.callFake(() => {});
+ spyOn(gl.utils, 'getParameterByName').and.returnValue(null);
+ spyOn(gl.FilteredSearchVisualTokens, 'unselectTokens').and.callThrough();
- afterEach(() => {
- getInput().outerHTML = '';
- });
+ input = document.querySelector('.filtered-search');
+ tokensContainer = document.querySelector('.tokens-container');
+ manager = new gl.FilteredSearchManager();
+ });
- it('should search with a single word', () => {
- getInput().value = 'searchTerm';
+ describe('search', () => {
+ const defaultParams = '?scope=all&utf8=✓&state=opened';
+
+ it('should search with a single word', (done) => {
+ input.value = 'searchTerm';
spyOn(gl.utils, 'visitUrl').and.callFake((url) => {
expect(url).toEqual(`${defaultParams}&search=searchTerm`);
+ done();
});
manager.search();
});
- it('should search with multiple words', () => {
- getInput().value = 'awesome search terms';
+ it('should search with multiple words', (done) => {
+ input.value = 'awesome search terms';
spyOn(gl.utils, 'visitUrl').and.callFake((url) => {
expect(url).toEqual(`${defaultParams}&search=awesome+search+terms`);
+ done();
});
manager.search();
});
- it('should search with special characters', () => {
- getInput().value = '~!@#$%^&*()_+{}:<>,.?/';
+ it('should search with special characters', (done) => {
+ input.value = '~!@#$%^&*()_+{}:<>,.?/';
spyOn(gl.utils, 'visitUrl').and.callFake((url) => {
expect(url).toEqual(`${defaultParams}&search=~!%40%23%24%25%5E%26*()_%2B%7B%7D%3A%3C%3E%2C.%3F%2F`);
+ done();
});
manager.search();
});
});
+
+ describe('handleInputPlaceholder', () => {
+ it('should render placeholder when there is no input', () => {
+ expect(input.placeholder).toEqual(placeholder);
+ });
+
+ it('should not render placeholder when there is input', () => {
+ input.value = 'test words';
+
+ const event = new Event('input');
+ input.dispatchEvent(event);
+
+ expect(input.placeholder).toEqual('');
+ });
+
+ it('should not render placeholder when there are tokens and no input', () => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
+ FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug'),
+ );
+
+ const event = new Event('input');
+ input.dispatchEvent(event);
+
+ expect(input.placeholder).toEqual('');
+ });
+ });
+
+ describe('checkForBackspace', () => {
+ describe('tokens and no input', () => {
+ beforeEach(() => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
+ FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug'),
+ );
+ });
+
+ it('removes last token', () => {
+ spyOn(gl.FilteredSearchVisualTokens, 'removeLastTokenPartial').and.callThrough();
+ dispatchBackspaceEvent(input, 'keyup');
+
+ expect(gl.FilteredSearchVisualTokens.removeLastTokenPartial).toHaveBeenCalled();
+ });
+
+ it('sets the input', () => {
+ spyOn(gl.FilteredSearchVisualTokens, 'getLastTokenPartial').and.callThrough();
+ dispatchDeleteEvent(input, 'keyup');
+
+ expect(gl.FilteredSearchVisualTokens.getLastTokenPartial).toHaveBeenCalled();
+ expect(input.value).toEqual('~bug');
+ });
+ });
+
+ it('does not remove token or change input when there is existing input', () => {
+ spyOn(gl.FilteredSearchVisualTokens, 'removeLastTokenPartial').and.callThrough();
+ spyOn(gl.FilteredSearchVisualTokens, 'getLastTokenPartial').and.callThrough();
+
+ input.value = 'text';
+ dispatchDeleteEvent(input, 'keyup');
+
+ expect(gl.FilteredSearchVisualTokens.removeLastTokenPartial).not.toHaveBeenCalled();
+ expect(gl.FilteredSearchVisualTokens.getLastTokenPartial).not.toHaveBeenCalled();
+ expect(input.value).toEqual('text');
+ });
+ });
+
+ describe('removeSelectedToken', () => {
+ function getVisualTokens() {
+ return tokensContainer.querySelectorAll('.js-visual-token');
+ }
+
+ beforeEach(() => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
+ FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'none', true),
+ );
+ });
+
+ it('removes selected token when the backspace key is pressed', () => {
+ expect(getVisualTokens().length).toEqual(1);
+
+ dispatchBackspaceEvent(document, 'keydown');
+
+ expect(getVisualTokens().length).toEqual(0);
+ });
+
+ it('removes selected token when the delete key is pressed', () => {
+ expect(getVisualTokens().length).toEqual(1);
+
+ dispatchDeleteEvent(document, 'keydown');
+
+ expect(getVisualTokens().length).toEqual(0);
+ });
+
+ it('updates the input placeholder after removal', () => {
+ manager.handleInputPlaceholder();
+
+ expect(input.placeholder).toEqual('');
+ expect(getVisualTokens().length).toEqual(1);
+
+ dispatchBackspaceEvent(document, 'keydown');
+
+ expect(input.placeholder).not.toEqual('');
+ expect(getVisualTokens().length).toEqual(0);
+ });
+
+ it('updates the clear button after removal', () => {
+ manager.toggleClearSearchButton();
+
+ const clearButton = document.querySelector('.clear-search');
+
+ expect(clearButton.classList.contains('hidden')).toEqual(false);
+ expect(getVisualTokens().length).toEqual(1);
+
+ dispatchBackspaceEvent(document, 'keydown');
+
+ expect(clearButton.classList.contains('hidden')).toEqual(true);
+ expect(getVisualTokens().length).toEqual(0);
+ });
+ });
+
+ describe('unselects token', () => {
+ beforeEach(() => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
+ ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug', true)}
+ ${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search term')}
+ ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~awesome')}
+ `);
+ });
+
+ it('unselects token when input is clicked', () => {
+ const selectedToken = tokensContainer.querySelector('.js-visual-token .selected');
+
+ expect(selectedToken.classList.contains('selected')).toEqual(true);
+ expect(gl.FilteredSearchVisualTokens.unselectTokens).not.toHaveBeenCalled();
+
+ // Click directly on input attached to document
+ // so that the click event will propagate properly
+ document.querySelector('.filtered-search').click();
+
+ expect(gl.FilteredSearchVisualTokens.unselectTokens).toHaveBeenCalled();
+ expect(selectedToken.classList.contains('selected')).toEqual(false);
+ });
+
+ it('unselects token when document.body is clicked', () => {
+ const selectedToken = tokensContainer.querySelector('.js-visual-token .selected');
+
+ expect(selectedToken.classList.contains('selected')).toEqual(true);
+ expect(gl.FilteredSearchVisualTokens.unselectTokens).not.toHaveBeenCalled();
+
+ document.body.click();
+
+ expect(selectedToken.classList.contains('selected')).toEqual(false);
+ expect(gl.FilteredSearchVisualTokens.unselectTokens).toHaveBeenCalled();
+ });
+ });
});
})();
diff --git a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
new file mode 100644
index 00000000000..bbda1476fed
--- /dev/null
+++ b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
@@ -0,0 +1,600 @@
+require('~/filtered_search/filtered_search_visual_tokens');
+const FilteredSearchSpecHelper = require('../helpers/filtered_search_spec_helper');
+
+describe('Filtered Search Visual Tokens', () => {
+ let tokensContainer;
+
+ beforeEach(() => {
+ setFixtures(`
+ <ul class="tokens-container">
+ ${FilteredSearchSpecHelper.createInputHTML()}
+ </ul>
+ `);
+ tokensContainer = document.querySelector('.tokens-container');
+ });
+
+ describe('getLastVisualTokenBeforeInput', () => {
+ it('returns when there are no visual tokens', () => {
+ const { lastVisualToken, isLastVisualTokenValid }
+ = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+
+ expect(lastVisualToken).toEqual(null);
+ expect(isLastVisualTokenValid).toEqual(true);
+ });
+
+ describe('input is the last item in tokensContainer', () => {
+ it('returns when there is one visual token', () => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
+ FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug'),
+ );
+
+ const { lastVisualToken, isLastVisualTokenValid }
+ = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+
+ expect(lastVisualToken).toEqual(document.querySelector('.filtered-search-token'));
+ expect(isLastVisualTokenValid).toEqual(true);
+ });
+
+ it('returns when there is an incomplete visual token', () => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
+ FilteredSearchSpecHelper.createNameFilterVisualTokenHTML('Author'),
+ );
+
+ const { lastVisualToken, isLastVisualTokenValid }
+ = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+
+ expect(lastVisualToken).toEqual(document.querySelector('.filtered-search-token'));
+ expect(isLastVisualTokenValid).toEqual(false);
+ });
+
+ it('returns when there are multiple visual tokens', () => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
+ ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')}
+ ${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search term')}
+ ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '@root')}
+ `);
+
+ const { lastVisualToken, isLastVisualTokenValid }
+ = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+ const items = document.querySelectorAll('.tokens-container .js-visual-token');
+
+ expect(lastVisualToken.isEqualNode(items[items.length - 1])).toEqual(true);
+ expect(isLastVisualTokenValid).toEqual(true);
+ });
+
+ it('returns when there are multiple visual tokens and an incomplete visual token', () => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
+ ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')}
+ ${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search term')}
+ ${FilteredSearchSpecHelper.createNameFilterVisualTokenHTML('assignee')}
+ `);
+
+ const { lastVisualToken, isLastVisualTokenValid }
+ = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+ const items = document.querySelectorAll('.tokens-container .js-visual-token');
+
+ expect(lastVisualToken.isEqualNode(items[items.length - 1])).toEqual(true);
+ expect(isLastVisualTokenValid).toEqual(false);
+ });
+ });
+
+ describe('input is a middle item in tokensContainer', () => {
+ it('returns last token before input', () => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
+ ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')}
+ ${FilteredSearchSpecHelper.createInputHTML()}
+ ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '@root')}
+ `);
+
+ const { lastVisualToken, isLastVisualTokenValid }
+ = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+
+ expect(lastVisualToken).toEqual(document.querySelector('.filtered-search-token'));
+ expect(isLastVisualTokenValid).toEqual(true);
+ });
+
+ it('returns last partial token before input', () => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
+ ${FilteredSearchSpecHelper.createNameFilterVisualTokenHTML('label')}
+ ${FilteredSearchSpecHelper.createInputHTML()}
+ ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '@root')}
+ `);
+
+ const { lastVisualToken, isLastVisualTokenValid }
+ = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+
+ expect(lastVisualToken).toEqual(document.querySelector('.filtered-search-token'));
+ expect(isLastVisualTokenValid).toEqual(false);
+ });
+ });
+ });
+
+ describe('unselectTokens', () => {
+ it('does nothing when there are no tokens', () => {
+ const beforeHTML = tokensContainer.innerHTML;
+ gl.FilteredSearchVisualTokens.unselectTokens();
+
+ expect(tokensContainer.innerHTML).toEqual(beforeHTML);
+ });
+
+ it('removes the selected class from buttons', () => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
+ ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '@author')}
+ ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', '%123', true)}
+ `);
+
+ const selected = tokensContainer.querySelector('.js-visual-token .selected');
+ expect(selected.classList.contains('selected')).toEqual(true);
+
+ gl.FilteredSearchVisualTokens.unselectTokens();
+
+ expect(selected.classList.contains('selected')).toEqual(false);
+ });
+ });
+
+ describe('selectToken', () => {
+ beforeEach(() => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
+ ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')}
+ ${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search term')}
+ ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~awesome')}
+ `);
+ });
+
+ it('removes the selected class if it has selected class', () => {
+ const firstTokenButton = tokensContainer.querySelector('.js-visual-token .selectable');
+ firstTokenButton.classList.add('selected');
+
+ gl.FilteredSearchVisualTokens.selectToken(firstTokenButton);
+
+ expect(firstTokenButton.classList.contains('selected')).toEqual(false);
+ });
+
+ describe('has no selected class', () => {
+ it('adds selected class', () => {
+ const firstTokenButton = tokensContainer.querySelector('.js-visual-token .selectable');
+
+ gl.FilteredSearchVisualTokens.selectToken(firstTokenButton);
+
+ expect(firstTokenButton.classList.contains('selected')).toEqual(true);
+ });
+
+ it('removes selected class from other tokens', () => {
+ const tokenButtons = tokensContainer.querySelectorAll('.js-visual-token .selectable');
+ tokenButtons[1].classList.add('selected');
+
+ gl.FilteredSearchVisualTokens.selectToken(tokenButtons[0]);
+
+ expect(tokenButtons[0].classList.contains('selected')).toEqual(true);
+ expect(tokenButtons[1].classList.contains('selected')).toEqual(false);
+ });
+ });
+ });
+
+ describe('removeSelectedToken', () => {
+ it('does not remove when there are no selected tokens', () => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
+ FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'none'),
+ );
+
+ expect(tokensContainer.querySelector('.js-visual-token .selectable')).not.toEqual(null);
+
+ gl.FilteredSearchVisualTokens.removeSelectedToken();
+
+ expect(tokensContainer.querySelector('.js-visual-token .selectable')).not.toEqual(null);
+ });
+
+ it('removes selected token', () => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
+ FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'none', true),
+ );
+
+ expect(tokensContainer.querySelector('.js-visual-token .selectable')).not.toEqual(null);
+
+ gl.FilteredSearchVisualTokens.removeSelectedToken();
+
+ expect(tokensContainer.querySelector('.js-visual-token .selectable')).toEqual(null);
+ });
+ });
+
+ describe('createVisualTokenElementHTML', () => {
+ let tokenElement;
+
+ beforeEach(() => {
+ setFixtures(`
+ <div class="test-area">
+ ${gl.FilteredSearchVisualTokens.createVisualTokenElementHTML()}
+ </div>
+ `);
+
+ tokenElement = document.querySelector('.test-area').firstElementChild;
+ });
+
+ it('contains name div', () => {
+ expect(tokenElement.querySelector('.name')).toEqual(jasmine.anything());
+ });
+
+ it('contains value div', () => {
+ expect(tokenElement.querySelector('.value')).toEqual(jasmine.anything());
+ });
+
+ it('contains selectable class', () => {
+ expect(tokenElement.classList.contains('selectable')).toEqual(true);
+ });
+
+ it('contains button role', () => {
+ expect(tokenElement.getAttribute('role')).toEqual('button');
+ });
+ });
+
+ describe('addVisualTokenElement', () => {
+ it('renders search visual tokens', () => {
+ gl.FilteredSearchVisualTokens.addVisualTokenElement('search term', null, true);
+ const token = tokensContainer.querySelector('.js-visual-token');
+
+ expect(token.classList.contains('filtered-search-term')).toEqual(true);
+ expect(token.querySelector('.name').innerText).toEqual('search term');
+ expect(token.querySelector('.value')).toEqual(null);
+ });
+
+ it('renders filter visual token name', () => {
+ gl.FilteredSearchVisualTokens.addVisualTokenElement('milestone');
+ const token = tokensContainer.querySelector('.js-visual-token');
+
+ expect(token.classList.contains('filtered-search-token')).toEqual(true);
+ expect(token.querySelector('.name').innerText).toEqual('milestone');
+ expect(token.querySelector('.value')).toEqual(null);
+ });
+
+ it('renders filter visual token name and value', () => {
+ gl.FilteredSearchVisualTokens.addVisualTokenElement('label', 'Frontend');
+ const token = tokensContainer.querySelector('.js-visual-token');
+
+ expect(token.classList.contains('filtered-search-token')).toEqual(true);
+ expect(token.querySelector('.name').innerText).toEqual('label');
+ expect(token.querySelector('.value').innerText).toEqual('Frontend');
+ });
+
+ it('inserts visual token before input', () => {
+ tokensContainer.appendChild(FilteredSearchSpecHelper.createFilterVisualToken('assignee', '@root'));
+
+ gl.FilteredSearchVisualTokens.addVisualTokenElement('label', 'Frontend');
+ const tokens = tokensContainer.querySelectorAll('.js-visual-token');
+ const labelToken = tokens[0];
+ const assigneeToken = tokens[1];
+
+ expect(labelToken.classList.contains('filtered-search-token')).toEqual(true);
+ expect(labelToken.querySelector('.name').innerText).toEqual('label');
+ expect(labelToken.querySelector('.value').innerText).toEqual('Frontend');
+
+ expect(assigneeToken.classList.contains('filtered-search-token')).toEqual(true);
+ expect(assigneeToken.querySelector('.name').innerText).toEqual('assignee');
+ expect(assigneeToken.querySelector('.value').innerText).toEqual('@root');
+ });
+ });
+
+ describe('addValueToPreviousVisualTokenElement', () => {
+ it('does not add when previous visual token element has no value', () => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
+ FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '@root'),
+ );
+
+ const original = tokensContainer.innerHTML;
+ gl.FilteredSearchVisualTokens.addValueToPreviousVisualTokenElement('value');
+
+ expect(original).toEqual(tokensContainer.innerHTML);
+ });
+
+ it('does not add when previous visual token element is a search', () => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
+ ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '@root')}
+ ${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search term')}
+ `);
+
+ const original = tokensContainer.innerHTML;
+ gl.FilteredSearchVisualTokens.addValueToPreviousVisualTokenElement('value');
+
+ expect(original).toEqual(tokensContainer.innerHTML);
+ });
+
+ it('adds value to previous visual filter token', () => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
+ FilteredSearchSpecHelper.createNameFilterVisualTokenHTML('label'),
+ );
+
+ const original = tokensContainer.innerHTML;
+ gl.FilteredSearchVisualTokens.addValueToPreviousVisualTokenElement('value');
+ const updatedToken = tokensContainer.querySelector('.js-visual-token');
+
+ expect(updatedToken.querySelector('.name').innerText).toEqual('label');
+ expect(updatedToken.querySelector('.value').innerText).toEqual('value');
+ expect(original).not.toEqual(tokensContainer.innerHTML);
+ });
+ });
+
+ describe('addFilterVisualToken', () => {
+ it('creates visual token with just tokenName', () => {
+ gl.FilteredSearchVisualTokens.addFilterVisualToken('milestone');
+ const token = tokensContainer.querySelector('.js-visual-token');
+
+ expect(token.classList.contains('filtered-search-token')).toEqual(true);
+ expect(token.querySelector('.name').innerText).toEqual('milestone');
+ expect(token.querySelector('.value')).toEqual(null);
+ });
+
+ it('creates visual token with just tokenValue', () => {
+ gl.FilteredSearchVisualTokens.addFilterVisualToken('milestone');
+ gl.FilteredSearchVisualTokens.addFilterVisualToken('%8.17');
+ const token = tokensContainer.querySelector('.js-visual-token');
+
+ expect(token.classList.contains('filtered-search-token')).toEqual(true);
+ expect(token.querySelector('.name').innerText).toEqual('milestone');
+ expect(token.querySelector('.value').innerText).toEqual('%8.17');
+ });
+
+ it('creates full visual token', () => {
+ gl.FilteredSearchVisualTokens.addFilterVisualToken('assignee', '@john');
+ const token = tokensContainer.querySelector('.js-visual-token');
+
+ expect(token.classList.contains('filtered-search-token')).toEqual(true);
+ expect(token.querySelector('.name').innerText).toEqual('assignee');
+ expect(token.querySelector('.value').innerText).toEqual('@john');
+ });
+ });
+
+ describe('addSearchVisualToken', () => {
+ it('creates search visual token', () => {
+ gl.FilteredSearchVisualTokens.addSearchVisualToken('search term');
+ const token = tokensContainer.querySelector('.js-visual-token');
+
+ expect(token.classList.contains('filtered-search-term')).toEqual(true);
+ expect(token.querySelector('.name').innerText).toEqual('search term');
+ expect(token.querySelector('.value')).toEqual(null);
+ });
+
+ it('appends to previous search visual token if previous token was a search token', () => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
+ ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '@root')}
+ ${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search term')}
+ `);
+
+ gl.FilteredSearchVisualTokens.addSearchVisualToken('append this');
+ const token = tokensContainer.querySelector('.filtered-search-term');
+
+ expect(token.querySelector('.name').innerText).toEqual('search term append this');
+ expect(token.querySelector('.value')).toEqual(null);
+ });
+ });
+
+ describe('getLastTokenPartial', () => {
+ it('should get last token value', () => {
+ const value = '~bug';
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
+ FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', value),
+ );
+
+ expect(gl.FilteredSearchVisualTokens.getLastTokenPartial()).toEqual(value);
+ });
+
+ it('should get last token name if there is no value', () => {
+ const name = 'assignee';
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
+ FilteredSearchSpecHelper.createNameFilterVisualTokenHTML(name),
+ );
+
+ expect(gl.FilteredSearchVisualTokens.getLastTokenPartial()).toEqual(name);
+ });
+
+ it('should return empty when there are no tokens', () => {
+ expect(gl.FilteredSearchVisualTokens.getLastTokenPartial()).toEqual('');
+ });
+ });
+
+ describe('removeLastTokenPartial', () => {
+ it('should remove the last token value if it exists', () => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
+ FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~"Community Contribution"'),
+ );
+
+ expect(tokensContainer.querySelector('.js-visual-token .value')).not.toEqual(null);
+
+ gl.FilteredSearchVisualTokens.removeLastTokenPartial();
+
+ expect(tokensContainer.querySelector('.js-visual-token .value')).toEqual(null);
+ });
+
+ it('should remove the last token name if there is no value', () => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
+ FilteredSearchSpecHelper.createNameFilterVisualTokenHTML('milestone'),
+ );
+
+ expect(tokensContainer.querySelector('.js-visual-token .name')).not.toEqual(null);
+
+ gl.FilteredSearchVisualTokens.removeLastTokenPartial();
+
+ expect(tokensContainer.querySelector('.js-visual-token .name')).toEqual(null);
+ });
+
+ it('should not remove anything when there are no tokens', () => {
+ const html = tokensContainer.innerHTML;
+ gl.FilteredSearchVisualTokens.removeLastTokenPartial();
+
+ expect(tokensContainer.innerHTML).toEqual(html);
+ });
+ });
+
+ describe('tokenizeInput', () => {
+ it('does not do anything if there is no input', () => {
+ const original = tokensContainer.innerHTML;
+ gl.FilteredSearchVisualTokens.tokenizeInput();
+
+ expect(tokensContainer.innerHTML).toEqual(original);
+ });
+
+ it('adds search visual token if previous visual token is valid', () => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
+ FilteredSearchSpecHelper.createFilterVisualTokenHTML('assignee', 'none'),
+ );
+
+ const input = document.querySelector('.filtered-search');
+ input.value = 'some value';
+ gl.FilteredSearchVisualTokens.tokenizeInput();
+
+ const newToken = tokensContainer.querySelector('.filtered-search-term');
+
+ expect(input.value).toEqual('');
+ expect(newToken.querySelector('.name').innerText).toEqual('some value');
+ expect(newToken.querySelector('.value')).toEqual(null);
+ });
+
+ it('adds value to previous visual token element if previous visual token is invalid', () => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
+ FilteredSearchSpecHelper.createNameFilterVisualTokenHTML('assignee'),
+ );
+
+ const input = document.querySelector('.filtered-search');
+ input.value = '@john';
+ gl.FilteredSearchVisualTokens.tokenizeInput();
+
+ const updatedToken = tokensContainer.querySelector('.filtered-search-token');
+
+ expect(input.value).toEqual('');
+ expect(updatedToken.querySelector('.name').innerText).toEqual('assignee');
+ expect(updatedToken.querySelector('.value').innerText).toEqual('@john');
+ });
+ });
+
+ describe('editToken', () => {
+ let input;
+ let token;
+
+ beforeEach(() => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
+ ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', 'none')}
+ ${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search')}
+ ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'upcoming')}
+ `);
+
+ input = document.querySelector('.filtered-search');
+ token = document.querySelector('.js-visual-token');
+ });
+
+ it('tokenize\'s existing input', () => {
+ input.value = 'some text';
+ spyOn(gl.FilteredSearchVisualTokens, 'tokenizeInput').and.callThrough();
+
+ gl.FilteredSearchVisualTokens.editToken(token);
+
+ expect(gl.FilteredSearchVisualTokens.tokenizeInput).toHaveBeenCalled();
+ expect(input.value).not.toEqual('some text');
+ });
+
+ it('moves input to the token position', () => {
+ expect(tokensContainer.children[3].querySelector('.filtered-search')).not.toEqual(null);
+
+ gl.FilteredSearchVisualTokens.editToken(token);
+
+ expect(tokensContainer.children[1].querySelector('.filtered-search')).not.toEqual(null);
+ expect(tokensContainer.children[3].querySelector('.filtered-search')).toEqual(null);
+ });
+
+ it('input contains the visual token value', () => {
+ gl.FilteredSearchVisualTokens.editToken(token);
+
+ expect(input.value).toEqual('none');
+ });
+
+ describe('selected token is a search term token', () => {
+ beforeEach(() => {
+ token = document.querySelector('.filtered-search-term');
+ });
+
+ it('token is removed', () => {
+ expect(tokensContainer.querySelector('.filtered-search-term')).not.toEqual(null);
+
+ gl.FilteredSearchVisualTokens.editToken(token);
+
+ expect(tokensContainer.querySelector('.filtered-search-term')).toEqual(null);
+ });
+
+ it('input has the same value as removed token', () => {
+ expect(input.value).toEqual('');
+
+ gl.FilteredSearchVisualTokens.editToken(token);
+
+ expect(input.value).toEqual('search');
+ });
+ });
+ });
+
+ describe('moveInputTotheRight', () => {
+ it('does nothing if the input is already the right most element', () => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
+ FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', 'none'),
+ );
+
+ spyOn(gl.FilteredSearchVisualTokens, 'tokenizeInput').and.callFake(() => {});
+ spyOn(gl.FilteredSearchVisualTokens, 'getLastVisualTokenBeforeInput').and.callThrough();
+
+ gl.FilteredSearchVisualTokens.moveInputToTheRight();
+
+ expect(gl.FilteredSearchVisualTokens.tokenizeInput).toHaveBeenCalled();
+ expect(gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput).not.toHaveBeenCalled();
+ });
+
+ it('tokenize\'s input', () => {
+ tokensContainer.innerHTML = `
+ ${FilteredSearchSpecHelper.createNameFilterVisualTokenHTML('label')}
+ ${FilteredSearchSpecHelper.createInputHTML()}
+ ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')}
+ `;
+
+ document.querySelector('.filtered-search').value = 'none';
+
+ gl.FilteredSearchVisualTokens.moveInputToTheRight();
+ const value = tokensContainer.querySelector('.js-visual-token .value');
+
+ expect(value.innerText).toEqual('none');
+ });
+
+ it('converts input into search term token if last token is valid', () => {
+ tokensContainer.innerHTML = `
+ ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', 'none')}
+ ${FilteredSearchSpecHelper.createInputHTML()}
+ ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')}
+ `;
+
+ document.querySelector('.filtered-search').value = 'test';
+
+ gl.FilteredSearchVisualTokens.moveInputToTheRight();
+ const searchValue = tokensContainer.querySelector('.filtered-search-term .name');
+
+ expect(searchValue.innerText).toEqual('test');
+ });
+
+ it('moves the input to the right most element', () => {
+ tokensContainer.innerHTML = `
+ ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', 'none')}
+ ${FilteredSearchSpecHelper.createInputHTML()}
+ ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')}
+ `;
+
+ gl.FilteredSearchVisualTokens.moveInputToTheRight();
+
+ expect(tokensContainer.children[2].querySelector('.filtered-search')).not.toEqual(null);
+ });
+
+ it('tokenizes input even if input is the right most element', () => {
+ tokensContainer.innerHTML = `
+ ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', 'none')}
+ ${FilteredSearchSpecHelper.createNameFilterVisualTokenHTML('label')}
+ ${FilteredSearchSpecHelper.createInputHTML('', '~bug')}
+ `;
+
+ gl.FilteredSearchVisualTokens.moveInputToTheRight();
+
+ const token = tokensContainer.children[1];
+ expect(token.querySelector('.value').innerText).toEqual('~bug');
+ });
+ });
+});
diff --git a/spec/javascripts/fixtures/emoji_menu.js b/spec/javascripts/fixtures/emoji_menu.js
deleted file mode 100644
index a50812d9517..00000000000
--- a/spec/javascripts/fixtures/emoji_menu.js
+++ /dev/null
@@ -1,4 +0,0 @@
-/* eslint-disable space-before-function-paren */
-(function() {
- window.emojiMenu = "<div class='emoji-menu'>\n <input type=\"text\" name=\"emoji_search\" id=\"emoji_search\" value=\"\" class=\"emoji-search search-input form-control\" />\n <div class='emoji-menu-content'>\n <h5 class='emoji-menu-title'>\n Emoticons\n </h5>\n <ul class='clearfix emoji-menu-list'>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47D\" title=\"alien\" data-aliases=\"\" data-emoji=\"alien\" data-unicode-name=\"1F47D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47C\" title=\"angel\" data-aliases=\"\" data-emoji=\"angel\" data-unicode-name=\"1F47C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A2\" title=\"anger\" data-aliases=\"\" data-emoji=\"anger\" data-unicode-name=\"1F4A2\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F620\" title=\"angry\" data-aliases=\"\" data-emoji=\"angry\" data-unicode-name=\"1F620\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F627\" title=\"anguished\" data-aliases=\"\" data-emoji=\"anguished\" data-unicode-name=\"1F627\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F632\" title=\"astonished\" data-aliases=\"\" data-emoji=\"astonished\" data-unicode-name=\"1F632\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45F\" title=\"athletic_shoe\" data-aliases=\"\" data-emoji=\"athletic_shoe\" data-unicode-name=\"1F45F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F476\" title=\"baby\" data-aliases=\"\" data-emoji=\"baby\" data-unicode-name=\"1F476\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F459\" title=\"bikini\" data-aliases=\"\" data-emoji=\"bikini\" data-unicode-name=\"1F459\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F499\" title=\"blue_heart\" data-aliases=\"\" data-emoji=\"blue_heart\" data-unicode-name=\"1F499\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60A\" title=\"blush\" data-aliases=\"\" data-emoji=\"blush\" data-unicode-name=\"1F60A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A5\" title=\"boom\" data-aliases=\"\" data-emoji=\"boom\" data-unicode-name=\"1F4A5\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F462\" title=\"boot\" data-aliases=\"\" data-emoji=\"boot\" data-unicode-name=\"1F462\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F647\" title=\"bow\" data-aliases=\"\" data-emoji=\"bow\" data-unicode-name=\"1F647\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F466\" title=\"boy\" data-aliases=\"\" data-emoji=\"boy\" data-unicode-name=\"1F466\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F470\" title=\"bride_with_veil\" data-aliases=\"\" data-emoji=\"bride_with_veil\" data-unicode-name=\"1F470\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4BC\" title=\"briefcase\" data-aliases=\"\" data-emoji=\"briefcase\" data-unicode-name=\"1F4BC\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F494\" title=\"broken_heart\" data-aliases=\"\" data-emoji=\"broken_heart\" data-unicode-name=\"1F494\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F464\" title=\"bust_in_silhouette\" data-aliases=\"\" data-emoji=\"bust_in_silhouette\" data-unicode-name=\"1F464\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F465\" title=\"busts_in_silhouette\" data-aliases=\"\" data-emoji=\"busts_in_silhouette\" data-unicode-name=\"1F465\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44F\" title=\"clap\" data-aliases=\"\" data-emoji=\"clap\" data-unicode-name=\"1F44F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F302\" title=\"closed_umbrella\" data-aliases=\"\" data-emoji=\"closed_umbrella\" data-unicode-name=\"1F302\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F630\" title=\"cold_sweat\" data-aliases=\"\" data-emoji=\"cold_sweat\" data-unicode-name=\"1F630\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F616\" title=\"confounded\" data-aliases=\"\" data-emoji=\"confounded\" data-unicode-name=\"1F616\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F615\" title=\"confused\" data-aliases=\"\" data-emoji=\"confused\" data-unicode-name=\"1F615\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F477\" title=\"construction_worker\" data-aliases=\"\" data-emoji=\"construction_worker\" data-unicode-name=\"1F477\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46E\" title=\"cop\" data-aliases=\"\" data-emoji=\"cop\" data-unicode-name=\"1F46E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46B\" title=\"couple\" data-aliases=\"\" data-emoji=\"couple\" data-unicode-name=\"1F46B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F491\" title=\"couple_with_heart\" data-aliases=\"\" data-emoji=\"couple_with_heart\" data-unicode-name=\"1F491\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48F\" title=\"couplekiss\" data-aliases=\"\" data-emoji=\"couplekiss\" data-unicode-name=\"1F48F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F451\" title=\"crown\" data-aliases=\"\" data-emoji=\"crown\" data-unicode-name=\"1F451\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F622\" title=\"cry\" data-aliases=\"\" data-emoji=\"cry\" data-unicode-name=\"1F622\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63F\" title=\"crying_cat_face\" data-aliases=\"\" data-emoji=\"crying_cat_face\" data-unicode-name=\"1F63F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F498\" title=\"cupid\" data-aliases=\"\" data-emoji=\"cupid\" data-unicode-name=\"1F498\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F483\" title=\"dancer\" data-aliases=\"\" data-emoji=\"dancer\" data-unicode-name=\"1F483\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46F\" title=\"dancers\" data-aliases=\"\" data-emoji=\"dancers\" data-unicode-name=\"1F46F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A8\" title=\"dash\" data-aliases=\"\" data-emoji=\"dash\" data-unicode-name=\"1F4A8\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61E\" title=\"disappointed\" data-aliases=\"\" data-emoji=\"disappointed\" data-unicode-name=\"1F61E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F625\" title=\"disappointed_relieved\" data-aliases=\"\" data-emoji=\"disappointed_relieved\" data-unicode-name=\"1F625\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AB\" title=\"dizzy\" data-aliases=\"\" data-emoji=\"dizzy\" data-unicode-name=\"1F4AB\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F635\" title=\"dizzy_face\" data-aliases=\"\" data-emoji=\"dizzy_face\" data-unicode-name=\"1F635\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F457\" title=\"dress\" data-aliases=\"\" data-emoji=\"dress\" data-unicode-name=\"1F457\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A7\" title=\"droplet\" data-aliases=\"\" data-emoji=\"droplet\" data-unicode-name=\"1F4A7\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F442\" title=\"ear\" data-aliases=\"\" data-emoji=\"ear\" data-unicode-name=\"1F442\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F611\" title=\"expressionless\" data-aliases=\"\" data-emoji=\"expressionless\" data-unicode-name=\"1F611\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F453\" title=\"eyeglasses\" data-aliases=\"\" data-emoji=\"eyeglasses\" data-unicode-name=\"1F453\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F440\" title=\"eyes\" data-aliases=\"\" data-emoji=\"eyes\" data-unicode-name=\"1F440\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46A\" title=\"family\" data-aliases=\"\" data-emoji=\"family\" data-unicode-name=\"1F46A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F628\" title=\"fearful\" data-aliases=\"\" data-emoji=\"fearful\" data-unicode-name=\"1F628\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F525\" title=\"fire\" data-aliases=\":flame:\" data-emoji=\"fire\" data-unicode-name=\"1F525\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-270A\" title=\"fist\" data-aliases=\"\" data-emoji=\"fist\" data-unicode-name=\"270A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F633\" title=\"flushed\" data-aliases=\"\" data-emoji=\"flushed\" data-unicode-name=\"1F633\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F463\" title=\"footprints\" data-aliases=\"\" data-emoji=\"footprints\" data-unicode-name=\"1F463\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F626\" title=\"frowning\" data-aliases=\":anguished:\" data-emoji=\"frowning\" data-unicode-name=\"1F626\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48E\" title=\"gem\" data-aliases=\"\" data-emoji=\"gem\" data-unicode-name=\"1F48E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F467\" title=\"girl\" data-aliases=\"\" data-emoji=\"girl\" data-unicode-name=\"1F467\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49A\" title=\"green_heart\" data-aliases=\"\" data-emoji=\"green_heart\" data-unicode-name=\"1F49A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62C\" title=\"grimacing\" data-aliases=\"\" data-emoji=\"grimacing\" data-unicode-name=\"1F62C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F601\" title=\"grin\" data-aliases=\"\" data-emoji=\"grin\" data-unicode-name=\"1F601\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F600\" title=\"grinning\" data-aliases=\"\" data-emoji=\"grinning\" data-unicode-name=\"1F600\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F482\" title=\"guardsman\" data-aliases=\"\" data-emoji=\"guardsman\" data-unicode-name=\"1F482\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F487\" title=\"haircut\" data-aliases=\"\" data-emoji=\"haircut\" data-unicode-name=\"1F487\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45C\" title=\"handbag\" data-aliases=\"\" data-emoji=\"handbag\" data-unicode-name=\"1F45C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F649\" title=\"hear_no_evil\" data-aliases=\"\" data-emoji=\"hear_no_evil\" data-unicode-name=\"1F649\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-2764\" title=\"heart\" data-aliases=\"\" data-emoji=\"heart\" data-unicode-name=\"2764\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60D\" title=\"heart_eyes\" data-aliases=\"\" data-emoji=\"heart_eyes\" data-unicode-name=\"1F60D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63B\" title=\"heart_eyes_cat\" data-aliases=\"\" data-emoji=\"heart_eyes_cat\" data-unicode-name=\"1F63B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F493\" title=\"heartbeat\" data-aliases=\"\" data-emoji=\"heartbeat\" data-unicode-name=\"1F493\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F497\" title=\"heartpulse\" data-aliases=\"\" data-emoji=\"heartpulse\" data-unicode-name=\"1F497\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F460\" title=\"high_heel\" data-aliases=\"\" data-emoji=\"high_heel\" data-unicode-name=\"1F460\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62F\" title=\"hushed\" data-aliases=\"\" data-emoji=\"hushed\" data-unicode-name=\"1F62F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47F\" title=\"imp\" data-aliases=\"\" data-emoji=\"imp\" data-unicode-name=\"1F47F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F481\" title=\"information_desk_person\" data-aliases=\"\" data-emoji=\"information_desk_person\" data-unicode-name=\"1F481\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F607\" title=\"innocent\" data-aliases=\"\" data-emoji=\"innocent\" data-unicode-name=\"1F607\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47A\" title=\"japanese_goblin\" data-aliases=\"\" data-emoji=\"japanese_goblin\" data-unicode-name=\"1F47A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F479\" title=\"japanese_ogre\" data-aliases=\"\" data-emoji=\"japanese_ogre\" data-unicode-name=\"1F479\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F456\" title=\"jeans\" data-aliases=\"\" data-emoji=\"jeans\" data-unicode-name=\"1F456\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F602\" title=\"joy\" data-aliases=\"\" data-emoji=\"joy\" data-unicode-name=\"1F602\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F639\" title=\"joy_cat\" data-aliases=\"\" data-emoji=\"joy_cat\" data-unicode-name=\"1F639\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F458\" title=\"kimono\" data-aliases=\"\" data-emoji=\"kimono\" data-unicode-name=\"1F458\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48B\" title=\"kiss\" data-aliases=\"\" data-emoji=\"kiss\" data-unicode-name=\"1F48B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F617\" title=\"kissing\" data-aliases=\"\" data-emoji=\"kissing\" data-unicode-name=\"1F617\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63D\" title=\"kissing_cat\" data-aliases=\"\" data-emoji=\"kissing_cat\" data-unicode-name=\"1F63D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61A\" title=\"kissing_closed_eyes\" data-aliases=\"\" data-emoji=\"kissing_closed_eyes\" data-unicode-name=\"1F61A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F618\" title=\"kissing_heart\" data-aliases=\"\" data-emoji=\"kissing_heart\" data-unicode-name=\"1F618\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F619\" title=\"kissing_smiling_eyes\" data-aliases=\"\" data-emoji=\"kissing_smiling_eyes\" data-unicode-name=\"1F619\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F606\" title=\"laughing\" data-aliases=\":satisfied:\" data-emoji=\"laughing\" data-unicode-name=\"1F606\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F444\" title=\"lips\" data-aliases=\"\" data-emoji=\"lips\" data-unicode-name=\"1F444\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F484\" title=\"lipstick\" data-aliases=\"\" data-emoji=\"lipstick\" data-unicode-name=\"1F484\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48C\" title=\"love_letter\" data-aliases=\"\" data-emoji=\"love_letter\" data-unicode-name=\"1F48C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F468\" title=\"man\" data-aliases=\"\" data-emoji=\"man\" data-unicode-name=\"1F468\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F472\" title=\"man_with_gua_pi_mao\" data-aliases=\"\" data-emoji=\"man_with_gua_pi_mao\" data-unicode-name=\"1F472\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F473\" title=\"man_with_turban\" data-aliases=\"\" data-emoji=\"man_with_turban\" data-unicode-name=\"1F473\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45E\" title=\"mans_shoe\" data-aliases=\"\" data-emoji=\"mans_shoe\" data-unicode-name=\"1F45E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F637\" title=\"mask\" data-aliases=\"\" data-emoji=\"mask\" data-unicode-name=\"1F637\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F486\" title=\"massage\" data-aliases=\"\" data-emoji=\"massage\" data-unicode-name=\"1F486\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AA\" title=\"muscle\" data-aliases=\"\" data-emoji=\"muscle\" data-unicode-name=\"1F4AA\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F485\" title=\"nail_care\" data-aliases=\"\" data-emoji=\"nail_care\" data-unicode-name=\"1F485\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F454\" title=\"necktie\" data-aliases=\"\" data-emoji=\"necktie\" data-unicode-name=\"1F454\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F610\" title=\"neutral_face\" data-aliases=\"\" data-emoji=\"neutral_face\" data-unicode-name=\"1F610\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F645\" title=\"no_good\" data-aliases=\"\" data-emoji=\"no_good\" data-unicode-name=\"1F645\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F636\" title=\"no_mouth\" data-aliases=\"\" data-emoji=\"no_mouth\" data-unicode-name=\"1F636\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F443\" title=\"nose\" data-aliases=\"\" data-emoji=\"nose\" data-unicode-name=\"1F443\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44C\" title=\"ok_hand\" data-aliases=\"\" data-emoji=\"ok_hand\" data-unicode-name=\"1F44C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F646\" title=\"ok_woman\" data-aliases=\"\" data-emoji=\"ok_woman\" data-unicode-name=\"1F646\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F474\" title=\"older_man\" data-aliases=\"\" data-emoji=\"older_man\" data-unicode-name=\"1F474\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F475\" title=\"older_woman\" data-aliases=\":grandma:\" data-emoji=\"older_woman\" data-unicode-name=\"1F475\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F450\" title=\"open_hands\" data-aliases=\"\" data-emoji=\"open_hands\" data-unicode-name=\"1F450\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62E\" title=\"open_mouth\" data-aliases=\"\" data-emoji=\"open_mouth\" data-unicode-name=\"1F62E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F614\" title=\"pensive\" data-aliases=\"\" data-emoji=\"pensive\" data-unicode-name=\"1F614\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F623\" title=\"persevere\" data-aliases=\"\" data-emoji=\"persevere\" data-unicode-name=\"1F623\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64D\" title=\"person_frowning\" data-aliases=\"\" data-emoji=\"person_frowning\" data-unicode-name=\"1F64D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F471\" title=\"person_with_blond_hair\" data-aliases=\"\" data-emoji=\"person_with_blond_hair\" data-unicode-name=\"1F471\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64E\" title=\"person_with_pouting_face\" data-aliases=\"\" data-emoji=\"person_with_pouting_face\" data-unicode-name=\"1F64E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F447\" title=\"point_down\" data-aliases=\"\" data-emoji=\"point_down\" data-unicode-name=\"1F447\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F448\" title=\"point_left\" data-aliases=\"\" data-emoji=\"point_left\" data-unicode-name=\"1F448\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F449\" title=\"point_right\" data-aliases=\"\" data-emoji=\"point_right\" data-unicode-name=\"1F449\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-261D\" title=\"point_up\" data-aliases=\"\" data-emoji=\"point_up\" data-unicode-name=\"261D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F446\" title=\"point_up_2\" data-aliases=\"\" data-emoji=\"point_up_2\" data-unicode-name=\"1F446\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A9\" title=\"poop\" data-aliases=\":shit: :hankey: :poo:\" data-emoji=\"poop\" data-unicode-name=\"1F4A9\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45D\" title=\"pouch\" data-aliases=\"\" data-emoji=\"pouch\" data-unicode-name=\"1F45D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63E\" title=\"pouting_cat\" data-aliases=\"\" data-emoji=\"pouting_cat\" data-unicode-name=\"1F63E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64F\" title=\"pray\" data-aliases=\"\" data-emoji=\"pray\" data-unicode-name=\"1F64F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F478\" title=\"princess\" data-aliases=\"\" data-emoji=\"princess\" data-unicode-name=\"1F478\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44A\" title=\"punch\" data-aliases=\"\" data-emoji=\"punch\" data-unicode-name=\"1F44A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49C\" title=\"purple_heart\" data-aliases=\"\" data-emoji=\"purple_heart\" data-unicode-name=\"1F49C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45B\" title=\"purse\" data-aliases=\"\" data-emoji=\"purse\" data-unicode-name=\"1F45B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F621\" title=\"rage\" data-aliases=\"\" data-emoji=\"rage\" data-unicode-name=\"1F621\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-270B\" title=\"raised_hand\" data-aliases=\"\" data-emoji=\"raised_hand\" data-unicode-name=\"270B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64C\" title=\"raised_hands\" data-aliases=\"\" data-emoji=\"raised_hands\" data-unicode-name=\"1F64C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64B\" title=\"raising_hand\" data-aliases=\"\" data-emoji=\"raising_hand\" data-unicode-name=\"1F64B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-263A\" title=\"relaxed\" data-aliases=\"\" data-emoji=\"relaxed\" data-unicode-name=\"263A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60C\" title=\"relieved\" data-aliases=\"\" data-emoji=\"relieved\" data-unicode-name=\"1F60C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49E\" title=\"revolving_hearts\" data-aliases=\"\" data-emoji=\"revolving_hearts\" data-unicode-name=\"1F49E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F380\" title=\"ribbon\" data-aliases=\"\" data-emoji=\"ribbon\" data-unicode-name=\"1F380\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48D\" title=\"ring\" data-aliases=\"\" data-emoji=\"ring\" data-unicode-name=\"1F48D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F3C3\" title=\"runner\" data-aliases=\"\" data-emoji=\"runner\" data-unicode-name=\"1F3C3\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F3BD\" title=\"running_shirt_with_sash\" data-aliases=\"\" data-emoji=\"running_shirt_with_sash\" data-unicode-name=\"1F3BD\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F461\" title=\"sandal\" data-aliases=\"\" data-emoji=\"sandal\" data-unicode-name=\"1F461\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F631\" title=\"scream\" data-aliases=\"\" data-emoji=\"scream\" data-unicode-name=\"1F631\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F640\" title=\"scream_cat\" data-aliases=\"\" data-emoji=\"scream_cat\" data-unicode-name=\"1F640\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F648\" title=\"see_no_evil\" data-aliases=\"\" data-emoji=\"see_no_evil\" data-unicode-name=\"1F648\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F455\" title=\"shirt\" data-aliases=\"\" data-emoji=\"shirt\" data-unicode-name=\"1F455\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F480\" title=\"skull\" data-aliases=\":skeleton:\" data-emoji=\"skull\" data-unicode-name=\"1F480\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F634\" title=\"sleeping\" data-aliases=\"\" data-emoji=\"sleeping\" data-unicode-name=\"1F634\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62A\" title=\"sleepy\" data-aliases=\"\" data-emoji=\"sleepy\" data-unicode-name=\"1F62A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F604\" title=\"smile\" data-aliases=\"\" data-emoji=\"smile\" data-unicode-name=\"1F604\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F638\" title=\"smile_cat\" data-aliases=\"\" data-emoji=\"smile_cat\" data-unicode-name=\"1F638\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F603\" title=\"smiley\" data-aliases=\"\" data-emoji=\"smiley\" data-unicode-name=\"1F603\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63A\" title=\"smiley_cat\" data-aliases=\"\" data-emoji=\"smiley_cat\" data-unicode-name=\"1F63A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F608\" title=\"smiling_imp\" data-aliases=\"\" data-emoji=\"smiling_imp\" data-unicode-name=\"1F608\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60F\" title=\"smirk\" data-aliases=\"\" data-emoji=\"smirk\" data-unicode-name=\"1F60F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63C\" title=\"smirk_cat\" data-aliases=\"\" data-emoji=\"smirk_cat\" data-unicode-name=\"1F63C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62D\" title=\"sob\" data-aliases=\"\" data-emoji=\"sob\" data-unicode-name=\"1F62D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-2728\" title=\"sparkles\" data-aliases=\"\" data-emoji=\"sparkles\" data-unicode-name=\"2728\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F496\" title=\"sparkling_heart\" data-aliases=\"\" data-emoji=\"sparkling_heart\" data-unicode-name=\"1F496\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64A\" title=\"speak_no_evil\" data-aliases=\"\" data-emoji=\"speak_no_evil\" data-unicode-name=\"1F64A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AC\" title=\"speech_balloon\" data-aliases=\"\" data-emoji=\"speech_balloon\" data-unicode-name=\"1F4AC\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F31F\" title=\"star2\" data-aliases=\"\" data-emoji=\"star2\" data-unicode-name=\"1F31F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61B\" title=\"stuck_out_tongue\" data-aliases=\"\" data-emoji=\"stuck_out_tongue\" data-unicode-name=\"1F61B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61D\" title=\"stuck_out_tongue_closed_eyes\" data-aliases=\"\" data-emoji=\"stuck_out_tongue_closed_eyes\" data-unicode-name=\"1F61D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61C\" title=\"stuck_out_tongue_winking_eye\" data-aliases=\"\" data-emoji=\"stuck_out_tongue_winking_eye\" data-unicode-name=\"1F61C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60E\" title=\"sunglasses\" data-aliases=\"\" data-emoji=\"sunglasses\" data-unicode-name=\"1F60E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F613\" title=\"sweat\" data-aliases=\"\" data-emoji=\"sweat\" data-unicode-name=\"1F613\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A6\" title=\"sweat_drops\" data-aliases=\"\" data-emoji=\"sweat_drops\" data-unicode-name=\"1F4A6\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F605\" title=\"sweat_smile\" data-aliases=\"\" data-emoji=\"sweat_smile\" data-unicode-name=\"1F605\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AD\" title=\"thought_balloon\" data-aliases=\"\" data-emoji=\"thought_balloon\" data-unicode-name=\"1F4AD\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44E\" title=\"thumbsdown\" data-aliases=\":-1:\" data-emoji=\"thumbsdown\" data-unicode-name=\"1F44E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44D\" title=\"thumbsup\" data-aliases=\":+1:\" data-emoji=\"thumbsup\" data-unicode-name=\"1F44D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62B\" title=\"tired_face\" data-aliases=\"\" data-emoji=\"tired_face\" data-unicode-name=\"1F62B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F445\" title=\"tongue\" data-aliases=\"\" data-emoji=\"tongue\" data-unicode-name=\"1F445\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F3A9\" title=\"tophat\" data-aliases=\"\" data-emoji=\"tophat\" data-unicode-name=\"1F3A9\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F624\" title=\"triumph\" data-aliases=\"\" data-emoji=\"triumph\" data-unicode-name=\"1F624\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F495\" title=\"two_hearts\" data-aliases=\"\" data-emoji=\"two_hearts\" data-unicode-name=\"1F495\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46C\" title=\"two_men_holding_hands\" data-aliases=\"\" data-emoji=\"two_men_holding_hands\" data-unicode-name=\"1F46C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46D\" title=\"two_women_holding_hands\" data-aliases=\"\" data-emoji=\"two_women_holding_hands\" data-unicode-name=\"1F46D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F612\" title=\"unamused\" data-aliases=\"\" data-emoji=\"unamused\" data-unicode-name=\"1F612\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-270C\" title=\"v\" data-aliases=\"\" data-emoji=\"v\" data-unicode-name=\"270C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F6B6\" title=\"walking\" data-aliases=\"\" data-emoji=\"walking\" data-unicode-name=\"1F6B6\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44B\" title=\"wave\" data-aliases=\"\" data-emoji=\"wave\" data-unicode-name=\"1F44B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F629\" title=\"weary\" data-aliases=\"\" data-emoji=\"weary\" data-unicode-name=\"1F629\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F609\" title=\"wink\" data-aliases=\"\" data-emoji=\"wink\" data-unicode-name=\"1F609\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F469\" title=\"woman\" data-aliases=\"\" data-emoji=\"woman\" data-unicode-name=\"1F469\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45A\" title=\"womans_clothes\" data-aliases=\"\" data-emoji=\"womans_clothes\" data-unicode-name=\"1F45A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F452\" title=\"womans_hat\" data-aliases=\"\" data-emoji=\"womans_hat\" data-unicode-name=\"1F452\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61F\" title=\"worried\" data-aliases=\"\" data-emoji=\"worried\" data-unicode-name=\"1F61F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49B\" title=\"yellow_heart\" data-aliases=\"\" data-emoji=\"yellow_heart\" data-unicode-name=\"1F49B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60B\" title=\"yum\" data-aliases=\"\" data-emoji=\"yum\" data-unicode-name=\"1F60B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A4\" title=\"zzz\" data-aliases=\"\" data-emoji=\"zzz\" data-unicode-name=\"1F4A4\"></div>\n </button>\n </li>\n </ul>\n </div>\n</div>";
-}).call(window);
diff --git a/spec/javascripts/fixtures/environments/metrics.html.haml b/spec/javascripts/fixtures/environments/metrics.html.haml
new file mode 100644
index 00000000000..483063fb889
--- /dev/null
+++ b/spec/javascripts/fixtures/environments/metrics.html.haml
@@ -0,0 +1,12 @@
+%div
+ .top-area
+ .row
+ .col-sm-6
+ %h3.page-title
+ Metrics for environment
+ .row
+ .col-sm-12
+ %svg.prometheus-graph{ 'graph-type' => 'cpu_values' }
+ .row
+ .col-sm-12
+ %svg.prometheus-graph{ 'graph-type' => 'memory_values' } \ No newline at end of file
diff --git a/spec/javascripts/gl_emoji_spec.js b/spec/javascripts/gl_emoji_spec.js
new file mode 100644
index 00000000000..e94e220b19f
--- /dev/null
+++ b/spec/javascripts/gl_emoji_spec.js
@@ -0,0 +1,367 @@
+
+require('~/extensions/string');
+require('~/extensions/array');
+
+const glEmoji = require('~/behaviors/gl_emoji');
+
+const glEmojiTag = glEmoji.glEmojiTag;
+const isEmojiUnicodeSupported = glEmoji.isEmojiUnicodeSupported;
+const isFlagEmoji = glEmoji.isFlagEmoji;
+const isKeycapEmoji = glEmoji.isKeycapEmoji;
+const isSkinToneComboEmoji = glEmoji.isSkinToneComboEmoji;
+const isHorceRacingSkinToneComboEmoji = glEmoji.isHorceRacingSkinToneComboEmoji;
+const isPersonZwjEmoji = glEmoji.isPersonZwjEmoji;
+
+const emptySupportMap = {
+ personZwj: false,
+ horseRacing: false,
+ flag: false,
+ skinToneModifier: false,
+ '9.0': false,
+ '8.0': false,
+ '7.0': false,
+ 6.1: false,
+ '6.0': false,
+ 5.2: false,
+ 5.1: false,
+ 4.1: false,
+ '4.0': false,
+ 3.2: false,
+ '3.0': false,
+ 1.1: false,
+};
+
+const emojiFixtureMap = {
+ bomb: {
+ name: 'bomb',
+ moji: '💣',
+ unicodeVersion: '6.0',
+ },
+ construction_worker_tone5: {
+ name: 'construction_worker_tone5',
+ moji: '👷🏿',
+ unicodeVersion: '8.0',
+ },
+ five: {
+ name: 'five',
+ moji: '5️⃣',
+ unicodeVersion: '3.0',
+ },
+};
+
+function markupToDomElement(markup) {
+ const div = document.createElement('div');
+ div.innerHTML = markup;
+ return div.firstElementChild;
+}
+
+function testGlEmojiImageFallback(element, name, src) {
+ expect(element.tagName.toLowerCase()).toBe('img');
+ expect(element.getAttribute('src')).toBe(src);
+ expect(element.getAttribute('title')).toBe(`:${name}:`);
+ expect(element.getAttribute('alt')).toBe(`:${name}:`);
+}
+
+const defaults = {
+ forceFallback: false,
+ sprite: false,
+};
+
+function testGlEmojiElement(element, name, unicodeVersion, unicodeMoji, options = {}) {
+ const opts = Object.assign({}, defaults, options);
+ expect(element.tagName.toLowerCase()).toBe('gl-emoji');
+ expect(element.dataset.name).toBe(name);
+ expect(element.dataset.fallbackSrc.length).toBeGreaterThan(0);
+ expect(element.dataset.unicodeVersion).toBe(unicodeVersion);
+
+ const fallbackSpriteClass = `emoji-${name}`;
+ if (opts.sprite) {
+ expect(element.dataset.fallbackSpriteClass).toBe(fallbackSpriteClass);
+ }
+
+ if (opts.forceFallback && opts.sprite) {
+ expect(element.getAttribute('class')).toBe(`emoji-icon ${fallbackSpriteClass}`);
+ }
+
+ if (opts.forceFallback && !opts.sprite) {
+ // Check for image fallback
+ testGlEmojiImageFallback(element.firstElementChild, name, element.dataset.fallbackSrc);
+ } else {
+ // Otherwise make sure things are still unicode text
+ expect(element.textContent.trim()).toBe(unicodeMoji);
+ }
+}
+
+describe('gl_emoji', () => {
+ describe('glEmojiTag', () => {
+ it('bomb emoji', () => {
+ const emojiKey = 'bomb';
+ const markup = glEmojiTag(emojiFixtureMap[emojiKey].name);
+ const glEmojiElement = markupToDomElement(markup);
+ testGlEmojiElement(
+ glEmojiElement,
+ emojiFixtureMap[emojiKey].name,
+ emojiFixtureMap[emojiKey].unicodeVersion,
+ emojiFixtureMap[emojiKey].moji,
+ );
+ });
+
+ it('bomb emoji with image fallback', () => {
+ const emojiKey = 'bomb';
+ const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, {
+ forceFallback: true,
+ });
+ const glEmojiElement = markupToDomElement(markup);
+ testGlEmojiElement(
+ glEmojiElement,
+ emojiFixtureMap[emojiKey].name,
+ emojiFixtureMap[emojiKey].unicodeVersion,
+ emojiFixtureMap[emojiKey].moji,
+ {
+ forceFallback: true,
+ },
+ );
+ });
+
+ it('bomb emoji with sprite fallback readiness', () => {
+ const emojiKey = 'bomb';
+ const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, {
+ sprite: true,
+ });
+ const glEmojiElement = markupToDomElement(markup);
+ testGlEmojiElement(
+ glEmojiElement,
+ emojiFixtureMap[emojiKey].name,
+ emojiFixtureMap[emojiKey].unicodeVersion,
+ emojiFixtureMap[emojiKey].moji,
+ {
+ sprite: true,
+ },
+ );
+ });
+ it('bomb emoji with sprite fallback', () => {
+ const emojiKey = 'bomb';
+ const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, {
+ forceFallback: true,
+ sprite: true,
+ });
+ const glEmojiElement = markupToDomElement(markup);
+ testGlEmojiElement(
+ glEmojiElement,
+ emojiFixtureMap[emojiKey].name,
+ emojiFixtureMap[emojiKey].unicodeVersion,
+ emojiFixtureMap[emojiKey].moji,
+ {
+ forceFallback: true,
+ sprite: true,
+ },
+ );
+ });
+ });
+
+ describe('isFlagEmoji', () => {
+ it('should detect flag_ac', () => {
+ expect(isFlagEmoji('🇦🇨')).toBeTruthy();
+ });
+ it('should detect flag_us', () => {
+ expect(isFlagEmoji('🇺🇸')).toBeTruthy();
+ });
+ it('should detect flag_zw', () => {
+ expect(isFlagEmoji('🇿🇼')).toBeTruthy();
+ });
+ it('should not detect flags', () => {
+ expect(isFlagEmoji('🎏')).toBeFalsy();
+ });
+ it('should not detect triangular_flag_on_post', () => {
+ expect(isFlagEmoji('🚩')).toBeFalsy();
+ });
+ it('should not detect single letter', () => {
+ expect(isFlagEmoji('🇦')).toBeFalsy();
+ });
+ it('should not detect >2 letters', () => {
+ expect(isFlagEmoji('🇦🇧🇨')).toBeFalsy();
+ });
+ });
+
+ describe('isKeycapEmoji', () => {
+ it('should detect one(keycap)', () => {
+ expect(isKeycapEmoji('1️⃣')).toBeTruthy();
+ });
+ it('should detect nine(keycap)', () => {
+ expect(isKeycapEmoji('9️⃣')).toBeTruthy();
+ });
+ it('should not detect ten(keycap)', () => {
+ expect(isKeycapEmoji('🔟')).toBeFalsy();
+ });
+ it('should not detect hash(keycap)', () => {
+ expect(isKeycapEmoji('#⃣')).toBeFalsy();
+ });
+ });
+
+ describe('isSkinToneComboEmoji', () => {
+ it('should detect hand_splayed_tone5', () => {
+ expect(isSkinToneComboEmoji('🖐🏿')).toBeTruthy();
+ });
+ it('should not detect hand_splayed', () => {
+ expect(isSkinToneComboEmoji('🖐')).toBeFalsy();
+ });
+ it('should detect lifter_tone1', () => {
+ expect(isSkinToneComboEmoji('🏋🏻')).toBeTruthy();
+ });
+ it('should not detect lifter', () => {
+ expect(isSkinToneComboEmoji('🏋')).toBeFalsy();
+ });
+ it('should detect rowboat_tone4', () => {
+ expect(isSkinToneComboEmoji('🚣🏾')).toBeTruthy();
+ });
+ it('should not detect rowboat', () => {
+ expect(isSkinToneComboEmoji('🚣')).toBeFalsy();
+ });
+ it('should not detect individual tone emoji', () => {
+ expect(isSkinToneComboEmoji('🏻')).toBeFalsy();
+ });
+ });
+
+ describe('isHorceRacingSkinToneComboEmoji', () => {
+ it('should detect horse_racing_tone2', () => {
+ expect(isHorceRacingSkinToneComboEmoji('🏇🏼')).toBeTruthy();
+ });
+ it('should not detect horse_racing', () => {
+ expect(isHorceRacingSkinToneComboEmoji('🏇')).toBeFalsy();
+ });
+ });
+
+ describe('isPersonZwjEmoji', () => {
+ it('should detect couple_mm', () => {
+ expect(isPersonZwjEmoji('👨‍❤️‍👨')).toBeTruthy();
+ });
+ it('should not detect couple_with_heart', () => {
+ expect(isPersonZwjEmoji('💑')).toBeFalsy();
+ });
+ it('should not detect couplekiss', () => {
+ expect(isPersonZwjEmoji('💏')).toBeFalsy();
+ });
+ it('should detect family_mmb', () => {
+ expect(isPersonZwjEmoji('👨‍👨‍👦')).toBeTruthy();
+ });
+ it('should detect family_mwgb', () => {
+ expect(isPersonZwjEmoji('👨‍👩‍👧‍👦')).toBeTruthy();
+ });
+ it('should not detect family', () => {
+ expect(isPersonZwjEmoji('👪')).toBeFalsy();
+ });
+ it('should detect kiss_ww', () => {
+ expect(isPersonZwjEmoji('👩‍❤️‍💋‍👩')).toBeTruthy();
+ });
+ it('should not detect girl', () => {
+ expect(isPersonZwjEmoji('👧')).toBeFalsy();
+ });
+ it('should not detect girl_tone5', () => {
+ expect(isPersonZwjEmoji('👧🏿')).toBeFalsy();
+ });
+ it('should not detect man', () => {
+ expect(isPersonZwjEmoji('👨')).toBeFalsy();
+ });
+ it('should not detect woman', () => {
+ expect(isPersonZwjEmoji('👩')).toBeFalsy();
+ });
+ });
+
+ describe('isEmojiUnicodeSupported', () => {
+ it('bomb(6.0) with 6.0 support', () => {
+ const emojiKey = 'bomb';
+ const unicodeSupportMap = Object.assign({}, emptySupportMap, {
+ '6.0': true,
+ });
+ const isSupported = isEmojiUnicodeSupported(
+ unicodeSupportMap,
+ emojiFixtureMap[emojiKey].moji,
+ emojiFixtureMap[emojiKey].unicodeVersion,
+ );
+ expect(isSupported).toBeTruthy();
+ });
+
+ it('bomb(6.0) without 6.0 support', () => {
+ const emojiKey = 'bomb';
+ const unicodeSupportMap = emptySupportMap;
+ const isSupported = isEmojiUnicodeSupported(
+ unicodeSupportMap,
+ emojiFixtureMap[emojiKey].moji,
+ emojiFixtureMap[emojiKey].unicodeVersion,
+ );
+ expect(isSupported).toBeFalsy();
+ });
+
+ it('bomb(6.0) without 6.0 but with 9.0 support', () => {
+ const emojiKey = 'bomb';
+ const unicodeSupportMap = Object.assign({}, emptySupportMap, {
+ '9.0': true,
+ });
+ const isSupported = isEmojiUnicodeSupported(
+ unicodeSupportMap,
+ emojiFixtureMap[emojiKey].moji,
+ emojiFixtureMap[emojiKey].unicodeVersion,
+ );
+ expect(isSupported).toBeFalsy();
+ });
+
+ it('construction_worker_tone5(8.0) without skin tone modifier support', () => {
+ const emojiKey = 'construction_worker_tone5';
+ const unicodeSupportMap = Object.assign({}, emptySupportMap, {
+ skinToneModifier: false,
+ '9.0': true,
+ '8.0': true,
+ '7.0': true,
+ 6.1: true,
+ '6.0': true,
+ 5.2: true,
+ 5.1: true,
+ 4.1: true,
+ '4.0': true,
+ 3.2: true,
+ '3.0': true,
+ 1.1: true,
+ });
+ const isSupported = isEmojiUnicodeSupported(
+ unicodeSupportMap,
+ emojiFixtureMap[emojiKey].moji,
+ emojiFixtureMap[emojiKey].unicodeVersion,
+ );
+ expect(isSupported).toBeFalsy();
+ });
+
+ it('use native keycap on >=57 chrome', () => {
+ const emojiKey = 'five';
+ const unicodeSupportMap = Object.assign({}, emptySupportMap, {
+ '3.0': true,
+ meta: {
+ isChrome: true,
+ chromeVersion: 57,
+ },
+ });
+ const isSupported = isEmojiUnicodeSupported(
+ unicodeSupportMap,
+ emojiFixtureMap[emojiKey].moji,
+ emojiFixtureMap[emojiKey].unicodeVersion,
+ );
+ expect(isSupported).toBeTruthy();
+ });
+
+ it('fallback keycap on <57 chrome', () => {
+ const emojiKey = 'five';
+ const unicodeSupportMap = Object.assign({}, emptySupportMap, {
+ '3.0': true,
+ meta: {
+ isChrome: true,
+ chromeVersion: 50,
+ },
+ });
+ const isSupported = isEmojiUnicodeSupported(
+ unicodeSupportMap,
+ emojiFixtureMap[emojiKey].moji,
+ emojiFixtureMap[emojiKey].unicodeVersion,
+ );
+ expect(isSupported).toBeFalsy();
+ });
+ });
+});
diff --git a/spec/javascripts/helpers/filtered_search_spec_helper.js b/spec/javascripts/helpers/filtered_search_spec_helper.js
new file mode 100644
index 00000000000..ce83a256ddd
--- /dev/null
+++ b/spec/javascripts/helpers/filtered_search_spec_helper.js
@@ -0,0 +1,52 @@
+class FilteredSearchSpecHelper {
+ static createFilterVisualTokenHTML(name, value, isSelected) {
+ return FilteredSearchSpecHelper.createFilterVisualToken(name, value, isSelected).outerHTML;
+ }
+
+ static createFilterVisualToken(name, value, isSelected = false) {
+ const li = document.createElement('li');
+ li.classList.add('js-visual-token', 'filtered-search-token');
+
+ li.innerHTML = `
+ <div class="selectable ${isSelected ? 'selected' : ''}" role="button">
+ <div class="name">${name}</div>
+ <div class="value">${value}</div>
+ </div>
+ `;
+
+ return li;
+ }
+
+ static createNameFilterVisualTokenHTML(name) {
+ return `
+ <li class="js-visual-token filtered-search-token">
+ <div class="name">${name}</div>
+ </li>
+ `;
+ }
+
+ static createSearchVisualTokenHTML(name) {
+ return `
+ <li class="js-visual-token filtered-search-term">
+ <div class="name">${name}</div>
+ </li>
+ `;
+ }
+
+ static createInputHTML(placeholder = '', value = '') {
+ return `
+ <li class="input-token">
+ <input type='text' class='filtered-search' placeholder='${placeholder}' value='${value}'/>
+ </li>
+ `;
+ }
+
+ static createTokensContainerHTML(html, inputPlaceholder) {
+ return `
+ ${html}
+ ${FilteredSearchSpecHelper.createInputHTML(inputPlaceholder)}
+ `;
+ }
+}
+
+module.exports = FilteredSearchSpecHelper;
diff --git a/spec/javascripts/monitoring/prometheus_graph_spec.js b/spec/javascripts/monitoring/prometheus_graph_spec.js
new file mode 100644
index 00000000000..823b4bab7fc
--- /dev/null
+++ b/spec/javascripts/monitoring/prometheus_graph_spec.js
@@ -0,0 +1,78 @@
+import 'jquery';
+import es6Promise from 'es6-promise';
+import '~/lib/utils/common_utils';
+import PrometheusGraph from '~/monitoring/prometheus_graph';
+import { prometheusMockData } from './prometheus_mock_data';
+
+es6Promise.polyfill();
+
+describe('PrometheusGraph', () => {
+ const fixtureName = 'static/environments/metrics.html.raw';
+ const prometheusGraphContainer = '.prometheus-graph';
+ const prometheusGraphContents = `${prometheusGraphContainer}[graph-type=cpu_values]`;
+
+ preloadFixtures(fixtureName);
+
+ beforeEach(() => {
+ loadFixtures(fixtureName);
+ this.prometheusGraph = new PrometheusGraph();
+ const self = this;
+ const fakeInit = (metricsResponse) => {
+ self.prometheusGraph.transformData(metricsResponse);
+ self.prometheusGraph.createGraph();
+ };
+ spyOn(this.prometheusGraph, 'init').and.callFake(fakeInit);
+ });
+
+ it('initializes graph properties', () => {
+ // Test for the measurements
+ expect(this.prometheusGraph.margin).toBeDefined();
+ expect(this.prometheusGraph.marginLabelContainer).toBeDefined();
+ expect(this.prometheusGraph.originalWidth).toBeDefined();
+ expect(this.prometheusGraph.originalHeight).toBeDefined();
+ expect(this.prometheusGraph.height).toBeDefined();
+ expect(this.prometheusGraph.width).toBeDefined();
+ expect(this.prometheusGraph.backOffRequestCounter).toBeDefined();
+ // Test for the graph properties (colors, radius, etc.)
+ expect(this.prometheusGraph.graphSpecificProperties).toBeDefined();
+ expect(this.prometheusGraph.commonGraphProperties).toBeDefined();
+ });
+
+ it('transforms the data', () => {
+ this.prometheusGraph.init(prometheusMockData.metrics);
+ expect(this.prometheusGraph.data).toBeDefined();
+ expect(this.prometheusGraph.data.cpu_values.length).toBe(121);
+ expect(this.prometheusGraph.data.memory_values.length).toBe(121);
+ });
+
+ it('creates two graphs', () => {
+ this.prometheusGraph.init(prometheusMockData.metrics);
+ expect($(prometheusGraphContainer).length).toBe(2);
+ });
+
+ describe('Graph contents', () => {
+ beforeEach(() => {
+ this.prometheusGraph.init(prometheusMockData.metrics);
+ });
+
+ it('has axis, an area, a line and a overlay', () => {
+ const $graphContainer = $(prometheusGraphContents).find('.x-axis').parent();
+ expect($graphContainer.find('.x-axis')).toBeDefined();
+ expect($graphContainer.find('.y-axis')).toBeDefined();
+ expect($graphContainer.find('.prometheus-graph-overlay')).toBeDefined();
+ expect($graphContainer.find('.metric-line')).toBeDefined();
+ expect($graphContainer.find('.metric-area')).toBeDefined();
+ });
+
+ it('has legends, labels and an extra axis that labels the metrics', () => {
+ const $prometheusGraphContents = $(prometheusGraphContents);
+ const $axisLabelContainer = $(prometheusGraphContents).find('.label-x-axis-line').parent();
+ expect($prometheusGraphContents.find('.label-x-axis-line')).toBeDefined();
+ expect($prometheusGraphContents.find('.label-y-axis-line')).toBeDefined();
+ expect($prometheusGraphContents.find('.label-axis-text')).toBeDefined();
+ expect($prometheusGraphContents.find('.rect-axis-text')).toBeDefined();
+ expect($axisLabelContainer.find('rect').length).toBe(2);
+ expect($axisLabelContainer.find('text').length).toBe(4);
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/prometheus_mock_data.js b/spec/javascripts/monitoring/prometheus_mock_data.js
new file mode 100644
index 00000000000..1cdc14faaa8
--- /dev/null
+++ b/spec/javascripts/monitoring/prometheus_mock_data.js
@@ -0,0 +1,1014 @@
+/* eslint-disable import/prefer-default-export*/
+export const prometheusMockData = {
+ status: 200,
+ metrics: {
+ success: true,
+ metrics: {
+ memory_values: [
+ {
+ metric: {
+ },
+ values: [
+ [
+ 1488462917.256,
+ '10.12890625',
+ ],
+ [
+ 1488462977.256,
+ '10.140625',
+ ],
+ [
+ 1488463037.256,
+ '10.140625',
+ ],
+ [
+ 1488463097.256,
+ '10.14453125',
+ ],
+ [
+ 1488463157.256,
+ '10.1484375',
+ ],
+ [
+ 1488463217.256,
+ '10.15625',
+ ],
+ [
+ 1488463277.256,
+ '10.15625',
+ ],
+ [
+ 1488463337.256,
+ '10.15625',
+ ],
+ [
+ 1488463397.256,
+ '10.1640625',
+ ],
+ [
+ 1488463457.256,
+ '10.171875',
+ ],
+ [
+ 1488463517.256,
+ '10.171875',
+ ],
+ [
+ 1488463577.256,
+ '10.171875',
+ ],
+ [
+ 1488463637.256,
+ '10.18359375',
+ ],
+ [
+ 1488463697.256,
+ '10.1953125',
+ ],
+ [
+ 1488463757.256,
+ '10.203125',
+ ],
+ [
+ 1488463817.256,
+ '10.20703125',
+ ],
+ [
+ 1488463877.256,
+ '10.20703125',
+ ],
+ [
+ 1488463937.256,
+ '10.20703125',
+ ],
+ [
+ 1488463997.256,
+ '10.20703125',
+ ],
+ [
+ 1488464057.256,
+ '10.2109375',
+ ],
+ [
+ 1488464117.256,
+ '10.2109375',
+ ],
+ [
+ 1488464177.256,
+ '10.2109375',
+ ],
+ [
+ 1488464237.256,
+ '10.2109375',
+ ],
+ [
+ 1488464297.256,
+ '10.21484375',
+ ],
+ [
+ 1488464357.256,
+ '10.22265625',
+ ],
+ [
+ 1488464417.256,
+ '10.22265625',
+ ],
+ [
+ 1488464477.256,
+ '10.2265625',
+ ],
+ [
+ 1488464537.256,
+ '10.23046875',
+ ],
+ [
+ 1488464597.256,
+ '10.23046875',
+ ],
+ [
+ 1488464657.256,
+ '10.234375',
+ ],
+ [
+ 1488464717.256,
+ '10.234375',
+ ],
+ [
+ 1488464777.256,
+ '10.234375',
+ ],
+ [
+ 1488464837.256,
+ '10.234375',
+ ],
+ [
+ 1488464897.256,
+ '10.234375',
+ ],
+ [
+ 1488464957.256,
+ '10.234375',
+ ],
+ [
+ 1488465017.256,
+ '10.23828125',
+ ],
+ [
+ 1488465077.256,
+ '10.23828125',
+ ],
+ [
+ 1488465137.256,
+ '10.2421875',
+ ],
+ [
+ 1488465197.256,
+ '10.2421875',
+ ],
+ [
+ 1488465257.256,
+ '10.2421875',
+ ],
+ [
+ 1488465317.256,
+ '10.2421875',
+ ],
+ [
+ 1488465377.256,
+ '10.2421875',
+ ],
+ [
+ 1488465437.256,
+ '10.2421875',
+ ],
+ [
+ 1488465497.256,
+ '10.2421875',
+ ],
+ [
+ 1488465557.256,
+ '10.2421875',
+ ],
+ [
+ 1488465617.256,
+ '10.2421875',
+ ],
+ [
+ 1488465677.256,
+ '10.2421875',
+ ],
+ [
+ 1488465737.256,
+ '10.2421875',
+ ],
+ [
+ 1488465797.256,
+ '10.24609375',
+ ],
+ [
+ 1488465857.256,
+ '10.25',
+ ],
+ [
+ 1488465917.256,
+ '10.25390625',
+ ],
+ [
+ 1488465977.256,
+ '9.98828125',
+ ],
+ [
+ 1488466037.256,
+ '9.9921875',
+ ],
+ [
+ 1488466097.256,
+ '9.9921875',
+ ],
+ [
+ 1488466157.256,
+ '9.99609375',
+ ],
+ [
+ 1488466217.256,
+ '10',
+ ],
+ [
+ 1488466277.256,
+ '10.00390625',
+ ],
+ [
+ 1488466337.256,
+ '10.0078125',
+ ],
+ [
+ 1488466397.256,
+ '10.01171875',
+ ],
+ [
+ 1488466457.256,
+ '10.0234375',
+ ],
+ [
+ 1488466517.256,
+ '10.02734375',
+ ],
+ [
+ 1488466577.256,
+ '10.02734375',
+ ],
+ [
+ 1488466637.256,
+ '10.03125',
+ ],
+ [
+ 1488466697.256,
+ '10.03125',
+ ],
+ [
+ 1488466757.256,
+ '10.03125',
+ ],
+ [
+ 1488466817.256,
+ '10.03125',
+ ],
+ [
+ 1488466877.256,
+ '10.03125',
+ ],
+ [
+ 1488466937.256,
+ '10.03125',
+ ],
+ [
+ 1488466997.256,
+ '10.03125',
+ ],
+ [
+ 1488467057.256,
+ '10.0390625',
+ ],
+ [
+ 1488467117.256,
+ '10.0390625',
+ ],
+ [
+ 1488467177.256,
+ '10.04296875',
+ ],
+ [
+ 1488467237.256,
+ '10.05078125',
+ ],
+ [
+ 1488467297.256,
+ '10.05859375',
+ ],
+ [
+ 1488467357.256,
+ '10.06640625',
+ ],
+ [
+ 1488467417.256,
+ '10.06640625',
+ ],
+ [
+ 1488467477.256,
+ '10.0703125',
+ ],
+ [
+ 1488467537.256,
+ '10.07421875',
+ ],
+ [
+ 1488467597.256,
+ '10.0859375',
+ ],
+ [
+ 1488467657.256,
+ '10.0859375',
+ ],
+ [
+ 1488467717.256,
+ '10.09765625',
+ ],
+ [
+ 1488467777.256,
+ '10.1015625',
+ ],
+ [
+ 1488467837.256,
+ '10.10546875',
+ ],
+ [
+ 1488467897.256,
+ '10.10546875',
+ ],
+ [
+ 1488467957.256,
+ '10.125',
+ ],
+ [
+ 1488468017.256,
+ '10.13671875',
+ ],
+ [
+ 1488468077.256,
+ '10.1484375',
+ ],
+ [
+ 1488468137.256,
+ '10.15625',
+ ],
+ [
+ 1488468197.256,
+ '10.16796875',
+ ],
+ [
+ 1488468257.256,
+ '10.171875',
+ ],
+ [
+ 1488468317.256,
+ '10.171875',
+ ],
+ [
+ 1488468377.256,
+ '10.171875',
+ ],
+ [
+ 1488468437.256,
+ '10.171875',
+ ],
+ [
+ 1488468497.256,
+ '10.171875',
+ ],
+ [
+ 1488468557.256,
+ '10.171875',
+ ],
+ [
+ 1488468617.256,
+ '10.171875',
+ ],
+ [
+ 1488468677.256,
+ '10.17578125',
+ ],
+ [
+ 1488468737.256,
+ '10.17578125',
+ ],
+ [
+ 1488468797.256,
+ '10.265625',
+ ],
+ [
+ 1488468857.256,
+ '10.19921875',
+ ],
+ [
+ 1488468917.256,
+ '10.19921875',
+ ],
+ [
+ 1488468977.256,
+ '10.19921875',
+ ],
+ [
+ 1488469037.256,
+ '10.19921875',
+ ],
+ [
+ 1488469097.256,
+ '10.19921875',
+ ],
+ [
+ 1488469157.256,
+ '10.203125',
+ ],
+ [
+ 1488469217.256,
+ '10.43359375',
+ ],
+ [
+ 1488469277.256,
+ '10.20703125',
+ ],
+ [
+ 1488469337.256,
+ '10.2109375',
+ ],
+ [
+ 1488469397.256,
+ '10.22265625',
+ ],
+ [
+ 1488469457.256,
+ '10.21484375',
+ ],
+ [
+ 1488469517.256,
+ '10.21484375',
+ ],
+ [
+ 1488469577.256,
+ '10.21484375',
+ ],
+ [
+ 1488469637.256,
+ '10.22265625',
+ ],
+ [
+ 1488469697.256,
+ '10.234375',
+ ],
+ [
+ 1488469757.256,
+ '10.234375',
+ ],
+ [
+ 1488469817.256,
+ '10.234375',
+ ],
+ [
+ 1488469877.256,
+ '10.2421875',
+ ],
+ [
+ 1488469937.256,
+ '10.25',
+ ],
+ [
+ 1488469997.256,
+ '10.25390625',
+ ],
+ [
+ 1488470057.256,
+ '10.26171875',
+ ],
+ [
+ 1488470117.256,
+ '10.2734375',
+ ],
+ ],
+ },
+ ],
+ memory_current: [
+ {
+ metric: {
+ },
+ value: [
+ 1488470117.737,
+ '10.2734375',
+ ],
+ },
+ ],
+ cpu_values: [
+ {
+ metric: {
+ },
+ values: [
+ [
+ 1488462918.15,
+ '0.0002996458625058103',
+ ],
+ [
+ 1488462978.15,
+ '0.0002652382333333314',
+ ],
+ [
+ 1488463038.15,
+ '0.0003485461333333421',
+ ],
+ [
+ 1488463098.15,
+ '0.0003420421999999886',
+ ],
+ [
+ 1488463158.15,
+ '0.00023107150000001297',
+ ],
+ [
+ 1488463218.15,
+ '0.00030463981666664826',
+ ],
+ [
+ 1488463278.15,
+ '0.0002477177833333677',
+ ],
+ [
+ 1488463338.15,
+ '0.00026936656666665115',
+ ],
+ [
+ 1488463398.15,
+ '0.000406264750000022',
+ ],
+ [
+ 1488463458.15,
+ '0.00029592802026561453',
+ ],
+ [
+ 1488463518.15,
+ '0.00023426999683316343',
+ ],
+ [
+ 1488463578.15,
+ '0.0003057080666666915',
+ ],
+ [
+ 1488463638.15,
+ '0.0003408470500000149',
+ ],
+ [
+ 1488463698.15,
+ '0.00025497336666665166',
+ ],
+ [
+ 1488463758.15,
+ '0.0003009282833333534',
+ ],
+ [
+ 1488463818.15,
+ '0.0003119383499999924',
+ ],
+ [
+ 1488463878.15,
+ '0.00028719019999998705',
+ ],
+ [
+ 1488463938.15,
+ '0.000327864749999988',
+ ],
+ [
+ 1488463998.15,
+ '0.0002514917333333422',
+ ],
+ [
+ 1488464058.15,
+ '0.0003614651166666742',
+ ],
+ [
+ 1488464118.15,
+ '0.0003221668000000122',
+ ],
+ [
+ 1488464178.15,
+ '0.00023323083333330884',
+ ],
+ [
+ 1488464238.15,
+ '0.00028531499475009274',
+ ],
+ [
+ 1488464298.15,
+ '0.0002627695294921391',
+ ],
+ [
+ 1488464358.15,
+ '0.00027145463333333453',
+ ],
+ [
+ 1488464418.15,
+ '0.00025669488333335266',
+ ],
+ [
+ 1488464478.15,
+ '0.00022307761666665965',
+ ],
+ [
+ 1488464538.15,
+ '0.0003307265833333517',
+ ],
+ [
+ 1488464598.15,
+ '0.0002817050666666709',
+ ],
+ [
+ 1488464658.15,
+ '0.00022357458333332285',
+ ],
+ [
+ 1488464718.15,
+ '0.00032648590000000275',
+ ],
+ [
+ 1488464778.15,
+ '0.00028410750000000816',
+ ],
+ [
+ 1488464838.15,
+ '0.0003038076999999954',
+ ],
+ [
+ 1488464898.15,
+ '0.00037568226666667335',
+ ],
+ [
+ 1488464958.15,
+ '0.00020160354999999202',
+ ],
+ [
+ 1488465018.15,
+ '0.0003229403333333399',
+ ],
+ [
+ 1488465078.15,
+ '0.00033516069999999236',
+ ],
+ [
+ 1488465138.15,
+ '0.0003365978333333371',
+ ],
+ [
+ 1488465198.15,
+ '0.00020262178333331585',
+ ],
+ [
+ 1488465258.15,
+ '0.00040567498333331876',
+ ],
+ [
+ 1488465318.15,
+ '0.00029114155000001436',
+ ],
+ [
+ 1488465378.15,
+ '0.0002498841000000122',
+ ],
+ [
+ 1488465438.15,
+ '0.00027296763333331715',
+ ],
+ [
+ 1488465498.15,
+ '0.0002958794000000135',
+ ],
+ [
+ 1488465558.15,
+ '0.0002922354666666867',
+ ],
+ [
+ 1488465618.15,
+ '0.00034186624999999653',
+ ],
+ [
+ 1488465678.15,
+ '0.0003397984166666627',
+ ],
+ [
+ 1488465738.15,
+ '0.0002658284166666469',
+ ],
+ [
+ 1488465798.15,
+ '0.00026221139999999346',
+ ],
+ [
+ 1488465858.15,
+ '0.00029467960000001034',
+ ],
+ [
+ 1488465918.15,
+ '0.0002634141333333358',
+ ],
+ [
+ 1488465978.15,
+ '0.0003202958333333209',
+ ],
+ [
+ 1488466038.15,
+ '0.00037890760000000394',
+ ],
+ [
+ 1488466098.15,
+ '0.00023453356666666518',
+ ],
+ [
+ 1488466158.15,
+ '0.0002866827333333433',
+ ],
+ [
+ 1488466218.15,
+ '0.0003335935499999998',
+ ],
+ [
+ 1488466278.15,
+ '0.00022787131666666125',
+ ],
+ [
+ 1488466338.15,
+ '0.00033821938333333064',
+ ],
+ [
+ 1488466398.15,
+ '0.00029233375000001043',
+ ],
+ [
+ 1488466458.15,
+ '0.00026562758333333514',
+ ],
+ [
+ 1488466518.15,
+ '0.0003142600999999819',
+ ],
+ [
+ 1488466578.15,
+ '0.00027392178333333444',
+ ],
+ [
+ 1488466638.15,
+ '0.00028178598333334173',
+ ],
+ [
+ 1488466698.15,
+ '0.0002463400666666911',
+ ],
+ [
+ 1488466758.15,
+ '0.00040234373333332125',
+ ],
+ [
+ 1488466818.15,
+ '0.00023677453333332822',
+ ],
+ [
+ 1488466878.15,
+ '0.00030852703333333523',
+ ],
+ [
+ 1488466938.15,
+ '0.0003582272833333455',
+ ],
+ [
+ 1488466998.15,
+ '0.0002176380833332973',
+ ],
+ [
+ 1488467058.15,
+ '0.00026180203333335447',
+ ],
+ [
+ 1488467118.15,
+ '0.00027862966666667436',
+ ],
+ [
+ 1488467178.15,
+ '0.0002769731166666567',
+ ],
+ [
+ 1488467238.15,
+ '0.0002832899166666477',
+ ],
+ [
+ 1488467298.15,
+ '0.0003446533500000311',
+ ],
+ [
+ 1488467358.15,
+ '0.0002691345999999761',
+ ],
+ [
+ 1488467418.15,
+ '0.000284919933333357',
+ ],
+ [
+ 1488467478.15,
+ '0.0002396026166666528',
+ ],
+ [
+ 1488467538.15,
+ '0.00035625295000002075',
+ ],
+ [
+ 1488467598.15,
+ '0.00036759816666664946',
+ ],
+ [
+ 1488467658.15,
+ '0.00030326608333333855',
+ ],
+ [
+ 1488467718.15,
+ '0.00023584972418043393',
+ ],
+ [
+ 1488467778.15,
+ '0.00025744508892115107',
+ ],
+ [
+ 1488467838.15,
+ '0.00036737541666663395',
+ ],
+ [
+ 1488467898.15,
+ '0.00034325741666666094',
+ ],
+ [
+ 1488467958.15,
+ '0.00026390046666667407',
+ ],
+ [
+ 1488468018.15,
+ '0.0003302534500000102',
+ ],
+ [
+ 1488468078.15,
+ '0.00035243794999999527',
+ ],
+ [
+ 1488468138.15,
+ '0.00020149738333333407',
+ ],
+ [
+ 1488468198.15,
+ '0.0003183469666666679',
+ ],
+ [
+ 1488468258.15,
+ '0.0003835329166666845',
+ ],
+ [
+ 1488468318.15,
+ '0.0002485075333333124',
+ ],
+ [
+ 1488468378.15,
+ '0.0003011457166666768',
+ ],
+ [
+ 1488468438.15,
+ '0.00032242785497684965',
+ ],
+ [
+ 1488468498.15,
+ '0.0002659713747457531',
+ ],
+ [
+ 1488468558.15,
+ '0.0003476860333333202',
+ ],
+ [
+ 1488468618.15,
+ '0.00028336403333334794',
+ ],
+ [
+ 1488468678.15,
+ '0.00017132354999998728',
+ ],
+ [
+ 1488468738.15,
+ '0.0003001915833333276',
+ ],
+ [
+ 1488468798.15,
+ '0.0003025715666666725',
+ ],
+ [
+ 1488468858.15,
+ '0.0003012370166666815',
+ ],
+ [
+ 1488468918.15,
+ '0.00030203619999997025',
+ ],
+ [
+ 1488468978.15,
+ '0.0002804355000000314',
+ ],
+ [
+ 1488469038.15,
+ '0.00033194884999998564',
+ ],
+ [
+ 1488469098.15,
+ '0.00025201496666665455',
+ ],
+ [
+ 1488469158.15,
+ '0.0002777531500000189',
+ ],
+ [
+ 1488469218.15,
+ '0.0003314885833333392',
+ ],
+ [
+ 1488469278.15,
+ '0.0002234891422095589',
+ ],
+ [
+ 1488469338.15,
+ '0.000349117355867791',
+ ],
+ [
+ 1488469398.15,
+ '0.0004036731333333303',
+ ],
+ [
+ 1488469458.15,
+ '0.00024553911666667835',
+ ],
+ [
+ 1488469518.15,
+ '0.0003056456833333184',
+ ],
+ [
+ 1488469578.15,
+ '0.0002618737166666681',
+ ],
+ [
+ 1488469638.15,
+ '0.00022972643333331414',
+ ],
+ [
+ 1488469698.15,
+ '0.0003713522500000307',
+ ],
+ [
+ 1488469758.15,
+ '0.00018322576666666515',
+ ],
+ [
+ 1488469818.15,
+ '0.00034534762753952466',
+ ],
+ [
+ 1488469878.15,
+ '0.00028200510008501677',
+ ],
+ [
+ 1488469938.15,
+ '0.0002773708499999768',
+ ],
+ [
+ 1488469998.15,
+ '0.00027547160000001013',
+ ],
+ [
+ 1488470058.15,
+ '0.00031713610000000023',
+ ],
+ [
+ 1488470118.15,
+ '0.00035276853333332525',
+ ],
+ ],
+ },
+ ],
+ cpu_current: [
+ {
+ metric: {
+ },
+ value: [
+ 1488470118.566,
+ '0.00035276853333332525',
+ ],
+ },
+ ],
+ last_update: '2017-03-02T15:55:18.981Z',
+ },
+ },
+};
diff --git a/spec/lib/banzai/filter/emoji_filter_spec.rb b/spec/lib/banzai/filter/emoji_filter_spec.rb
index c8e62f528df..707212e07fd 100644
--- a/spec/lib/banzai/filter/emoji_filter_spec.rb
+++ b/spec/lib/banzai/filter/emoji_filter_spec.rb
@@ -14,12 +14,12 @@ describe Banzai::Filter::EmojiFilter, lib: true do
it 'replaces supported name emoji' do
doc = filter('<p>:heart:</p>')
- expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/2764.png'
+ expect(doc.css('gl-emoji').first.text).to eq '❤'
end
it 'replaces supported unicode emoji' do
doc = filter('<p>❤️</p>')
- expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/2764.png'
+ expect(doc.css('gl-emoji').first.text).to eq '❤'
end
it 'ignores unsupported emoji' do
@@ -30,152 +30,78 @@ describe Banzai::Filter::EmojiFilter, lib: true do
it 'correctly encodes the URL' do
doc = filter('<p>:+1:</p>')
- expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/1F44D.png'
+ expect(doc.css('gl-emoji').first.text).to eq '👍'
end
it 'correctly encodes unicode to the URL' do
doc = filter('<p>👍</p>')
- expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/1F44D.png'
+ expect(doc.css('gl-emoji').first.text).to eq '👍'
end
it 'matches at the start of a string' do
doc = filter(':+1:')
- expect(doc.css('img').size).to eq 1
+ expect(doc.css('gl-emoji').size).to eq 1
end
it 'unicode matches at the start of a string' do
doc = filter("'👍'")
- expect(doc.css('img').size).to eq 1
+ expect(doc.css('gl-emoji').size).to eq 1
end
it 'matches at the end of a string' do
doc = filter('This gets a :-1:')
- expect(doc.css('img').size).to eq 1
+ expect(doc.css('gl-emoji').size).to eq 1
end
it 'unicode matches at the end of a string' do
doc = filter('This gets a 👍')
- expect(doc.css('img').size).to eq 1
+ expect(doc.css('gl-emoji').size).to eq 1
end
it 'matches with adjacent text' do
doc = filter('+1 (:+1:)')
- expect(doc.css('img').size).to eq 1
+ expect(doc.css('gl-emoji').size).to eq 1
end
it 'unicode matches with adjacent text' do
doc = filter('+1 (👍)')
- expect(doc.css('img').size).to eq 1
+ expect(doc.css('gl-emoji').size).to eq 1
end
it 'matches multiple emoji in a row' do
doc = filter(':see_no_evil::hear_no_evil::speak_no_evil:')
- expect(doc.css('img').size).to eq 3
+ expect(doc.css('gl-emoji').size).to eq 3
end
it 'unicode matches multiple emoji in a row' do
doc = filter("'🙈🙉🙊'")
- expect(doc.css('img').size).to eq 3
+ expect(doc.css('gl-emoji').size).to eq 3
end
it 'mixed matches multiple emoji in a row' do
doc = filter("'🙈:see_no_evil:🙉:hear_no_evil:🙊:speak_no_evil:'")
- expect(doc.css('img').size).to eq 6
+ expect(doc.css('gl-emoji').size).to eq 6
end
- it 'has a title attribute' do
+ it 'has a data-name attribute' do
doc = filter(':-1:')
- expect(doc.css('img').first.attr('title')).to eq ':-1:'
+ expect(doc.css('gl-emoji').first.attr('data-name')).to eq 'thumbsdown'
end
- it 'unicode has a title attribute' do
- doc = filter("'👎'")
- expect(doc.css('img').first.attr('title')).to eq ':thumbsdown:'
- end
-
- it 'has an alt attribute' do
+ it 'has a data-unicode-version attribute' do
doc = filter(':-1:')
- expect(doc.css('img').first.attr('alt')).to eq ':-1:'
- end
-
- it 'unicode has an alt attribute' do
- doc = filter("'👎'")
- expect(doc.css('img').first.attr('alt')).to eq ':thumbsdown:'
- end
-
- it 'has an align attribute' do
- doc = filter(':8ball:')
- expect(doc.css('img').first.attr('align')).to eq 'absmiddle'
- end
-
- it 'unicode has an align attribute' do
- doc = filter("'🎱'")
- expect(doc.css('img').first.attr('align')).to eq 'absmiddle'
- end
-
- it 'has an emoji class' do
- doc = filter(':cat:')
- expect(doc.css('img').first.attr('class')).to eq 'emoji'
- end
-
- it 'unicode has an emoji class' do
- doc = filter("'🐱'")
- expect(doc.css('img').first.attr('class')).to eq 'emoji'
- end
-
- it 'has height and width attributes' do
- doc = filter(':dog:')
- img = doc.css('img').first
-
- expect(img.attr('width')).to eq '20'
- expect(img.attr('height')).to eq '20'
- end
-
- it 'unicode has height and width attributes' do
- doc = filter("'🐶'")
- img = doc.css('img').first
-
- expect(img.attr('width')).to eq '20'
- expect(img.attr('height')).to eq '20'
+ expect(doc.css('gl-emoji').first.attr('data-unicode-version')).to eq '6.0'
end
it 'keeps whitespace intact' do
doc = filter('This deserves a :+1:, big time.')
- expect(doc.to_html).to match(/^This deserves a <img.+>, big time\.\z/)
+ expect(doc.to_html).to match(/^This deserves a <gl-emoji.+>, big time\.\z/)
end
it 'unicode keeps whitespace intact' do
doc = filter('This deserves a 🎱, big time.')
- expect(doc.to_html).to match(/^This deserves a <img.+>, big time\.\z/)
- end
-
- it 'uses a custom asset_root context' do
- root = Gitlab.config.gitlab.url + 'gitlab/root'
-
- doc = filter(':smile:', asset_root: root)
- expect(doc.css('img').first.attr('src')).to start_with(root)
- end
-
- it 'uses a custom asset_host context' do
- ActionController::Base.asset_host = 'https://cdn.example.com'
-
- doc = filter(':frowning:', asset_host: 'https://this-is-ignored-i-guess?')
- expect(doc.css('img').first.attr('src')).to start_with('https://cdn.example.com')
- end
-
- it 'uses a custom asset_root context' do
- root = Gitlab.config.gitlab.url + 'gitlab/root'
-
- doc = filter("'🎱'", asset_root: root)
- expect(doc.css('img').first.attr('src')).to start_with(root)
- end
-
- it 'uses a custom asset_host context' do
- ActionController::Base.asset_host = 'https://cdn.example.com'
-
- doc = filter("'🎱'", asset_host: 'https://this-is-ignored-i-guess?')
- expect(doc.css('img').first.attr('src')).to start_with('https://cdn.example.com')
+ expect(doc.to_html).to match(/^This deserves a <gl-emoji.+>, big time\.\z/)
end
end
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index daf8f5c1d6c..03c4879ed6f 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -3,6 +3,24 @@ require 'spec_helper'
describe Gitlab::Auth, lib: true do
let(:gl_auth) { described_class }
+ describe 'constants' do
+ it 'API_SCOPES contains all scopes for API access' do
+ expect(subject::API_SCOPES).to eq [:api, :read_user]
+ end
+
+ it 'OPENID_SCOPES contains all scopes for OpenID Connect' do
+ expect(subject::OPENID_SCOPES).to eq [:openid]
+ end
+
+ it 'DEFAULT_SCOPES contains all default scopes' do
+ expect(subject::DEFAULT_SCOPES).to eq [:api]
+ end
+
+ it 'OPTIONAL_SCOPES contains all non-default scopes' do
+ expect(subject::OPTIONAL_SCOPES).to eq [:read_user, :openid]
+ end
+ end
+
describe 'find_for_git_client' do
context 'build token' do
subject { gl_auth.find_for_git_client('gitlab-ci-token', build.token, project: project, ip: 'ip') }
@@ -118,25 +136,37 @@ describe Gitlab::Auth, lib: true do
end
context 'while using personal access tokens as passwords' do
- let(:user) { create(:user) }
- let(:token_w_api_scope) { create(:personal_access_token, user: user, scopes: ['api']) }
-
it 'succeeds for personal access tokens with the `api` scope' do
- expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.email)
- expect(gl_auth.find_for_git_client(user.email, token_w_api_scope.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :personal_token, full_authentication_abilities))
+ personal_access_token = create(:personal_access_token, scopes: ['api'])
+
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '')
+ expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_token, full_authentication_abilities))
+ end
+
+ it 'succeeds if it is an impersonation token' do
+ impersonation_token = create(:personal_access_token, :impersonation, scopes: ['api'])
+
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '')
+ expect(gl_auth.find_for_git_client('', impersonation_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(impersonation_token.user, nil, :personal_token, full_authentication_abilities))
end
it 'fails for personal access tokens with other scopes' do
- personal_access_token = create(:personal_access_token, user: user, scopes: ['read_user'])
+ personal_access_token = create(:personal_access_token, scopes: ['read_user'])
- expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: user.email)
- expect(gl_auth.find_for_git_client(user.email, personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil))
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: '')
+ expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil))
end
- it 'does not try password auth before personal access tokens' do
- expect(gl_auth).not_to receive(:find_with_user_password)
+ it 'fails for impersonation token with other scopes' do
+ impersonation_token = create(:personal_access_token, scopes: ['read_user'])
- gl_auth.find_for_git_client(user.email, token_w_api_scope.token, project: nil, ip: 'ip')
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: '')
+ expect(gl_auth.find_for_git_client('', impersonation_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil))
+ end
+
+ it 'fails if password is nil' do
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: '')
+ expect(gl_auth.find_for_git_client('', nil, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil))
end
end
@@ -210,6 +240,18 @@ describe Gitlab::Auth, lib: true do
end
end
+ it "does not find user in blocked state" do
+ user.block
+
+ expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
+ end
+
+ it "does not find user in ldap_blocked state" do
+ user.ldap_block
+
+ expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
+ end
+
context "with ldap enabled" do
before do
allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true)
diff --git a/spec/lib/gitlab/award_emoji_spec.rb b/spec/lib/gitlab/award_emoji_spec.rb
deleted file mode 100644
index 00a110e31f8..00000000000
--- a/spec/lib/gitlab/award_emoji_spec.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::AwardEmoji do
- describe '.urls' do
- after do
- Gitlab::AwardEmoji.instance_variable_set(:@urls, nil)
- end
-
- subject { Gitlab::AwardEmoji.urls }
-
- it { is_expected.to be_an_instance_of(Array) }
- it { is_expected.not_to be_empty }
-
- context 'every Hash in the Array' do
- it 'has the correct keys and values' do
- subject.each do |hash|
- expect(hash[:name]).to be_an_instance_of(String)
- expect(hash[:path]).to be_an_instance_of(String)
- end
- end
- end
-
- context 'handles relative root' do
- it 'includes the full path' do
- allow(Gitlab::Application.config).to receive(:relative_url_root).and_return('/gitlab')
-
- subject.each do |hash|
- expect(hash[:name]).to be_an_instance_of(String)
- expect(hash[:path]).to start_with('/gitlab')
- end
- end
- end
- end
-
- describe '.emoji_by_category' do
- it "only contains known categories" do
- undefined_categories = Gitlab::AwardEmoji.emoji_by_category.keys - Gitlab::AwardEmoji::CATEGORIES.keys
- expect(undefined_categories).to be_empty
- end
- end
-end
diff --git a/spec/lib/gitlab/ci/build/image_spec.rb b/spec/lib/gitlab/ci/build/image_spec.rb
new file mode 100644
index 00000000000..382385dfd6b
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/image_spec.rb
@@ -0,0 +1,67 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Build::Image do
+ let(:job) { create(:ci_build, :no_options) }
+
+ describe '#from_image' do
+ subject { described_class.from_image(job) }
+
+ context 'when image is defined in job' do
+ let(:image_name) { 'ruby:2.1' }
+ let(:job) { create(:ci_build, options: { image: image_name } ) }
+
+ it 'fabricates an object of the proper class' do
+ is_expected.to be_kind_of(described_class)
+ end
+
+ it 'populates fabricated object with the proper name attribute' do
+ expect(subject.name).to eq(image_name)
+ end
+
+ context 'when image name is empty' do
+ let(:image_name) { '' }
+
+ it 'does not fabricate an object' do
+ is_expected.to be_nil
+ end
+ end
+ end
+
+ context 'when image is not defined in job' do
+ it 'does not fabricate an object' do
+ is_expected.to be_nil
+ end
+ end
+ end
+
+ describe '#from_services' do
+ subject { described_class.from_services(job) }
+
+ context 'when services are defined in job' do
+ let(:service_image_name) { 'postgres' }
+ let(:job) { create(:ci_build, options: { services: [service_image_name] }) }
+
+ it 'fabricates an non-empty array of objects' do
+ is_expected.to be_kind_of(Array)
+ is_expected.not_to be_empty
+ expect(subject.first.name).to eq(service_image_name)
+ end
+
+ context 'when service image name is empty' do
+ let(:service_image_name) { '' }
+
+ it 'fabricates an empty array' do
+ is_expected.to be_kind_of(Array)
+ is_expected.to be_empty
+ end
+ end
+ end
+
+ context 'when services are not defined in job' do
+ it 'fabricates an empty array' do
+ is_expected.to be_kind_of(Array)
+ is_expected.to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/build/step_spec.rb b/spec/lib/gitlab/ci/build/step_spec.rb
new file mode 100644
index 00000000000..2a314a744ca
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/step_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Build::Step do
+ let(:job) { create(:ci_build, :no_options, commands: "ls -la\ndate") }
+
+ describe '#from_commands' do
+ subject { described_class.from_commands(job) }
+
+ it 'fabricates an object' do
+ expect(subject.name).to eq(:script)
+ expect(subject.script).to eq(['ls -la', 'date'])
+ expect(subject.timeout).to eq(job.timeout)
+ expect(subject.when).to eq('on_success')
+ expect(subject.allow_failure).to be_falsey
+ end
+ end
+
+ describe '#from_after_script' do
+ subject { described_class.from_after_script(job) }
+
+ context 'when after_script is empty' do
+ it 'doesn not fabricate an object' do
+ is_expected.to be_nil
+ end
+ end
+
+ context 'when after_script is not empty' do
+ let(:job) { create(:ci_build, options: { after_script: "ls -la\ndate" }) }
+
+ it 'fabricates an object' do
+ expect(subject.name).to eq(:after_script)
+ expect(subject.script).to eq(['ls -la', 'date'])
+ expect(subject.timeout).to eq(job.timeout)
+ expect(subject.when).to eq('always')
+ expect(subject.allow_failure).to be_truthy
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/cache_spec.rb b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
index 70a327c5183..2ed120f356a 100644
--- a/spec/lib/gitlab/ci/config/entry/cache_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
@@ -24,6 +24,20 @@ describe Gitlab::Ci::Config::Entry::Cache do
expect(entry).to be_valid
end
end
+
+ context 'when key is missing' do
+ let(:config) do
+ { untracked: true,
+ paths: ['some/path/'] }
+ end
+
+ describe '#value' do
+ it 'sets key with the default' do
+ expect(entry.value[:key])
+ .to eq(Gitlab::Ci::Config::Entry::Key.default)
+ end
+ end
+ end
end
context 'when entry value is not correct' do
diff --git a/spec/lib/gitlab/ci/config/entry/factory_spec.rb b/spec/lib/gitlab/ci/config/entry/factory_spec.rb
index 3395b3c645b..8dd48e4efae 100644
--- a/spec/lib/gitlab/ci/config/entry/factory_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/factory_spec.rb
@@ -60,13 +60,13 @@ describe Gitlab::Ci::Config::Entry::Factory do
end
context 'when creating entry with nil value' do
- it 'creates an undefined entry' do
+ it 'creates an unspecified entry' do
entry = factory
.value(nil)
.create!
expect(entry)
- .to be_an_instance_of Gitlab::Ci::Config::Entry::Unspecified
+ .not_to be_specified
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb
index 1f757f12a56..684d01e9056 100644
--- a/spec/lib/gitlab/ci/config/entry/global_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb
@@ -188,7 +188,7 @@ describe Gitlab::Ci::Config::Entry::Global do
it 'contains unspecified nodes' do
expect(global.descendants.first)
- .to be_an_instance_of Gitlab::Ci::Config::Entry::Unspecified
+ .not_to be_specified
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/key_spec.rb b/spec/lib/gitlab/ci/config/entry/key_spec.rb
index 0dd36fe1f44..5d4de60bc8a 100644
--- a/spec/lib/gitlab/ci/config/entry/key_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/key_spec.rb
@@ -31,4 +31,10 @@ describe Gitlab::Ci::Config::Entry::Key do
end
end
end
+
+ describe '.default' do
+ it 'returns default key' do
+ expect(described_class.default).to eq 'default'
+ end
+ end
end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 3f11f0a4516..bc139d5ef28 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -824,6 +824,32 @@ describe Gitlab::Git::Repository, seed_helper: true do
it { is_expected.to eq(17) }
end
+ describe '#count_commits' do
+ context 'with after timestamp' do
+ it 'returns the number of commits after timestamp' do
+ options = { ref: 'master', limit: nil, after: Time.iso8601('2013-03-03T20:15:01+00:00') }
+
+ expect(repository.count_commits(options)).to eq(25)
+ end
+ end
+
+ context 'with before timestamp' do
+ it 'returns the number of commits after timestamp' do
+ options = { ref: 'feature', limit: nil, before: Time.iso8601('2015-03-03T20:15:01+00:00') }
+
+ expect(repository.count_commits(options)).to eq(9)
+ end
+ end
+
+ context 'with path' do
+ it 'returns the number of commits with path ' do
+ options = { ref: 'master', limit: nil, path: "encoding" }
+
+ expect(repository.count_commits(options)).to eq(2)
+ end
+ end
+ end
+
describe "branch_names_contains" do
subject { repository.branch_names_contains(SeedRepo::LastCommit::ID) }
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 1a1280e5198..e47956a365f 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -136,6 +136,7 @@ project:
- slack_slash_commands_service
- irker_service
- pivotaltracker_service
+- prometheus_service
- hipchat_service
- flowdock_service
- assembla_service
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 3bd1f335a89..c718e792461 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -21,6 +21,7 @@ Issue:
- milestone_id
- weight
- time_estimate
+- relative_position
Event:
- id
- target_type
diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb
index fd3769d75b5..c2ab015d5cb 100644
--- a/spec/lib/gitlab/middleware/go_spec.rb
+++ b/spec/lib/gitlab/middleware/go_spec.rb
@@ -15,16 +15,93 @@ describe Gitlab::Middleware::Go, lib: true do
end
describe 'when go-get=1' do
- it 'returns a document' do
- env = { 'rack.input' => '',
- 'QUERY_STRING' => 'go-get=1',
- 'PATH_INFO' => '/group/project/path' }
- resp = middleware.call(env)
- expect(resp[0]).to eq(200)
- expect(resp[1]['Content-Type']).to eq('text/html')
- expected_body = "<!DOCTYPE html><html><head><meta content='#{Gitlab.config.gitlab.host}/group/project git http://#{Gitlab.config.gitlab.host}/group/project.git' name='go-import'></head></html>\n"
- expect(resp[2].body).to eq([expected_body])
+ let(:current_user) { nil }
+
+ context 'with simple 2-segment project path' do
+ let!(:project) { create(:project, :private) }
+
+ context 'with subpackages' do
+ let(:path) { "#{project.full_path}/subpackage" }
+
+ it 'returns the full project path' do
+ expect_response_with_path(go, project.full_path)
+ end
+ end
+
+ context 'without subpackages' do
+ let(:path) { project.full_path }
+
+ it 'returns the full project path' do
+ expect_response_with_path(go, project.full_path)
+ end
+ end
+ end
+
+ context 'with a nested project path' do
+ let(:group) { create(:group, :nested) }
+ let!(:project) { create(:project, :public, namespace: group) }
+
+ shared_examples 'a nested project' do
+ context 'when the project is public' do
+ it 'returns the full project path' do
+ expect_response_with_path(go, project.full_path)
+ end
+ end
+
+ context 'when the project is private' do
+ before do
+ project.update_attribute(:visibility_level, Project::PRIVATE)
+ end
+
+ context 'with access to the project' do
+ let(:current_user) { project.creator }
+
+ before do
+ project.team.add_master(current_user)
+ end
+
+ it 'returns the full project path' do
+ expect_response_with_path(go, project.full_path)
+ end
+ end
+
+ context 'without access to the project' do
+ it 'returns the 2-segment group path' do
+ expect_response_with_path(go, group.full_path)
+ end
+ end
+ end
+ end
+
+ context 'with subpackages' do
+ let(:path) { "#{project.full_path}/subpackage" }
+
+ it_behaves_like 'a nested project'
+ end
+
+ context 'without subpackages' do
+ let(:path) { project.full_path }
+
+ it_behaves_like 'a nested project'
+ end
end
end
+
+ def go
+ env = {
+ 'rack.input' => '',
+ 'QUERY_STRING' => 'go-get=1',
+ 'PATH_INFO' => "/#{path}",
+ 'warden' => double(authenticate: current_user)
+ }
+ middleware.call(env)
+ end
+
+ def expect_response_with_path(response, path)
+ expect(response[0]).to eq(200)
+ expect(response[1]['Content-Type']).to eq('text/html')
+ expected_body = "<!DOCTYPE html><html><head><meta content='#{Gitlab.config.gitlab.host}/#{path} git http://#{Gitlab.config.gitlab.host}/#{path}.git' name='go-import'></head></html>\n"
+ expect(response[2].body).to eq([expected_body])
+ end
end
end
diff --git a/spec/lib/gitlab/prometheus_spec.rb b/spec/lib/gitlab/prometheus_spec.rb
new file mode 100644
index 00000000000..280264188e2
--- /dev/null
+++ b/spec/lib/gitlab/prometheus_spec.rb
@@ -0,0 +1,143 @@
+require 'spec_helper'
+
+describe Gitlab::Prometheus, lib: true do
+ include PrometheusHelpers
+
+ subject { described_class.new(api_url: 'https://prometheus.example.com') }
+
+ describe '#ping' do
+ it 'issues a "query" request to the API endpoint' do
+ req_stub = stub_prometheus_request(prometheus_query_url('1'), body: prometheus_value_body('vector'))
+
+ expect(subject.ping).to eq({ "resultType" => "vector", "result" => [{ "metric" => {}, "value" => [1488772511.004, "0.000041021495238095323"] }] })
+ expect(req_stub).to have_been_requested
+ end
+ end
+
+ # This shared examples expect:
+ # - query_url: A query URL
+ # - execute_query: A query call
+ shared_examples 'failure response' do
+ context 'when request returns 400 with an error message' do
+ it 'raises a Gitlab::PrometheusError error' do
+ req_stub = stub_prometheus_request(query_url, status: 400, body: { error: 'bar!' })
+
+ expect { execute_query }
+ .to raise_error(Gitlab::PrometheusError, 'bar!')
+ expect(req_stub).to have_been_requested
+ end
+ end
+
+ context 'when request returns 400 without an error message' do
+ it 'raises a Gitlab::PrometheusError error' do
+ req_stub = stub_prometheus_request(query_url, status: 400)
+
+ expect { execute_query }
+ .to raise_error(Gitlab::PrometheusError, 'Bad data received')
+ expect(req_stub).to have_been_requested
+ end
+ end
+
+ context 'when request returns 500' do
+ it 'raises a Gitlab::PrometheusError error' do
+ req_stub = stub_prometheus_request(query_url, status: 500, body: { message: 'FAIL!' })
+
+ expect { execute_query }
+ .to raise_error(Gitlab::PrometheusError, '500 - {"message":"FAIL!"}')
+ expect(req_stub).to have_been_requested
+ end
+ end
+ end
+
+ describe '#query' do
+ let(:prometheus_query) { prometheus_cpu_query('env-slug') }
+ let(:query_url) { prometheus_query_url(prometheus_query) }
+
+ context 'when request returns vector results' do
+ it 'returns data from the API call' do
+ req_stub = stub_prometheus_request(query_url, body: prometheus_value_body('vector'))
+
+ expect(subject.query(prometheus_query)).to eq [{ "metric" => {}, "value" => [1488772511.004, "0.000041021495238095323"] }]
+ expect(req_stub).to have_been_requested
+ end
+ end
+
+ context 'when request returns matrix results' do
+ it 'returns nil' do
+ req_stub = stub_prometheus_request(query_url, body: prometheus_value_body('matrix'))
+
+ expect(subject.query(prometheus_query)).to be_nil
+ expect(req_stub).to have_been_requested
+ end
+ end
+
+ context 'when request returns no data' do
+ it 'returns []' do
+ req_stub = stub_prometheus_request(query_url, body: prometheus_empty_body('vector'))
+
+ expect(subject.query(prometheus_query)).to be_empty
+ expect(req_stub).to have_been_requested
+ end
+ end
+
+ it_behaves_like 'failure response' do
+ let(:execute_query) { subject.query(prometheus_query) }
+ end
+ end
+
+ describe '#query_range' do
+ let(:prometheus_query) { prometheus_memory_query('env-slug') }
+ let(:query_url) { prometheus_query_range_url(prometheus_query) }
+
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ context 'when a start time is passed' do
+ let(:query_url) { prometheus_query_range_url(prometheus_query, start: 2.hours.ago) }
+
+ it 'passed it in the requested URL' do
+ req_stub = stub_prometheus_request(query_url, body: prometheus_values_body('vector'))
+
+ subject.query_range(prometheus_query, start: 2.hours.ago)
+ expect(req_stub).to have_been_requested
+ end
+ end
+
+ context 'when request returns vector results' do
+ it 'returns nil' do
+ req_stub = stub_prometheus_request(query_url, body: prometheus_values_body('vector'))
+
+ expect(subject.query_range(prometheus_query)).to be_nil
+ expect(req_stub).to have_been_requested
+ end
+ end
+
+ context 'when request returns matrix results' do
+ it 'returns data from the API call' do
+ req_stub = stub_prometheus_request(query_url, body: prometheus_values_body('matrix'))
+
+ expect(subject.query_range(prometheus_query)).to eq([
+ {
+ "metric" => {},
+ "values" => [[1488758662.506, "0.00002996364761904785"], [1488758722.506, "0.00003090239047619091"]]
+ }
+ ])
+ expect(req_stub).to have_been_requested
+ end
+ end
+
+ context 'when request returns no data' do
+ it 'returns []' do
+ req_stub = stub_prometheus_request(query_url, body: prometheus_empty_body('matrix'))
+
+ expect(subject.query_range(prometheus_query)).to be_empty
+ expect(req_stub).to have_been_requested
+ end
+ end
+
+ it_behaves_like 'failure response' do
+ let(:execute_query) { subject.query_range(prometheus_query) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index a32c6131030..8e5e8288c49 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -199,4 +199,58 @@ describe Gitlab::Workhorse, lib: true do
end
end
end
+
+ describe '.set_key_and_notify' do
+ let(:key) { 'test-key' }
+ let(:value) { 'test-value' }
+
+ subject { described_class.set_key_and_notify(key, value, overwrite: overwrite) }
+
+ shared_examples 'set and notify' do
+ it 'set and return the same value' do
+ is_expected.to eq(value)
+ end
+
+ it 'set and notify' do
+ expect_any_instance_of(Redis).to receive(:publish)
+ .with(described_class::NOTIFICATION_CHANNEL, "test-key=test-value")
+
+ subject
+ end
+ end
+
+ context 'when we set a new key' do
+ let(:overwrite) { true }
+
+ it_behaves_like 'set and notify'
+ end
+
+ context 'when we set an existing key' do
+ let(:old_value) { 'existing-key' }
+
+ before do
+ described_class.set_key_and_notify(key, old_value, overwrite: true)
+ end
+
+ context 'and overwrite' do
+ let(:overwrite) { true }
+
+ it_behaves_like 'set and notify'
+ end
+
+ context 'and do not overwrite' do
+ let(:overwrite) { false }
+
+ it 'try to set but return the previous value' do
+ is_expected.to eq(old_value)
+ end
+
+ it 'does not notify' do
+ expect_any_instance_of(Redis).not_to receive(:publish)
+
+ subject
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/chat_team_spec.rb b/spec/models/chat_team_spec.rb
index 1aab161ec13..5283561a83f 100644
--- a/spec/models/chat_team_spec.rb
+++ b/spec/models/chat_team_spec.rb
@@ -1,9 +1,14 @@
require 'spec_helper'
describe ChatTeam, type: :model do
+ subject { create(:chat_team) }
+
# Associations
it { is_expected.to belong_to(:namespace) }
+ # Validations
+ it { is_expected.to validate_uniqueness_of(:namespace) }
+
# Fields
it { is_expected.to respond_to(:name) }
it { is_expected.to respond_to(:team_id) }
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 2db42a94077..fd6ea2d6722 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -345,11 +345,11 @@ describe Ci::Build, :models do
describe '#expanded_environment_name' do
subject { build.expanded_environment_name }
- context 'when environment uses $CI_BUILD_REF_NAME' do
+ context 'when environment uses $CI_COMMIT_REF_NAME' do
let(:build) do
create(:ci_build,
ref: 'master',
- environment: 'review/$CI_BUILD_REF_NAME')
+ environment: 'review/$CI_COMMIT_REF_NAME')
end
it { is_expected.to eq('review/master') }
@@ -915,7 +915,7 @@ describe Ci::Build, :models do
end
context 'referenced with a variable' do
- let(:build) { create(:ci_build, pipeline: pipeline, environment: "foo-$CI_BUILD_REF_NAME") }
+ let(:build) { create(:ci_build, pipeline: pipeline, environment: "foo-$CI_COMMIT_REF_NAME") }
it { is_expected.to eq(@environment) }
end
@@ -1286,23 +1286,25 @@ describe Ci::Build, :models do
[
{ key: 'CI', value: 'true', public: true },
{ key: 'GITLAB_CI', value: 'true', public: true },
- { key: 'CI_BUILD_ID', value: build.id.to_s, public: true },
- { key: 'CI_BUILD_TOKEN', value: build.token, public: false },
- { key: 'CI_BUILD_REF', value: build.sha, public: true },
- { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true },
- { key: 'CI_BUILD_REF_NAME', value: 'master', public: true },
- { key: 'CI_BUILD_REF_SLUG', value: 'master', public: true },
- { key: 'CI_BUILD_NAME', value: 'test', public: true },
- { key: 'CI_BUILD_STAGE', value: 'test', public: true },
{ key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
{ key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
{ key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true },
+ { key: 'CI_JOB_ID', value: build.id.to_s, public: true },
+ { key: 'CI_JOB_NAME', value: 'test', public: true },
+ { key: 'CI_JOB_STAGE', value: 'test', public: true },
+ { key: 'CI_JOB_TOKEN', value: build.token, public: false },
+ { key: 'CI_COMMIT_SHA', value: build.sha, public: true },
+ { key: 'CI_COMMIT_REF_NAME', value: build.ref, public: true },
+ { key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true },
{ key: 'CI_PROJECT_ID', value: project.id.to_s, public: true },
{ key: 'CI_PROJECT_NAME', value: project.path, public: true },
{ key: 'CI_PROJECT_PATH', value: project.full_path, public: true },
{ key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true },
{ key: 'CI_PROJECT_URL', value: project.web_url, public: true },
- { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true }
+ { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true },
+ { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true },
+ { key: 'CI_REGISTRY_PASSWORD', value: build.token, public: false },
+ { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false },
]
end
@@ -1317,7 +1319,7 @@ describe Ci::Build, :models do
build.yaml_variables = []
end
- it { is_expected.to eq(predefined_variables) }
+ it { is_expected.to include(*predefined_variables) }
end
context 'when build has user' do
@@ -1355,7 +1357,7 @@ describe Ci::Build, :models do
end
let(:manual_variable) do
- { key: 'CI_BUILD_MANUAL', value: 'true', public: true }
+ { key: 'CI_JOB_MANUAL', value: 'true', public: true }
end
it { is_expected.to include(manual_variable) }
@@ -1363,7 +1365,7 @@ describe Ci::Build, :models do
context 'when build is for tag' do
let(:tag_variable) do
- { key: 'CI_BUILD_TAG', value: 'master', public: true }
+ { key: 'CI_COMMIT_TAG', value: 'master', public: true }
end
before do
@@ -1392,7 +1394,7 @@ describe Ci::Build, :models do
{ key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1', public: false }
end
let(:predefined_trigger_variable) do
- { key: 'CI_BUILD_TRIGGERED', value: 'true', public: true }
+ { key: 'CI_PIPELINE_TRIGGERED', value: 'true', public: true }
end
before do
@@ -1416,7 +1418,7 @@ describe Ci::Build, :models do
context 'when config is not found' do
let(:config) { nil }
- it { is_expected.to eq(predefined_variables) }
+ it { is_expected.to include(*predefined_variables) }
end
context 'when config does not have a questioned job' do
@@ -1428,7 +1430,7 @@ describe Ci::Build, :models do
})
end
- it { is_expected.to eq(predefined_variables) }
+ it { is_expected.to include(*predefined_variables) }
end
context 'when config has variables' do
@@ -1446,7 +1448,8 @@ describe Ci::Build, :models do
[{ key: 'KEY', value: 'value', public: true }]
end
- it { is_expected.to eq(predefined_variables + variables) }
+ it { is_expected.to include(*predefined_variables) }
+ it { is_expected.to include(*variables) }
end
end
end
diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb
index 074cf1a0bd7..1bcb673cb16 100644
--- a/spec/models/ci/trigger_spec.rb
+++ b/spec/models/ci/trigger_spec.rb
@@ -22,4 +22,62 @@ describe Ci::Trigger, models: true do
expect(trigger.token).to eq('token')
end
end
+
+ describe '#short_token' do
+ let(:trigger) { create(:ci_trigger, token: '12345678') }
+
+ subject { trigger.short_token }
+
+ it 'returns shortened token' do
+ is_expected.to eq('1234')
+ end
+ end
+
+ describe '#legacy?' do
+ let(:trigger) { create(:ci_trigger, owner: owner, project: project) }
+
+ subject { trigger }
+
+ context 'when owner is blank' do
+ let(:owner) { nil }
+
+ it { is_expected.to be_legacy }
+ end
+
+ context 'when owner is set' do
+ let(:owner) { create(:user) }
+
+ it { is_expected.not_to be_legacy }
+ end
+ end
+
+ describe '#can_access_project?' do
+ let(:trigger) { create(:ci_trigger, owner: owner, project: project) }
+
+ context 'when owner is blank' do
+ let(:owner) { nil }
+
+ subject { trigger.can_access_project? }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when owner is set' do
+ let(:owner) { create(:user) }
+
+ subject { trigger.can_access_project? }
+
+ context 'and is member of the project' do
+ before do
+ project.team << [owner, :developer]
+ end
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'and is not member of the project' do
+ it { is_expected.to eq(false) }
+ end
+ end
+ end
end
diff --git a/spec/models/concerns/relative_positioning_spec.rb b/spec/models/concerns/relative_positioning_spec.rb
new file mode 100644
index 00000000000..69906382545
--- /dev/null
+++ b/spec/models/concerns/relative_positioning_spec.rb
@@ -0,0 +1,104 @@
+require 'spec_helper'
+
+describe Issue, 'RelativePositioning' do
+ let(:project) { create(:empty_project) }
+ let(:issue) { create(:issue, project: project) }
+ let(:issue1) { create(:issue, project: project) }
+ let(:new_issue) { create(:issue, project: project) }
+
+ before do
+ [issue, issue1].each do |issue|
+ issue.move_to_end && issue.save
+ end
+ end
+
+ describe '#min_relative_position' do
+ it 'returns maximum position' do
+ expect(issue.min_relative_position).to eq issue.relative_position
+ end
+ end
+
+ describe '#max_relative_position' do
+ it 'returns maximum position' do
+ expect(issue.max_relative_position).to eq issue1.relative_position
+ end
+ end
+
+ describe '#prev_relative_position' do
+ it 'returns previous position if there is an issue above' do
+ expect(issue1.prev_relative_position).to eq issue.relative_position
+ end
+
+ it 'returns minimum position if there is no issue above' do
+ expect(issue.prev_relative_position).to eq RelativePositioning::MIN_POSITION
+ end
+ end
+
+ describe '#next_relative_position' do
+ it 'returns next position if there is an issue below' do
+ expect(issue.next_relative_position).to eq issue1.relative_position
+ end
+
+ it 'returns next position if there is no issue below' do
+ expect(issue1.next_relative_position).to eq RelativePositioning::MAX_POSITION
+ end
+ end
+
+ describe '#move_before' do
+ it 'moves issue before' do
+ [issue1, issue].each(&:move_to_end)
+
+ issue.move_before(issue1)
+
+ expect(issue.relative_position).to be < issue1.relative_position
+ end
+ end
+
+ describe '#move_after' do
+ it 'moves issue after' do
+ [issue, issue1].each(&:move_to_end)
+
+ issue.move_after(issue1)
+
+ expect(issue.relative_position).to be > issue1.relative_position
+ end
+ end
+
+ describe '#move_to_end' do
+ it 'moves issue to the end' do
+ new_issue.move_to_end
+
+ expect(new_issue.relative_position).to be > issue1.relative_position
+ end
+ end
+
+ describe '#move_between' do
+ it 'positions issue between two other' do
+ new_issue.move_between(issue, issue1)
+
+ expect(new_issue.relative_position).to be > issue.relative_position
+ expect(new_issue.relative_position).to be < issue1.relative_position
+ end
+
+ it 'positions issue between on top' do
+ new_issue.move_between(nil, issue)
+
+ expect(new_issue.relative_position).to be < issue.relative_position
+ end
+
+ it 'positions issue between to end' do
+ new_issue.move_between(issue1, nil)
+
+ expect(new_issue.relative_position).to be > issue1.relative_position
+ end
+
+ it 'positions issues even when after and before positions are the same' do
+ issue1.update relative_position: issue.relative_position
+
+ new_issue.move_between(issue, issue1)
+
+ expect(new_issue.relative_position).to be > issue.relative_position
+ expect(issue.relative_position).to be < issue1.relative_position
+ end
+ end
+end
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index dce18f008f8..b4305e92812 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -271,7 +271,11 @@ describe Environment, models: true do
context 'when the environment is unavailable' do
let(:project) { create(:kubernetes_project) }
- before { environment.stop }
+
+ before do
+ environment.stop
+ end
+
it { is_expected.to be_falsy }
end
end
@@ -281,20 +285,85 @@ describe Environment, models: true do
subject { environment.terminals }
context 'when the environment has terminals' do
- before { allow(environment).to receive(:has_terminals?).and_return(true) }
+ before do
+ allow(environment).to receive(:has_terminals?).and_return(true)
+ end
it 'returns the terminals from the deployment service' do
- expect(project.deployment_service).
- to receive(:terminals).with(environment).
- and_return(:fake_terminals)
+ expect(project.deployment_service)
+ .to receive(:terminals).with(environment)
+ .and_return(:fake_terminals)
is_expected.to eq(:fake_terminals)
end
end
context 'when the environment does not have terminals' do
- before { allow(environment).to receive(:has_terminals?).and_return(false) }
- it { is_expected.to eq(nil) }
+ before do
+ allow(environment).to receive(:has_terminals?).and_return(false)
+ end
+
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe '#has_metrics?' do
+ subject { environment.has_metrics? }
+
+ context 'when the enviroment is available' do
+ context 'with a deployment service' do
+ let(:project) { create(:prometheus_project) }
+
+ context 'and a deployment' do
+ let!(:deployment) { create(:deployment, environment: environment) }
+ it { is_expected.to be_truthy }
+ end
+
+ context 'but no deployments' do
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ context 'without a monitoring service' do
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ context 'when the environment is unavailable' do
+ let(:project) { create(:prometheus_project) }
+
+ before do
+ environment.stop
+ end
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ describe '#metrics' do
+ let(:project) { create(:prometheus_project) }
+ subject { environment.metrics }
+
+ context 'when the environment has metrics' do
+ before do
+ allow(environment).to receive(:has_metrics?).and_return(true)
+ end
+
+ it 'returns the metrics from the deployment service' do
+ expect(project.monitoring_service)
+ .to receive(:metrics).with(environment)
+ .and_return(:fake_metrics)
+
+ is_expected.to eq(:fake_metrics)
+ end
+ end
+
+ context 'when the environment does not have metrics' do
+ before do
+ allow(environment).to receive(:has_metrics?).and_return(false)
+ end
+
+ it { is_expected.to be_nil }
end
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 3f9c4289de9..757f3921450 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -28,6 +28,20 @@ describe Namespace, models: true do
expect(nested).not_to be_valid
expect(nested.errors[:parent_id].first).to eq('has too deep level of nesting')
end
+
+ describe 'reserved path validation' do
+ context 'nested group' do
+ let(:group) { build(:group, :nested, path: 'tree') }
+
+ it { expect(group).not_to be_valid }
+ end
+
+ context 'top-level group' do
+ let(:group) { build(:group, path: 'tree') }
+
+ it { expect(group).to be_valid }
+ end
+ end
end
describe "Respond to" do
@@ -151,7 +165,7 @@ describe Namespace, models: true do
describe :rm_dir do
let!(:project) { create(:empty_project, namespace: namespace) }
- let!(:path) { File.join(Gitlab.config.repositories.storages.default, namespace.full_path) }
+ let!(:path) { File.join(Gitlab.config.repositories.storages.default['path'], namespace.full_path) }
it "removes its dirs when deleted" do
namespace.destroy
diff --git a/spec/models/personal_access_token_spec.rb b/spec/models/personal_access_token_spec.rb
index 46eb71cef14..823623d96fa 100644
--- a/spec/models/personal_access_token_spec.rb
+++ b/spec/models/personal_access_token_spec.rb
@@ -1,15 +1,61 @@
require 'spec_helper'
describe PersonalAccessToken, models: true do
- describe ".generate" do
- it "generates a random token" do
- personal_access_token = PersonalAccessToken.generate({})
- expect(personal_access_token.token).to be_present
+ describe '.build' do
+ let(:personal_access_token) { build(:personal_access_token) }
+ let(:invalid_personal_access_token) { build(:personal_access_token, :invalid) }
+
+ it 'is a valid personal access token' do
+ expect(personal_access_token).to be_valid
+ end
+
+ it 'ensures that the token is generated' do
+ invalid_personal_access_token.save!
+
+ expect(invalid_personal_access_token).to be_valid
+ expect(invalid_personal_access_token.token).not_to be_nil
end
+ end
+
+ describe ".active?" do
+ let(:active_personal_access_token) { build(:personal_access_token) }
+ let(:revoked_personal_access_token) { build(:personal_access_token, :revoked) }
+ let(:expired_personal_access_token) { build(:personal_access_token, :expired) }
+
+ it "returns false if the personal_access_token is revoked" do
+ expect(revoked_personal_access_token).not_to be_active
+ end
+
+ it "returns false if the personal_access_token is expired" do
+ expect(expired_personal_access_token).not_to be_active
+ end
+
+ it "returns true if the personal_access_token is not revoked and not expired" do
+ expect(active_personal_access_token).to be_active
+ end
+ end
+
+ context "validations" do
+ let(:personal_access_token) { build(:personal_access_token) }
+
+ it "requires at least one scope" do
+ personal_access_token.scopes = []
+
+ expect(personal_access_token).not_to be_valid
+ expect(personal_access_token.errors[:scopes].first).to eq "can't be blank"
+ end
+
+ it "allows creating a token with API scopes" do
+ personal_access_token.scopes = [:api, :read_user]
+
+ expect(personal_access_token).to be_valid
+ end
+
+ it "rejects creating a token with non-API scopes" do
+ personal_access_token.scopes = [:openid, :api]
- it "doesn't save the record" do
- personal_access_token = PersonalAccessToken.generate({})
- expect(personal_access_token).not_to be_persisted
+ expect(personal_access_token).not_to be_valid
+ expect(personal_access_token.errors[:scopes].first).to eq "can only contain API scopes"
end
end
end
diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb
index 585c899cdf9..bf7950ef1c9 100644
--- a/spec/models/project_services/kubernetes_service_spec.rb
+++ b/spec/models/project_services/kubernetes_service_spec.rb
@@ -74,8 +74,10 @@ describe KubernetesService, models: true, caching: true do
describe '#initialize_properties' do
context 'with a project' do
- it 'defaults to the project name' do
- expect(described_class.new(project: project).namespace).to eq(project.name)
+ let(:namespace_name) { "#{project.path}-#{project.id}" }
+
+ it 'defaults to the project name with ID' do
+ expect(described_class.new(project: project).namespace).to eq(namespace_name)
end
end
diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb
new file mode 100644
index 00000000000..d15079b686b
--- /dev/null
+++ b/spec/models/project_services/prometheus_service_spec.rb
@@ -0,0 +1,104 @@
+require 'spec_helper'
+
+describe PrometheusService, models: true, caching: true do
+ include PrometheusHelpers
+ include ReactiveCachingHelpers
+
+ let(:project) { create(:prometheus_project) }
+ let(:service) { project.prometheus_service }
+
+ describe "Associations" do
+ it { is_expected.to belong_to :project }
+ end
+
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:api_url) }
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:api_url) }
+ end
+ end
+
+ describe '#test' do
+ let!(:req_stub) { stub_prometheus_request(prometheus_query_url('1'), body: prometheus_value_body('vector')) }
+
+ context 'success' do
+ it 'reads the discovery endpoint' do
+ expect(service.test[:success]).to be_truthy
+ expect(req_stub).to have_been_requested
+ end
+ end
+
+ context 'failure' do
+ let!(:req_stub) { stub_prometheus_request(prometheus_query_url('1'), status: 404) }
+
+ it 'fails to read the discovery endpoint' do
+ expect(service.test[:success]).to be_falsy
+ expect(req_stub).to have_been_requested
+ end
+ end
+ end
+
+ describe '#metrics' do
+ let(:environment) { build_stubbed(:environment, slug: 'env-slug') }
+ subject { service.metrics(environment) }
+
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ context 'with valid data' do
+ before do
+ stub_reactive_cache(service, prometheus_data, 'env-slug')
+ end
+
+ it 'returns reactive data' do
+ is_expected.to eq(prometheus_data)
+ end
+ end
+ end
+
+ describe '#calculate_reactive_cache' do
+ let(:environment) { build_stubbed(:environment, slug: 'env-slug') }
+
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ subject do
+ service.calculate_reactive_cache(environment.slug)
+ end
+
+ context 'when service is inactive' do
+ before do
+ service.active = false
+ end
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when Prometheus responds with valid data' do
+ before do
+ stub_all_prometheus_requests(environment.slug)
+ end
+
+ it { expect(subject.to_json).to eq(prometheus_data.to_json) }
+ end
+
+ [404, 500].each do |status|
+ context "when Prometheus responds with #{status}" do
+ before do
+ stub_all_prometheus_requests(environment.slug, status: status, body: 'QUERY FAILED!')
+ end
+
+ it { is_expected.to eq(success: false, result: %(#{status} - "QUERY FAILED!")) }
+ end
+ end
+ end
+end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 84bdcbe8e59..e120e21af06 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -179,7 +179,7 @@ describe Project, models: true do
let(:project2) { build(:empty_project, repository_storage: 'missing') }
before do
- storages = { 'custom' => 'tmp/tests/custom_repositories' }
+ storages = { 'custom' => { 'path' => 'tmp/tests/custom_repositories' } }
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
end
@@ -381,7 +381,7 @@ describe Project, models: true do
before do
FileUtils.mkdir('tmp/tests/custom_repositories')
- storages = { 'custom' => 'tmp/tests/custom_repositories' }
+ storages = { 'custom' => { 'path' => 'tmp/tests/custom_repositories' } }
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
end
@@ -947,8 +947,8 @@ describe Project, models: true do
before do
storages = {
- 'default' => 'tmp/tests/repositories',
- 'picked' => 'tmp/tests/repositories',
+ 'default' => { 'path' => 'tmp/tests/repositories' },
+ 'picked' => { 'path' => 'tmp/tests/repositories' },
}
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index eb992e1354e..274e4f00a0a 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -1042,7 +1042,7 @@ describe Repository, models: true do
it 'expires the cache for all branches' do
expect(cache).to receive(:expire).
- at_least(repository.branches.length).
+ at_least(repository.branches.length * 2).
times
repository.expire_branch_cache
@@ -1050,14 +1050,14 @@ describe Repository, models: true do
it 'expires the cache for all branches when the root branch is given' do
expect(cache).to receive(:expire).
- at_least(repository.branches.length).
+ at_least(repository.branches.length * 2).
times
repository.expire_branch_cache(repository.root_ref)
end
it 'expires the cache for a specific branch' do
- expect(cache).to receive(:expire).once
+ expect(cache).to receive(:expire).twice
repository.expire_branch_cache('foo')
end
@@ -1742,6 +1742,29 @@ describe Repository, models: true do
end
end
+ describe '#commit_count_for_ref' do
+ let(:project) { create :empty_project }
+
+ context 'with a non-existing repository' do
+ it 'returns 0' do
+ expect(project.repository.commit_count_for_ref('master')).to eq(0)
+ end
+ end
+
+ context 'with empty repository' do
+ it 'returns 0' do
+ project.create_repository
+ expect(project.repository.commit_count_for_ref('master')).to eq(0)
+ end
+ end
+
+ context 'when searching for the root ref' do
+ it 'returns the same count as #commit_count' do
+ expect(repository.commit_count_for_ref(repository.root_ref)).to eq(repository.commit_count)
+ end
+ end
+ end
+
describe '#cache_method_output', caching: true do
context 'with a non-existing repository' do
let(:value) do
diff --git a/spec/policies/ci/trigger_policy_spec.rb b/spec/policies/ci/trigger_policy_spec.rb
new file mode 100644
index 00000000000..63ad5eb7322
--- /dev/null
+++ b/spec/policies/ci/trigger_policy_spec.rb
@@ -0,0 +1,103 @@
+require 'spec_helper'
+
+describe Ci::TriggerPolicy, :models do
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project) }
+ let(:trigger) { create(:ci_trigger, project: project, owner: owner) }
+
+ let(:policies) do
+ described_class.abilities(user, trigger).to_set
+ end
+
+ shared_examples 'allows to admin and manage trigger' do
+ it 'does include ability to admin trigger' do
+ expect(policies).to include :admin_trigger
+ end
+
+ it 'does include ability to manage trigger' do
+ expect(policies).to include :manage_trigger
+ end
+ end
+
+ shared_examples 'allows to manage trigger' do
+ it 'does not include ability to admin trigger' do
+ expect(policies).not_to include :admin_trigger
+ end
+
+ it 'does include ability to manage trigger' do
+ expect(policies).to include :manage_trigger
+ end
+ end
+
+ shared_examples 'disallows to admin and manage trigger' do
+ it 'does not include ability to admin trigger' do
+ expect(policies).not_to include :admin_trigger
+ end
+
+ it 'does not include ability to manage trigger' do
+ expect(policies).not_to include :manage_trigger
+ end
+ end
+
+ describe '#rules' do
+ context 'when owner is undefined' do
+ let(:owner) { nil }
+
+ context 'when user is master of the project' do
+ before do
+ project.team << [user, :master]
+ end
+
+ it_behaves_like 'allows to admin and manage trigger'
+ end
+
+ context 'when user is developer of the project' do
+ before do
+ project.team << [user, :developer]
+ end
+
+ it_behaves_like 'disallows to admin and manage trigger'
+ end
+
+ context 'when user is not member of the project' do
+ it_behaves_like 'disallows to admin and manage trigger'
+ end
+ end
+
+ context 'when owner is an user' do
+ let(:owner) { user }
+
+ context 'when user is master of the project' do
+ before do
+ project.team << [user, :master]
+ end
+
+ it_behaves_like 'allows to admin and manage trigger'
+ end
+ end
+
+ context 'when owner is another user' do
+ let(:owner) { create(:user) }
+
+ context 'when user is master of the project' do
+ before do
+ project.team << [user, :master]
+ end
+
+ it_behaves_like 'allows to manage trigger'
+ end
+
+ context 'when user is developer of the project' do
+ before do
+ project.team << [user, :developer]
+ end
+
+ it_behaves_like 'disallows to admin and manage trigger'
+ end
+
+ context 'when user is not member of the project' do
+ it_behaves_like 'disallows to admin and manage trigger'
+ end
+ end
+ end
+end
diff --git a/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb b/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb
new file mode 100644
index 00000000000..6443f86b6a1
--- /dev/null
+++ b/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+describe Projects::Settings::DeployKeysPresenter do
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+ let(:deploy_key) { create(:deploy_key, public: true) }
+
+ let!(:deploy_keys_project) do
+ create(:deploy_keys_project, project: project, deploy_key: deploy_key)
+ end
+
+ subject(:presenter) do
+ described_class.new(project, current_user: user)
+ end
+
+ it 'inherits from Gitlab::View::Presenter::Simple' do
+ expect(described_class.superclass).to eq(Gitlab::View::Presenter::Simple)
+ end
+
+ describe '#enabled_keys' do
+ it 'returns currently enabled keys' do
+ expect(presenter.enabled_keys).to eq [deploy_keys_project.deploy_key]
+ end
+
+ it 'does not contain enabled_keys inside available_keys' do
+ expect(presenter.available_keys).not_to include deploy_key
+ end
+
+ it 'returns the enabled_keys size' do
+ expect(presenter.enabled_keys_size).to eq(1)
+ end
+
+ it 'returns true if there is any enabled_keys' do
+ expect(presenter.any_keys_enabled?).to eq(true)
+ end
+ end
+
+ describe '#available_keys/#available_project_keys' do
+ let(:other_deploy_key) { create(:another_deploy_key) }
+
+ before do
+ project_key = create(:deploy_keys_project, deploy_key: other_deploy_key)
+ project_key.project.add_developer(user)
+ end
+
+ it 'returns the current available_keys' do
+ expect(presenter.available_keys).not_to be_empty
+ end
+
+ it 'returns the current available_project_keys' do
+ expect(presenter.available_project_keys).not_to be_empty
+ end
+
+ it 'returns false if any available_project_keys are enabled' do
+ expect(presenter.any_available_project_keys_enabled?).to eq(true)
+ end
+
+ it 'returns the available_project_keys size' do
+ expect(presenter.available_project_keys_size).to eq(1)
+ end
+
+ it 'shows if there is an available key' do
+ expect(presenter.key_available?(deploy_key)).to eq(false)
+ end
+ end
+end
diff --git a/spec/requests/api/api_internal_helpers_spec.rb b/spec/requests/api/api_internal_helpers_spec.rb
index be4bc39ada2..f5265ea60ff 100644
--- a/spec/requests/api/api_internal_helpers_spec.rb
+++ b/spec/requests/api/api_internal_helpers_spec.rb
@@ -21,7 +21,7 @@ describe ::API::Helpers::InternalHelpers do
# Relative and absolute storage paths, with and without trailing /
['.', './', Dir.pwd, Dir.pwd + '/'].each do |storage_path|
context "storage path is #{storage_path}" do
- subject { clean_project_path(project_path, [storage_path]) }
+ subject { clean_project_path(project_path, [{ 'path' => storage_path }]) }
it { is_expected.to eq(expected) }
end
diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb
index 9756991162e..f4d4a8a2cc7 100644
--- a/spec/requests/api/award_emoji_spec.rb
+++ b/spec/requests/api/award_emoji_spec.rb
@@ -15,7 +15,7 @@ describe API::AwardEmoji, api: true do
describe "GET /projects/:id/awardable/:awardable_id/award_emoji" do
context 'on an issue' do
it "returns an array of award_emoji" do
- get api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user)
+ get api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -31,7 +31,7 @@ describe API::AwardEmoji, api: true do
context 'on a merge request' do
it "returns an array of award_emoji" do
- get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji", user)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/award_emoji", user)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
@@ -57,7 +57,7 @@ describe API::AwardEmoji, api: true do
it 'returns a status code 404' do
user1 = create(:user)
- get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji", user1)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/award_emoji", user1)
expect(response).to have_http_status(404)
end
@@ -68,7 +68,7 @@ describe API::AwardEmoji, api: true do
let!(:rocket) { create(:award_emoji, awardable: note, name: 'rocket') }
it 'returns an array of award emoji' do
- get api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user)
+ get api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -79,7 +79,7 @@ describe API::AwardEmoji, api: true do
describe "GET /projects/:id/awardable/:awardable_id/award_emoji/:award_id" do
context 'on an issue' do
it "returns the award emoji" do
- get api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/#{award_emoji.id}", user)
+ get api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji/#{award_emoji.id}", user)
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(award_emoji.name)
@@ -88,7 +88,7 @@ describe API::AwardEmoji, api: true do
end
it "returns a 404 error if the award is not found" do
- get api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/12345", user)
+ get api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji/12345", user)
expect(response).to have_http_status(404)
end
@@ -96,7 +96,7 @@ describe API::AwardEmoji, api: true do
context 'on a merge request' do
it 'returns the award emoji' do
- get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji/#{downvote.id}", user)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/award_emoji/#{downvote.id}", user)
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(downvote.name)
@@ -123,7 +123,7 @@ describe API::AwardEmoji, api: true do
it 'returns a status code 404' do
user1 = create(:user)
- get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji/#{downvote.id}", user1)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/award_emoji/#{downvote.id}", user1)
expect(response).to have_http_status(404)
end
@@ -134,7 +134,7 @@ describe API::AwardEmoji, api: true do
let!(:rocket) { create(:award_emoji, awardable: note, name: 'rocket') }
it 'returns an award emoji' do
- get api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji/#{rocket.id}", user)
+ get api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji/#{rocket.id}", user)
expect(response).to have_http_status(200)
expect(json_response).not_to be_an Array
@@ -147,7 +147,7 @@ describe API::AwardEmoji, api: true do
context "on an issue" do
it "creates a new award emoji" do
- post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'blowfish'
+ post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), name: 'blowfish'
expect(response).to have_http_status(201)
expect(json_response['name']).to eq('blowfish')
@@ -155,13 +155,13 @@ describe API::AwardEmoji, api: true do
end
it "returns a 400 bad request error if the name is not given" do
- post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user)
+ post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user)
expect(response).to have_http_status(400)
end
it "returns a 401 unauthorized error if the user is not authenticated" do
- post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji"), name: 'thumbsup'
+ post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji"), name: 'thumbsup'
expect(response).to have_http_status(401)
end
@@ -173,15 +173,15 @@ describe API::AwardEmoji, api: true do
end
it "normalizes +1 as thumbsup award" do
- post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: '+1'
+ post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), name: '+1'
expect(issue.award_emoji.last.name).to eq("thumbsup")
end
context 'when the emoji already has been awarded' do
it 'returns a 404 status code' do
- post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'thumbsup'
- post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'thumbsup'
+ post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), name: 'thumbsup'
+ post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), name: 'thumbsup'
expect(response).to have_http_status(404)
expect(json_response["message"]).to match("has already been taken")
@@ -207,7 +207,7 @@ describe API::AwardEmoji, api: true do
it 'creates a new award emoji' do
expect do
- post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket'
+ post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), name: 'rocket'
end.to change { note.award_emoji.count }.from(0).to(1)
expect(response).to have_http_status(201)
@@ -215,21 +215,21 @@ describe API::AwardEmoji, api: true do
end
it "it returns 404 error when user authored note" do
- post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note2.id}/award_emoji", user), name: 'thumbsup'
+ post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note2.id}/award_emoji", user), name: 'thumbsup'
expect(response).to have_http_status(404)
end
it "normalizes +1 as thumbsup award" do
- post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: '+1'
+ post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), name: '+1'
expect(note.award_emoji.last.name).to eq("thumbsup")
end
context 'when the emoji already has been awarded' do
it 'returns a 404 status code' do
- post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket'
- post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket'
+ post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), name: 'rocket'
+ post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), name: 'rocket'
expect(response).to have_http_status(404)
expect(json_response["message"]).to match("has already been taken")
@@ -241,14 +241,14 @@ describe API::AwardEmoji, api: true do
context 'when the awardable is an Issue' do
it 'deletes the award' do
expect do
- delete api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/#{award_emoji.id}", user)
+ delete api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji/#{award_emoji.id}", user)
expect(response).to have_http_status(204)
end.to change { issue.award_emoji.count }.from(1).to(0)
end
it 'returns a 404 error when the award emoji can not be found' do
- delete api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/12345", user)
+ delete api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji/12345", user)
expect(response).to have_http_status(404)
end
@@ -257,14 +257,14 @@ describe API::AwardEmoji, api: true do
context 'when the awardable is a Merge Request' do
it 'deletes the award' do
expect do
- delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji/#{downvote.id}", user)
+ delete api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/award_emoji/#{downvote.id}", user)
expect(response).to have_http_status(204)
end.to change { merge_request.award_emoji.count }.from(1).to(0)
end
it 'returns a 404 error when note id not found' do
- delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/notes/12345", user)
+ delete api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes/12345", user)
expect(response).to have_http_status(404)
end
@@ -289,7 +289,7 @@ describe API::AwardEmoji, api: true do
it 'deletes the award' do
expect do
- delete api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji/#{rocket.id}", user)
+ delete api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji/#{rocket.id}", user)
expect(response).to have_http_status(204)
end.to change { note.award_emoji.count }.from(1).to(0)
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 5190fcca2d1..585449e62b6 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -19,6 +19,7 @@ describe API::Commits, api: true do
it "returns project commits" do
commit = project.repository.commit
+
get api("/projects/#{project.id}/repository/commits", user)
expect(response).to have_http_status(200)
@@ -27,6 +28,16 @@ describe API::Commits, api: true do
expect(json_response.first['committer_name']).to eq(commit.committer_name)
expect(json_response.first['committer_email']).to eq(commit.committer_email)
end
+
+ it 'include correct pagination headers' do
+ commit_count = project.repository.count_commits(ref: 'master').to_s
+
+ get api("/projects/#{project.id}/repository/commits", user)
+
+ expect(response).to include_pagination_headers
+ expect(response.headers['X-Total']).to eq(commit_count)
+ expect(response.headers['X-Page']).to eql('1')
+ end
end
context "unauthorized user" do
@@ -39,14 +50,26 @@ describe API::Commits, api: true do
context "since optional parameter" do
it "returns project commits since provided parameter" do
commits = project.repository.commits("master")
- since = commits.second.created_at
+ after = commits.second.created_at
- get api("/projects/#{project.id}/repository/commits?since=#{since.utc.iso8601}", user)
+ get api("/projects/#{project.id}/repository/commits?since=#{after.utc.iso8601}", user)
expect(json_response.size).to eq 2
expect(json_response.first["id"]).to eq(commits.first.id)
expect(json_response.second["id"]).to eq(commits.second.id)
end
+
+ it 'include correct pagination headers' do
+ commits = project.repository.commits("master")
+ after = commits.second.created_at
+ commit_count = project.repository.count_commits(ref: 'master', after: after).to_s
+
+ get api("/projects/#{project.id}/repository/commits?since=#{after.utc.iso8601}", user)
+
+ expect(response).to include_pagination_headers
+ expect(response.headers['X-Total']).to eq(commit_count)
+ expect(response.headers['X-Page']).to eql('1')
+ end
end
context "until optional parameter" do
@@ -65,6 +88,18 @@ describe API::Commits, api: true do
expect(json_response.first["id"]).to eq(commits.second.id)
expect(json_response.second["id"]).to eq(commits.third.id)
end
+
+ it 'include correct pagination headers' do
+ commits = project.repository.commits("master")
+ before = commits.second.created_at
+ commit_count = project.repository.count_commits(ref: 'master', before: before).to_s
+
+ get api("/projects/#{project.id}/repository/commits?until=#{before.utc.iso8601}", user)
+
+ expect(response).to include_pagination_headers
+ expect(response.headers['X-Total']).to eq(commit_count)
+ expect(response.headers['X-Page']).to eql('1')
+ end
end
context "invalid xmlschema date parameters" do
@@ -79,11 +114,66 @@ describe API::Commits, api: true do
context "path optional parameter" do
it "returns project commits matching provided path parameter" do
path = 'files/ruby/popen.rb'
+ commit_count = project.repository.count_commits(ref: 'master', path: path).to_s
get api("/projects/#{project.id}/repository/commits?path=#{path}", user)
expect(json_response.size).to eq(3)
expect(json_response.first["id"]).to eq("570e7b2abdd848b95f2f578043fc23bd6f6fd24d")
+ expect(response).to include_pagination_headers
+ expect(response.headers['X-Total']).to eq(commit_count)
+ end
+
+ it 'include correct pagination headers' do
+ path = 'files/ruby/popen.rb'
+ commit_count = project.repository.count_commits(ref: 'master', path: path).to_s
+
+ get api("/projects/#{project.id}/repository/commits?path=#{path}", user)
+
+ expect(response).to include_pagination_headers
+ expect(response.headers['X-Total']).to eq(commit_count)
+ expect(response.headers['X-Page']).to eql('1')
+ end
+ end
+
+ context 'with pagination params' do
+ let(:page) { 1 }
+ let(:per_page) { 5 }
+ let(:ref_name) { 'master' }
+ let!(:request) do
+ get api("/projects/#{project.id}/repository/commits?page=#{page}&per_page=#{per_page}&ref_name=#{ref_name}", user)
+ end
+
+ it 'returns correct headers' do
+ commit_count = project.repository.count_commits(ref: ref_name).to_s
+
+ expect(response).to include_pagination_headers
+ expect(response.headers['X-Total']).to eq(commit_count)
+ expect(response.headers['X-Page']).to eq('1')
+ expect(response.headers['Link']).to match(/page=1&per_page=5/)
+ expect(response.headers['Link']).to match(/page=2&per_page=5/)
+ end
+
+ context 'viewing the first page' do
+ it 'returns the first 5 commits' do
+ commit = project.repository.commit
+
+ expect(json_response.size).to eq(per_page)
+ expect(json_response.first['id']).to eq(commit.id)
+ expect(response.headers['X-Page']).to eq('1')
+ end
+ end
+
+ context 'viewing the third page' do
+ let(:page) { 3 }
+
+ it 'returns the third 5 commits' do
+ commit = project.repository.commits('HEAD', offset: (page - 1) * per_page).first
+
+ expect(json_response.size).to eq(per_page)
+ expect(json_response.first['id']).to eq(commit.id)
+ expect(response.headers['X-Page']).to eq('3')
+ end
end
end
end
diff --git a/spec/requests/api/doorkeeper_access_spec.rb b/spec/requests/api/doorkeeper_access_spec.rb
index 2974875510a..f6fd567eca5 100644
--- a/spec/requests/api/doorkeeper_access_spec.rb
+++ b/spec/requests/api/doorkeeper_access_spec.rb
@@ -39,4 +39,22 @@ describe API::API, api: true do
end
end
end
+
+ describe "when user is blocked" do
+ it "returns authentication error" do
+ user.block
+ get api("/user"), access_token: token.token
+
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ describe "when user is ldap_blocked" do
+ it "returns authentication error" do
+ user.ldap_block
+ get api("/user"), access_token: token.token
+
+ expect(response).to have_http_status(401)
+ end
+ end
end
diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb
index f2fd1dfc8db..b54ee8e8b85 100644
--- a/spec/requests/api/environments_spec.rb
+++ b/spec/requests/api/environments_spec.rb
@@ -15,6 +15,8 @@ describe API::Environments, api: true do
describe 'GET /projects/:id/environments' do
context 'as member of the project' do
it 'returns project environments' do
+ project_data_keys = %w(id http_url_to_repo web_url name name_with_namespace path path_with_namespace)
+
get api("/projects/#{project.id}/environments", user)
expect(response).to have_http_status(200)
@@ -23,8 +25,7 @@ describe API::Environments, api: true do
expect(json_response.size).to eq(1)
expect(json_response.first['name']).to eq(environment.name)
expect(json_response.first['external_url']).to eq(environment.external_url)
- expect(json_response.first['project']['id']).to eq(project.id)
- expect(json_response.first['project']['visibility']).to be_present
+ expect(json_response.first['project'].keys).to contain_exactly(*project_data_keys)
end
end
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index 91f8a35e045..a7fad7f0bdb 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -5,10 +5,9 @@ describe API::Files, api: true do
let(:user) { create(:user) }
let!(:project) { create(:project, :repository, namespace: user.namespace ) }
let(:guest) { create(:user) { |u| project.add_guest(u) } }
- let(:file_path) { 'files/ruby/popen.rb' }
+ let(:file_path) { "files%2Fruby%2Fpopen%2Erb" }
let(:params) do
{
- file_path: file_path,
ref: 'master'
}
end
@@ -30,36 +29,54 @@ describe API::Files, api: true do
before { project.team << [user, :developer] }
- describe "GET /projects/:id/repository/files" do
- let(:route) { "/projects/#{project.id}/repository/files" }
+ def route(file_path = nil)
+ "/projects/#{project.id}/repository/files/#{file_path}"
+ end
+ describe "GET /projects/:id/repository/files/:file_path" do
shared_examples_for 'repository files' do
- it "returns file info" do
- get api(route, current_user), params
+ it 'returns file attributes as json' do
+ get api(route(file_path), current_user), params
expect(response).to have_http_status(200)
- expect(json_response['file_path']).to eq(file_path)
+ expect(json_response['file_path']).to eq(CGI.unescape(file_path))
expect(json_response['file_name']).to eq('popen.rb')
expect(json_response['last_commit_id']).to eq('570e7b2abdd848b95f2f578043fc23bd6f6fd24d')
expect(Base64.decode64(json_response['content']).lines.first).to eq("require 'fileutils'\n")
end
- context 'when no params are given' do
+ it 'returns file by commit sha' do
+ # This file is deleted on HEAD
+ file_path = "files%2Fjs%2Fcommit%2Ejs%2Ecoffee"
+ params[:ref] = "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9"
+
+ get api(route(file_path), current_user), params
+
+ expect(response).to have_http_status(200)
+ expect(json_response['file_name']).to eq('commit.js.coffee')
+ expect(Base64.decode64(json_response['content']).lines.first).to eq("class Commit\n")
+ end
+
+ it 'returns raw file info' do
+ url = route(file_path) + "/raw"
+ expect(Gitlab::Workhorse).to receive(:send_git_blob)
+
+ get api(url, current_user), params
+
+ expect(response).to have_http_status(200)
+ end
+
+ context 'when mandatory params are not given' do
it_behaves_like '400 response' do
- let(:request) { get api(route, current_user) }
+ let(:request) { get api(route("any%2Ffile"), current_user) }
end
end
context 'when file_path does not exist' do
- let(:params) do
- {
- file_path: 'app/models/application.rb',
- ref: 'master',
- }
- end
+ let(:params) { { ref: 'master' } }
it_behaves_like '404 response' do
- let(:request) { get api(route, current_user), params }
+ let(:request) { get api(route('app%2Fmodels%2Fapplication%2Erb'), current_user), params }
let(:message) { '404 File Not Found' }
end
end
@@ -68,7 +85,7 @@ describe API::Files, api: true do
include_context 'disabled repository'
it_behaves_like '403 response' do
- let(:request) { get api(route, current_user), params }
+ let(:request) { get api(route(file_path), current_user), params }
end
end
end
@@ -82,7 +99,7 @@ describe API::Files, api: true do
context 'when unauthenticated', 'and project is private' do
it_behaves_like '404 response' do
- let(:request) { get api(route), params }
+ let(:request) { get api(route(file_path)), params }
let(:message) { '404 Project Not Found' }
end
end
@@ -95,33 +112,106 @@ describe API::Files, api: true do
context 'when authenticated', 'as a guest' do
it_behaves_like '403 response' do
- let(:request) { get api(route, guest), params }
+ let(:request) { get api(route(file_path), guest), params }
end
end
end
- describe "POST /projects/:id/repository/files" do
+ describe "GET /projects/:id/repository/files/:file_path/raw" do
+ shared_examples_for 'repository raw files' do
+ it 'returns raw file info' do
+ url = route(file_path) + "/raw"
+ expect(Gitlab::Workhorse).to receive(:send_git_blob)
+
+ get api(url, current_user), params
+
+ expect(response).to have_http_status(200)
+ end
+
+ it 'returns file by commit sha' do
+ # This file is deleted on HEAD
+ file_path = "files%2Fjs%2Fcommit%2Ejs%2Ecoffee"
+ params[:ref] = "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9"
+ expect(Gitlab::Workhorse).to receive(:send_git_blob)
+
+ get api(route(file_path) + "/raw", current_user), params
+
+ expect(response).to have_http_status(200)
+ end
+
+ context 'when mandatory params are not given' do
+ it_behaves_like '400 response' do
+ let(:request) { get api(route("any%2Ffile"), current_user) }
+ end
+ end
+
+ context 'when file_path does not exist' do
+ let(:params) { { ref: 'master' } }
+
+ it_behaves_like '404 response' do
+ let(:request) { get api(route('app%2Fmodels%2Fapplication%2Erb'), current_user), params }
+ let(:message) { '404 File Not Found' }
+ end
+ end
+
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
+
+ it_behaves_like '403 response' do
+ let(:request) { get api(route(file_path), current_user), params }
+ end
+ end
+ end
+
+ context 'when unauthenticated', 'and project is public' do
+ it_behaves_like 'repository raw files' do
+ let(:project) { create(:project, :public) }
+ let(:current_user) { nil }
+ end
+ end
+
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(route(file_path)), params }
+ let(:message) { '404 Project Not Found' }
+ end
+ end
+
+ context 'when authenticated', 'as a developer' do
+ it_behaves_like 'repository raw files' do
+ let(:current_user) { user }
+ end
+ end
+
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:request) { get api(route(file_path), guest), params }
+ end
+ end
+ end
+
+ describe "POST /projects/:id/repository/files/:file_path" do
+ let!(:file_path) { "new_subfolder%2Fnewfile%2Erb" }
let(:valid_params) do
{
- file_path: 'newfile.rb',
- branch: 'master',
- content: 'puts 8',
- commit_message: 'Added newfile'
+ branch: "master",
+ content: "puts 8",
+ commit_message: "Added newfile"
}
end
it "creates a new file in project repo" do
- post api("/projects/#{project.id}/repository/files", user), valid_params
+ post api(route(file_path), user), valid_params
expect(response).to have_http_status(201)
- expect(json_response['file_path']).to eq('newfile.rb')
+ expect(json_response["file_path"]).to eq(CGI.unescape(file_path))
last_commit = project.repository.commit.raw
expect(last_commit.author_email).to eq(user.email)
expect(last_commit.author_name).to eq(user.name)
end
- it "returns a 400 bad request if no params given" do
- post api("/projects/#{project.id}/repository/files", user)
+ it "returns a 400 bad request if no mandatory params given" do
+ post api(route("any%2Etxt"), user)
expect(response).to have_http_status(400)
end
@@ -130,7 +220,7 @@ describe API::Files, api: true do
allow_any_instance_of(Repository).to receive(:create_file).
and_return(false)
- post api("/projects/#{project.id}/repository/files", user), valid_params
+ post api(route("any%2Etxt"), user), valid_params
expect(response).to have_http_status(400)
end
@@ -139,7 +229,7 @@ describe API::Files, api: true do
it "creates a new file with the specified author" do
valid_params.merge!(author_email: author_email, author_name: author_name)
- post api("/projects/#{project.id}/repository/files", user), valid_params
+ post api(route("new_file_with_author%2Etxt"), user), valid_params
expect(response).to have_http_status(201)
last_commit = project.repository.commit.raw
@@ -152,7 +242,7 @@ describe API::Files, api: true do
let!(:project) { create(:project_empty_repo, namespace: user.namespace ) }
it "creates a new file in project repo" do
- post api("/projects/#{project.id}/repository/files", user), valid_params
+ post api(route("newfile%2Erb"), user), valid_params
expect(response).to have_http_status(201)
expect(json_response['file_path']).to eq('newfile.rb')
@@ -166,7 +256,6 @@ describe API::Files, api: true do
describe "PUT /projects/:id/repository/files" do
let(:valid_params) do
{
- file_path: file_path,
branch: 'master',
content: 'puts 8',
commit_message: 'Changed file'
@@ -174,17 +263,17 @@ describe API::Files, api: true do
end
it "updates existing file in project repo" do
- put api("/projects/#{project.id}/repository/files", user), valid_params
+ put api(route(file_path), user), valid_params
expect(response).to have_http_status(200)
- expect(json_response['file_path']).to eq(file_path)
+ expect(json_response['file_path']).to eq(CGI.unescape(file_path))
last_commit = project.repository.commit.raw
expect(last_commit.author_email).to eq(user.email)
expect(last_commit.author_name).to eq(user.name)
end
it "returns a 400 bad request if no params given" do
- put api("/projects/#{project.id}/repository/files", user)
+ put api(route(file_path), user)
expect(response).to have_http_status(400)
end
@@ -193,7 +282,7 @@ describe API::Files, api: true do
it "updates a file with the specified author" do
valid_params.merge!(author_email: author_email, author_name: author_name, content: "New content")
- put api("/projects/#{project.id}/repository/files", user), valid_params
+ put api(route(file_path), user), valid_params
expect(response).to have_http_status(200)
last_commit = project.repository.commit.raw
@@ -206,20 +295,19 @@ describe API::Files, api: true do
describe "DELETE /projects/:id/repository/files" do
let(:valid_params) do
{
- file_path: file_path,
branch: 'master',
commit_message: 'Changed file'
}
end
it "deletes existing file in project repo" do
- delete api("/projects/#{project.id}/repository/files", user), valid_params
+ delete api(route(file_path), user), valid_params
expect(response).to have_http_status(204)
end
it "returns a 400 bad request if no params given" do
- delete api("/projects/#{project.id}/repository/files", user)
+ delete api(route(file_path), user)
expect(response).to have_http_status(400)
end
@@ -227,7 +315,7 @@ describe API::Files, api: true do
it "returns a 400 if fails to create file" do
allow_any_instance_of(Repository).to receive(:delete_file).and_return(false)
- delete api("/projects/#{project.id}/repository/files", user), valid_params
+ delete api(route(file_path), user), valid_params
expect(response).to have_http_status(400)
end
@@ -236,7 +324,7 @@ describe API::Files, api: true do
it "removes a file with the specified author" do
valid_params.merge!(author_email: author_email, author_name: author_name)
- delete api("/projects/#{project.id}/repository/files", user), valid_params
+ delete api(route(file_path), user), valid_params
expect(response).to have_http_status(204)
end
@@ -244,10 +332,9 @@ describe API::Files, api: true do
end
describe "POST /projects/:id/repository/files with binary file" do
- let(:file_path) { 'test.bin' }
+ let(:file_path) { 'test%2Ebin' }
let(:put_params) do
{
- file_path: file_path,
branch: 'master',
content: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=',
commit_message: 'Binary file with a \n should not be touched',
@@ -256,21 +343,20 @@ describe API::Files, api: true do
end
let(:get_params) do
{
- file_path: file_path,
ref: 'master',
}
end
before do
- post api("/projects/#{project.id}/repository/files", user), put_params
+ post api(route(file_path), user), put_params
end
it "remains unchanged" do
- get api("/projects/#{project.id}/repository/files", user), get_params
+ get api(route(file_path), user), get_params
expect(response).to have_http_status(200)
- expect(json_response['file_path']).to eq(file_path)
- expect(json_response['file_name']).to eq(file_path)
+ expect(json_response['file_path']).to eq(CGI.unescape(file_path))
+ expect(json_response['file_name']).to eq(CGI.unescape(file_path))
expect(json_response['content']).to eq(put_params[:content])
end
end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index aca7c6a0734..2fc11a3b782 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -757,9 +757,9 @@ describe API::Issues, api: true do
end
end
- describe "GET /projects/:id/issues/:issue_id" do
+ describe "GET /projects/:id/issues/:issue_iid" do
it 'exposes known attributes' do
- get api("/projects/#{project.id}/issues/#{issue.id}", user)
+ get api("/projects/#{project.id}/issues/#{issue.iid}", user)
expect(response).to have_http_status(200)
expect(json_response['id']).to eq(issue.id)
@@ -777,8 +777,8 @@ describe API::Issues, api: true do
expect(json_response['confidential']).to be_falsy
end
- it "returns a project issue by id" do
- get api("/projects/#{project.id}/issues/#{issue.id}", user)
+ it "returns a project issue by internal id" do
+ get api("/projects/#{project.id}/issues/#{issue.iid}", user)
expect(response).to have_http_status(200)
expect(json_response['title']).to eq(issue.title)
@@ -790,40 +790,52 @@ describe API::Issues, api: true do
expect(response).to have_http_status(404)
end
+ it "returns 404 if the issue ID is used" do
+ get api("/projects/#{project.id}/issues/#{issue.id}", user)
+
+ expect(response).to have_http_status(404)
+ end
+
context 'confidential issues' do
it "returns 404 for non project members" do
- get api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member)
+ get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", non_member)
+
expect(response).to have_http_status(404)
end
it "returns 404 for project members with guest role" do
- get api("/projects/#{project.id}/issues/#{confidential_issue.id}", guest)
+ get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", guest)
+
expect(response).to have_http_status(404)
end
it "returns confidential issue for project members" do
- get api("/projects/#{project.id}/issues/#{confidential_issue.id}", user)
+ get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user)
+
expect(response).to have_http_status(200)
expect(json_response['title']).to eq(confidential_issue.title)
expect(json_response['iid']).to eq(confidential_issue.iid)
end
it "returns confidential issue for author" do
- get api("/projects/#{project.id}/issues/#{confidential_issue.id}", author)
+ get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", author)
+
expect(response).to have_http_status(200)
expect(json_response['title']).to eq(confidential_issue.title)
expect(json_response['iid']).to eq(confidential_issue.iid)
end
it "returns confidential issue for assignee" do
- get api("/projects/#{project.id}/issues/#{confidential_issue.id}", assignee)
+ get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", assignee)
+
expect(response).to have_http_status(200)
expect(json_response['title']).to eq(confidential_issue.title)
expect(json_response['iid']).to eq(confidential_issue.iid)
end
it "returns confidential issue for admin" do
- get api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin)
+ get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", admin)
+
expect(response).to have_http_status(200)
expect(json_response['title']).to eq(confidential_issue.title)
expect(json_response['iid']).to eq(confidential_issue.iid)
@@ -1004,23 +1016,29 @@ describe API::Issues, api: true do
end
end
- describe "PUT /projects/:id/issues/:issue_id to update only title" do
+ describe "PUT /projects/:id/issues/:issue_iid to update only title" do
it "updates a project issue" do
- put api("/projects/#{project.id}/issues/#{issue.id}", user),
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
title: 'updated title'
expect(response).to have_http_status(200)
expect(json_response['title']).to eq('updated title')
end
- it "returns 404 error if issue id not found" do
+ it "returns 404 error if issue iid not found" do
put api("/projects/#{project.id}/issues/44444", user),
title: 'updated title'
expect(response).to have_http_status(404)
end
- it 'allows special label names' do
+ it "returns 404 error if issue id is used instead of the iid" do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
+ title: 'updated title'
+ expect(response).to have_http_status(404)
+ end
+
+ it 'allows special label names' do
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
title: 'updated title',
labels: 'label, label?, label&foo, ?, &'
@@ -1034,40 +1052,40 @@ describe API::Issues, api: true do
context 'confidential issues' do
it "returns 403 for non project members" do
- put api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member),
+ put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", non_member),
title: 'updated title'
expect(response).to have_http_status(403)
end
it "returns 403 for project members with guest role" do
- put api("/projects/#{project.id}/issues/#{confidential_issue.id}", guest),
+ put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", guest),
title: 'updated title'
expect(response).to have_http_status(403)
end
it "updates a confidential issue for project members" do
- put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user),
+ put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user),
title: 'updated title'
expect(response).to have_http_status(200)
expect(json_response['title']).to eq('updated title')
end
it "updates a confidential issue for author" do
- put api("/projects/#{project.id}/issues/#{confidential_issue.id}", author),
+ put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", author),
title: 'updated title'
expect(response).to have_http_status(200)
expect(json_response['title']).to eq('updated title')
end
it "updates a confidential issue for admin" do
- put api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin),
+ put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", admin),
title: 'updated title'
expect(response).to have_http_status(200)
expect(json_response['title']).to eq('updated title')
end
it 'sets an issue to confidential' do
- put api("/projects/#{project.id}/issues/#{issue.id}", user),
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
confidential: true
expect(response).to have_http_status(200)
@@ -1075,7 +1093,7 @@ describe API::Issues, api: true do
end
it 'makes a confidential issue public' do
- put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user),
+ put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user),
confidential: false
expect(response).to have_http_status(200)
@@ -1083,7 +1101,7 @@ describe API::Issues, api: true do
end
it 'does not update a confidential issue with wrong confidential flag' do
- put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user),
+ put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user),
confidential: 'foo'
expect(response).to have_http_status(400)
@@ -1092,7 +1110,7 @@ describe API::Issues, api: true do
end
end
- describe 'PUT /projects/:id/issues/:issue_id with spam filtering' do
+ describe 'PUT /projects/:id/issues/:issue_iid with spam filtering' do
let(:params) do
{
title: 'updated title',
@@ -1105,7 +1123,7 @@ describe API::Issues, api: true do
allow_any_instance_of(SpamService).to receive_messages(check_for_spam?: true)
allow_any_instance_of(AkismetService).to receive_messages(is_spam?: true)
- put api("/projects/#{project.id}/issues/#{issue.id}", user), params
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user), params
expect(response).to have_http_status(400)
expect(json_response['message']).to eq({ "error" => "Spam detected" })
@@ -1119,12 +1137,12 @@ describe API::Issues, api: true do
end
end
- describe 'PUT /projects/:id/issues/:issue_id to update labels' do
+ describe 'PUT /projects/:id/issues/:issue_iid to update labels' do
let!(:label) { create(:label, title: 'dummy', project: project) }
let!(:label_link) { create(:label_link, label: label, target: issue) }
it 'does not update labels if not present' do
- put api("/projects/#{project.id}/issues/#{issue.id}", user),
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
title: 'updated title'
expect(response).to have_http_status(200)
expect(json_response['labels']).to eq([label.title])
@@ -1135,7 +1153,7 @@ describe API::Issues, api: true do
label.toggle_subscription(user2, project)
perform_enqueued_jobs do
- put api("/projects/#{project.id}/issues/#{issue.id}", user),
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
title: 'updated title', labels: label.title
end
@@ -1143,14 +1161,14 @@ describe API::Issues, api: true do
end
it 'removes all labels' do
- put api("/projects/#{project.id}/issues/#{issue.id}", user), labels: ''
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user), labels: ''
expect(response).to have_http_status(200)
expect(json_response['labels']).to eq([])
end
it 'updates labels' do
- put api("/projects/#{project.id}/issues/#{issue.id}", user),
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
labels: 'foo,bar'
expect(response).to have_http_status(200)
expect(json_response['labels']).to include 'foo'
@@ -1158,7 +1176,7 @@ describe API::Issues, api: true do
end
it 'allows special label names' do
- put api("/projects/#{project.id}/issues/#{issue.id}", user),
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
labels: 'label:foo, label-bar,label_bar,label/bar,label?bar,label&bar,?,&'
expect(response.status).to eq(200)
expect(json_response['labels']).to include 'label:foo'
@@ -1172,7 +1190,7 @@ describe API::Issues, api: true do
end
it 'returns 400 if title is too long' do
- put api("/projects/#{project.id}/issues/#{issue.id}", user),
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
title: 'g' * 256
expect(response).to have_http_status(400)
expect(json_response['message']['title']).to eq([
@@ -1181,9 +1199,9 @@ describe API::Issues, api: true do
end
end
- describe "PUT /projects/:id/issues/:issue_id to update state and label" do
+ describe "PUT /projects/:id/issues/:issue_iid to update state and label" do
it "updates a project issue" do
- put api("/projects/#{project.id}/issues/#{issue.id}", user),
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
labels: 'label2', state_event: "close"
expect(response).to have_http_status(200)
@@ -1192,7 +1210,7 @@ describe API::Issues, api: true do
end
it 'reopens a project isssue' do
- put api("/projects/#{project.id}/issues/#{closed_issue.id}", user), state_event: 'reopen'
+ put api("/projects/#{project.id}/issues/#{closed_issue.iid}", user), state_event: 'reopen'
expect(response).to have_http_status(200)
expect(json_response['state']).to eq 'reopened'
@@ -1201,7 +1219,7 @@ describe API::Issues, api: true do
context 'when an admin or owner makes the request' do
it 'accepts the update date to be set' do
update_time = 2.weeks.ago
- put api("/projects/#{project.id}/issues/#{issue.id}", user),
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
labels: 'label3', state_event: 'close', updated_at: update_time
expect(response).to have_http_status(200)
@@ -1211,25 +1229,25 @@ describe API::Issues, api: true do
end
end
- describe 'PUT /projects/:id/issues/:issue_id to update due date' do
+ describe 'PUT /projects/:id/issues/:issue_iid to update due date' do
it 'creates a new project issue' do
due_date = 2.weeks.from_now.strftime('%Y-%m-%d')
- put api("/projects/#{project.id}/issues/#{issue.id}", user), due_date: due_date
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user), due_date: due_date
expect(response).to have_http_status(200)
expect(json_response['due_date']).to eq(due_date)
end
end
- describe "DELETE /projects/:id/issues/:issue_id" do
+ describe "DELETE /projects/:id/issues/:issue_iid" do
it "rejects a non member from deleting an issue" do
- delete api("/projects/#{project.id}/issues/#{issue.id}", non_member)
+ delete api("/projects/#{project.id}/issues/#{issue.iid}", non_member)
expect(response).to have_http_status(403)
end
it "rejects a developer from deleting an issue" do
- delete api("/projects/#{project.id}/issues/#{issue.id}", author)
+ delete api("/projects/#{project.id}/issues/#{issue.iid}", author)
expect(response).to have_http_status(403)
end
@@ -1238,7 +1256,7 @@ describe API::Issues, api: true do
let(:project) { create(:empty_project, namespace: owner.namespace) }
it "deletes the issue if an admin requests it" do
- delete api("/projects/#{project.id}/issues/#{issue.id}", owner)
+ delete api("/projects/#{project.id}/issues/#{issue.iid}", owner)
expect(response).to have_http_status(204)
end
@@ -1251,14 +1269,20 @@ describe API::Issues, api: true do
expect(response).to have_http_status(404)
end
end
+
+ it 'returns 404 when using the issue ID instead of IID' do
+ delete api("/projects/#{project.id}/issues/#{issue.id}", user)
+
+ expect(response).to have_http_status(404)
+ end
end
- describe '/projects/:id/issues/:issue_id/move' do
+ describe '/projects/:id/issues/:issue_iid/move' do
let!(:target_project) { create(:empty_project, path: 'project2', creator_id: user.id, namespace: user.namespace ) }
let!(:target_project2) { create(:empty_project, creator_id: non_member.id, namespace: non_member.namespace ) }
it 'moves an issue' do
- post api("/projects/#{project.id}/issues/#{issue.id}/move", user),
+ post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
to_project_id: target_project.id
expect(response).to have_http_status(201)
@@ -1267,7 +1291,7 @@ describe API::Issues, api: true do
context 'when source and target projects are the same' do
it 'returns 400 when trying to move an issue' do
- post api("/projects/#{project.id}/issues/#{issue.id}/move", user),
+ post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
to_project_id: project.id
expect(response).to have_http_status(400)
@@ -1277,7 +1301,7 @@ describe API::Issues, api: true do
context 'when the user does not have the permission to move issues' do
it 'returns 400 when trying to move an issue' do
- post api("/projects/#{project.id}/issues/#{issue.id}/move", user),
+ post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
to_project_id: target_project2.id
expect(response).to have_http_status(400)
@@ -1286,13 +1310,23 @@ describe API::Issues, api: true do
end
it 'moves the issue to another namespace if I am admin' do
- post api("/projects/#{project.id}/issues/#{issue.id}/move", admin),
+ post api("/projects/#{project.id}/issues/#{issue.iid}/move", admin),
to_project_id: target_project2.id
expect(response).to have_http_status(201)
expect(json_response['project_id']).to eq(target_project2.id)
end
+ context 'when using the issue ID instead of iid' do
+ it 'returns 404 when trying to move an issue' do
+ post api("/projects/#{project.id}/issues/#{issue.id}/move", user),
+ to_project_id: target_project.id
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 Issue Not Found')
+ end
+ end
+
context 'when issue does not exist' do
it 'returns 404 when trying to move an issue' do
post api("/projects/#{project.id}/issues/123/move", user),
@@ -1305,7 +1339,7 @@ describe API::Issues, api: true do
context 'when source project does not exist' do
it 'returns 404 when trying to move an issue' do
- post api("/projects/123/issues/#{issue.id}/move", user),
+ post api("/projects/123/issues/#{issue.iid}/move", user),
to_project_id: target_project.id
expect(response).to have_http_status(404)
@@ -1315,7 +1349,7 @@ describe API::Issues, api: true do
context 'when target project does not exist' do
it 'returns 404 when trying to move an issue' do
- post api("/projects/#{project.id}/issues/#{issue.id}/move", user),
+ post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
to_project_id: 123
expect(response).to have_http_status(404)
@@ -1323,16 +1357,16 @@ describe API::Issues, api: true do
end
end
- describe 'POST :id/issues/:issue_id/subscribe' do
+ describe 'POST :id/issues/:issue_iid/subscribe' do
it 'subscribes to an issue' do
- post api("/projects/#{project.id}/issues/#{issue.id}/subscribe", user2)
+ post api("/projects/#{project.id}/issues/#{issue.iid}/subscribe", user2)
expect(response).to have_http_status(201)
expect(json_response['subscribed']).to eq(true)
end
it 'returns 304 if already subscribed' do
- post api("/projects/#{project.id}/issues/#{issue.id}/subscribe", user)
+ post api("/projects/#{project.id}/issues/#{issue.iid}/subscribe", user)
expect(response).to have_http_status(304)
end
@@ -1343,8 +1377,14 @@ describe API::Issues, api: true do
expect(response).to have_http_status(404)
end
+ it 'returns 404 if the issue ID is used instead of the iid' do
+ post api("/projects/#{project.id}/issues/#{issue.id}/subscribe", user)
+
+ expect(response).to have_http_status(404)
+ end
+
it 'returns 404 if the issue is confidential' do
- post api("/projects/#{project.id}/issues/#{confidential_issue.id}/subscribe", non_member)
+ post api("/projects/#{project.id}/issues/#{confidential_issue.iid}/subscribe", non_member)
expect(response).to have_http_status(404)
end
@@ -1352,14 +1392,14 @@ describe API::Issues, api: true do
describe 'POST :id/issues/:issue_id/unsubscribe' do
it 'unsubscribes from an issue' do
- post api("/projects/#{project.id}/issues/#{issue.id}/unsubscribe", user)
+ post api("/projects/#{project.id}/issues/#{issue.iid}/unsubscribe", user)
expect(response).to have_http_status(201)
expect(json_response['subscribed']).to eq(false)
end
it 'returns 304 if not subscribed' do
- post api("/projects/#{project.id}/issues/#{issue.id}/unsubscribe", user2)
+ post api("/projects/#{project.id}/issues/#{issue.iid}/unsubscribe", user2)
expect(response).to have_http_status(304)
end
@@ -1370,8 +1410,14 @@ describe API::Issues, api: true do
expect(response).to have_http_status(404)
end
+ it 'returns 404 if using the issue ID instead of iid' do
+ post api("/projects/#{project.id}/issues/#{issue.id}/unsubscribe", user)
+
+ expect(response).to have_http_status(404)
+ end
+
it 'returns 404 if the issue is confidential' do
- post api("/projects/#{project.id}/issues/#{confidential_issue.id}/unsubscribe", non_member)
+ post api("/projects/#{project.id}/issues/#{confidential_issue.iid}/unsubscribe", non_member)
expect(response).to have_http_status(404)
end
diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb
index a4d27734cc2..9450701064b 100644
--- a/spec/requests/api/jobs_spec.rb
+++ b/spec/requests/api/jobs_spec.rb
@@ -51,7 +51,7 @@ describe API::Jobs, api: true do
end
context 'filter project with array of scope elements' do
- let(:query) { { 'scope[0]' => 'pending', 'scope[1]' => 'running' } }
+ let(:query) { { scope: %w(pending running) } }
it do
expect(response).to have_http_status(200)
@@ -60,7 +60,7 @@ describe API::Jobs, api: true do
end
context 'respond 400 when scope contains invalid state' do
- let(:query) { { 'scope[0]' => 'unknown', 'scope[1]' => 'running' } }
+ let(:query) { { scope: %w(unknown running) } }
it { expect(response).to have_http_status(400) }
end
@@ -75,6 +75,78 @@ describe API::Jobs, api: true do
end
end
+ describe 'GET /projects/:id/pipelines/:pipeline_id/jobs' do
+ let(:query) { Hash.new }
+
+ before do
+ get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query
+ end
+
+ context 'authorized user' do
+ it 'returns pipeline jobs' do
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ end
+
+ it 'returns correct values' do
+ expect(json_response).not_to be_empty
+ expect(json_response.first['commit']['id']).to eq project.commit.id
+ end
+
+ it 'returns pipeline data' do
+ json_build = json_response.first
+
+ expect(json_build['pipeline']).not_to be_empty
+ expect(json_build['pipeline']['id']).to eq build.pipeline.id
+ expect(json_build['pipeline']['ref']).to eq build.pipeline.ref
+ expect(json_build['pipeline']['sha']).to eq build.pipeline.sha
+ expect(json_build['pipeline']['status']).to eq build.pipeline.status
+ end
+
+ context 'filter jobs with one scope element' do
+ let(:query) { { 'scope' => 'pending' } }
+
+ it do
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ end
+ end
+
+ context 'filter jobs with array of scope elements' do
+ let(:query) { { scope: %w(pending running) } }
+
+ it do
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ end
+ end
+
+ context 'respond 400 when scope contains invalid state' do
+ let(:query) { { scope: %w(unknown running) } }
+
+ it { expect(response).to have_http_status(400) }
+ end
+
+ context 'jobs in different pipelines' do
+ let!(:pipeline2) { create(:ci_empty_pipeline, project: project) }
+ let!(:build2) { create(:ci_build, pipeline: pipeline2) }
+
+ it 'excludes jobs from other pipelines' do
+ json_response.each { |job| expect(job['pipeline']['id']).to eq(pipeline.id) }
+ end
+ end
+ end
+
+ context 'unauthorized user' do
+ let(:api_user) { nil }
+
+ it 'does not return jobs' do
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+
describe 'GET /projects/:id/jobs/:job_id' do
before do
get api("/projects/#{project.id}/jobs/#{build.id}", api_user)
diff --git a/spec/requests/api/merge_request_diffs_spec.rb b/spec/requests/api/merge_request_diffs_spec.rb
index 1d02e827183..79f3151ba52 100644
--- a/spec/requests/api/merge_request_diffs_spec.rb
+++ b/spec/requests/api/merge_request_diffs_spec.rb
@@ -13,9 +13,9 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do
project.team << [user, :master]
end
- describe 'GET /projects/:id/merge_requests/:merge_request_id/versions' do
+ describe 'GET /projects/:id/merge_requests/:merge_request_iid/versions' do
it 'returns 200 for a valid merge request' do
- get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions", user)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/versions", user)
merge_request_diff = merge_request.merge_request_diffs.first
expect(response.status).to eq 200
@@ -26,16 +26,22 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do
expect(json_response.first['head_commit_sha']).to eq(merge_request_diff.head_commit_sha)
end
- it 'returns a 404 when merge_request_id not found' do
+ it 'returns a 404 when merge_request id is used instead of the iid' do
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions", user)
+ expect(response).to have_http_status(404)
+ end
+
+ it 'returns a 404 when merge_request_iid not found' do
get api("/projects/#{project.id}/merge_requests/999/versions", user)
expect(response).to have_http_status(404)
end
end
- describe 'GET /projects/:id/merge_requests/:merge_request_id/versions/:version_id' do
+ describe 'GET /projects/:id/merge_requests/:merge_request_iid/versions/:version_id' do
+ let(:merge_request_diff) { merge_request.merge_request_diffs.first }
+
it 'returns a 200 for a valid merge request' do
- merge_request_diff = merge_request.merge_request_diffs.first
- get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/#{merge_request_diff.id}", user)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/versions/#{merge_request_diff.id}", user)
expect(response.status).to eq 200
expect(json_response['id']).to eq(merge_request_diff.id)
@@ -43,8 +49,18 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do
expect(json_response['diffs'].size).to eq(merge_request_diff.diffs.size)
end
- it 'returns a 404 when merge_request_id not found' do
- get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/999", user)
+ it 'returns a 404 when merge_request id is used instead of the iid' do
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/#{merge_request_diff.id}", user)
+ expect(response).to have_http_status(404)
+ end
+
+ it 'returns a 404 when merge_request version_id is not found' do
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/versions/999", user)
+ expect(response).to have_http_status(404)
+ end
+
+ it 'returns a 404 when merge_request_iid is not found' do
+ get api("/projects/#{project.id}/merge_requests/12345/versions/#{merge_request_diff.id}", user)
expect(response).to have_http_status(404)
end
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 1083abf2ad3..9aba1d75612 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -153,9 +153,9 @@ describe API::MergeRequests, api: true do
end
end
- describe "GET /projects/:id/merge_requests/:merge_request_id" do
+ describe "GET /projects/:id/merge_requests/:merge_request_iid" do
it 'exposes known attributes' do
- get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user)
expect(response).to have_http_status(200)
expect(json_response['id']).to eq(merge_request.id)
@@ -184,7 +184,7 @@ describe API::MergeRequests, api: true do
end
it "returns merge_request" do
- get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user)
expect(response).to have_http_status(200)
expect(json_response['title']).to eq(merge_request.title)
expect(json_response['iid']).to eq(merge_request.iid)
@@ -194,25 +194,31 @@ describe API::MergeRequests, api: true do
expect(json_response['force_close_merge_request']).to be_falsy
end
- it "returns a 404 error if merge_request_id not found" do
+ it "returns a 404 error if merge_request_iid not found" do
get api("/projects/#{project.id}/merge_requests/999", user)
expect(response).to have_http_status(404)
end
+ it "returns a 404 error if merge_request `id` is used instead of iid" do
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user)
+
+ expect(response).to have_http_status(404)
+ end
+
context 'Work in Progress' do
let!(:merge_request_wip) { create(:merge_request, author: user, assignee: user, source_project: project, target_project: project, title: "WIP: Test", created_at: base_time + 1.second) }
it "returns merge_request" do
- get api("/projects/#{project.id}/merge_requests/#{merge_request_wip.id}", user)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request_wip.iid}", user)
expect(response).to have_http_status(200)
expect(json_response['work_in_progress']).to eq(true)
end
end
end
- describe 'GET /projects/:id/merge_requests/:merge_request_id/commits' do
+ describe 'GET /projects/:id/merge_requests/:merge_request_iid/commits' do
it 'returns a 200 when merge request is valid' do
- get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/commits", user)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/commits", user)
commit = merge_request.commits.first
expect(response.status).to eq 200
@@ -223,24 +229,36 @@ describe API::MergeRequests, api: true do
expect(json_response.first['title']).to eq(commit.title)
end
- it 'returns a 404 when merge_request_id not found' do
+ it 'returns a 404 when merge_request_iid not found' do
get api("/projects/#{project.id}/merge_requests/999/commits", user)
expect(response).to have_http_status(404)
end
+
+ it 'returns a 404 when merge_request id is used instead of iid' do
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/commits", user)
+
+ expect(response).to have_http_status(404)
+ end
end
- describe 'GET /projects/:id/merge_requests/:merge_request_id/changes' do
+ describe 'GET /projects/:id/merge_requests/:merge_request_iid/changes' do
it 'returns the change information of the merge_request' do
- get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/changes", user)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/changes", user)
expect(response.status).to eq 200
expect(json_response['changes'].size).to eq(merge_request.diffs.size)
end
- it 'returns a 404 when merge_request_id not found' do
+ it 'returns a 404 when merge_request_iid not found' do
get api("/projects/#{project.id}/merge_requests/999/changes", user)
expect(response).to have_http_status(404)
end
+
+ it 'returns a 404 when merge_request id is used instead of iid' do
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/changes", user)
+
+ expect(response).to have_http_status(404)
+ end
end
describe "POST /projects/:id/merge_requests" do
@@ -400,7 +418,7 @@ describe API::MergeRequests, api: true do
end
end
- describe "DELETE /projects/:id/merge_requests/:merge_request_id" do
+ describe "DELETE /projects/:id/merge_requests/:merge_request_iid" do
context "when the user is developer" do
let(:developer) { create(:user) }
@@ -409,25 +427,37 @@ describe API::MergeRequests, api: true do
end
it "denies the deletion of the merge request" do
- delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}", developer)
+ delete api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", developer)
expect(response).to have_http_status(403)
end
end
context "when the user is project owner" do
it "destroys the merge request owners can destroy" do
- delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user)
+ delete api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user)
expect(response).to have_http_status(204)
end
+
+ it "returns 404 for an invalid merge request IID" do
+ delete api("/projects/#{project.id}/merge_requests/12345", user)
+
+ expect(response).to have_http_status(404)
+ end
+
+ it "returns 404 if the merge request id is used instead of iid" do
+ delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user)
+
+ expect(response).to have_http_status(404)
+ end
end
end
- describe "PUT /projects/:id/merge_requests/:merge_request_id/merge" do
+ describe "PUT /projects/:id/merge_requests/:merge_request_iid/merge" do
let(:pipeline) { create(:ci_pipeline_without_jobs) }
it "returns merge_request in case of success" do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
expect(response).to have_http_status(200)
end
@@ -436,7 +466,7 @@ describe API::MergeRequests, api: true do
allow_any_instance_of(MergeRequest).
to receive(:can_be_merged?).and_return(false)
- put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
expect(response).to have_http_status(406)
expect(json_response['message']).to eq('Branch cannot be merged')
@@ -444,14 +474,14 @@ describe API::MergeRequests, api: true do
it "returns 405 if merge_request is not open" do
merge_request.close
- put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
expect(response).to have_http_status(405)
expect(json_response['message']).to eq('405 Method Not Allowed')
end
it "returns 405 if merge_request is a work in progress" do
merge_request.update_attribute(:title, "WIP: #{merge_request.title}")
- put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
expect(response).to have_http_status(405)
expect(json_response['message']).to eq('405 Method Not Allowed')
end
@@ -459,7 +489,7 @@ describe API::MergeRequests, api: true do
it 'returns 405 if the build failed for a merge request that requires success' do
allow_any_instance_of(MergeRequest).to receive(:mergeable_ci_state?).and_return(false)
- put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
expect(response).to have_http_status(405)
expect(json_response['message']).to eq('405 Method Not Allowed')
@@ -468,20 +498,20 @@ describe API::MergeRequests, api: true do
it "returns 401 if user has no permissions to merge" do
user2 = create(:user)
project.team << [user2, :reporter]
- put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user2)
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user2)
expect(response).to have_http_status(401)
expect(json_response['message']).to eq('401 Unauthorized')
end
it "returns 409 if the SHA parameter doesn't match" do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), sha: merge_request.diff_head_sha.reverse
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), sha: merge_request.diff_head_sha.reverse
expect(response).to have_http_status(409)
expect(json_response['message']).to start_with('SHA does not match HEAD of source branch')
end
it "succeeds if the SHA parameter matches" do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), sha: merge_request.diff_head_sha
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), sha: merge_request.diff_head_sha
expect(response).to have_http_status(200)
end
@@ -490,18 +520,30 @@ describe API::MergeRequests, api: true do
allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(pipeline)
allow(pipeline).to receive(:active?).and_return(true)
- put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), merge_when_pipeline_succeeds: true
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), merge_when_pipeline_succeeds: true
expect(response).to have_http_status(200)
expect(json_response['title']).to eq('Test')
expect(json_response['merge_when_pipeline_succeeds']).to eq(true)
end
+
+ it "returns 404 for an invalid merge request IID" do
+ put api("/projects/#{project.id}/merge_requests/12345/merge", user)
+
+ expect(response).to have_http_status(404)
+ end
+
+ it "returns 404 if the merge request id is used instead of iid" do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
+
+ expect(response).to have_http_status(404)
+ end
end
- describe "PUT /projects/:id/merge_requests/:merge_request_id" do
+ describe "PUT /projects/:id/merge_requests/:merge_request_iid" do
context "to close a MR" do
it "returns merge_request" do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close"
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), state_event: "close"
expect(response).to have_http_status(200)
expect(json_response['state']).to eq('closed')
@@ -509,38 +551,38 @@ describe API::MergeRequests, api: true do
end
it "updates title and returns merge_request" do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), title: "New title"
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), title: "New title"
expect(response).to have_http_status(200)
expect(json_response['title']).to eq('New title')
end
it "updates description and returns merge_request" do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), description: "New description"
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), description: "New description"
expect(response).to have_http_status(200)
expect(json_response['description']).to eq('New description')
end
it "updates milestone_id and returns merge_request" do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), milestone_id: milestone.id
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), milestone_id: milestone.id
expect(response).to have_http_status(200)
expect(json_response['milestone']['id']).to eq(milestone.id)
end
it "returns merge_request with renamed target_branch" do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), target_branch: "wiki"
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), target_branch: "wiki"
expect(response).to have_http_status(200)
expect(json_response['target_branch']).to eq('wiki')
end
it "returns merge_request that removes the source branch" do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), remove_source_branch: true
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), remove_source_branch: true
expect(response).to have_http_status(200)
expect(json_response['force_remove_source_branch']).to be_truthy
end
it 'allows special label names' do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user),
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user),
title: 'new issue',
labels: 'label, label?, label&foo, ?, &'
@@ -553,7 +595,7 @@ describe API::MergeRequests, api: true do
end
it 'does not update state when title is empty' do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: 'close', title: nil
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), state_event: 'close', title: nil
merge_request.reload
expect(response).to have_http_status(400)
@@ -561,19 +603,31 @@ describe API::MergeRequests, api: true do
end
it 'does not update state when target_branch is empty' do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: 'close', target_branch: nil
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), state_event: 'close', target_branch: nil
merge_request.reload
expect(response).to have_http_status(400)
expect(merge_request.state).to eq('opened')
end
+
+ it "returns 404 for an invalid merge request IID" do
+ put api("/projects/#{project.id}/merge_requests/12345", user), state_event: "close"
+
+ expect(response).to have_http_status(404)
+ end
+
+ it "returns 404 if the merge request id is used instead of iid" do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close"
+
+ expect(response).to have_http_status(404)
+ end
end
- describe "POST /projects/:id/merge_requests/:merge_request_id/comments" do
+ describe "POST /projects/:id/merge_requests/:merge_request_iid/comments" do
it "returns comment" do
original_count = merge_request.notes.size
- post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user), note: "My comment"
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/comments", user), note: "My comment"
expect(response).to have_http_status(201)
expect(json_response['note']).to eq('My comment')
@@ -583,23 +637,29 @@ describe API::MergeRequests, api: true do
end
it "returns 400 if note is missing" do
- post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user)
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/comments", user)
expect(response).to have_http_status(400)
end
- it "returns 404 if note is attached to non existent merge request" do
+ it "returns 404 if merge request iid is invalid" do
post api("/projects/#{project.id}/merge_requests/404/comments", user),
note: 'My comment'
expect(response).to have_http_status(404)
end
+
+ it "returns 404 if merge request id is used instead of iid" do
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user),
+ note: 'My comment'
+ expect(response).to have_http_status(404)
+ end
end
- describe "GET :id/merge_requests/:merge_request_id/comments" do
+ describe "GET :id/merge_requests/:merge_request_iid/comments" do
let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") }
it "returns merge_request comments ordered by created_at" do
- get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/comments", user)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
@@ -610,20 +670,25 @@ describe API::MergeRequests, api: true do
expect(json_response.last['note']).to eq("another comment on a MR")
end
- it "returns a 404 error if merge_request_id not found" do
+ it "returns a 404 error if merge_request_iid is invalid" do
get api("/projects/#{project.id}/merge_requests/999/comments", user)
expect(response).to have_http_status(404)
end
+
+ it "returns a 404 error if merge_request id is used instead of iid" do
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user)
+ expect(response).to have_http_status(404)
+ end
end
- describe 'GET :id/merge_requests/:merge_request_id/closes_issues' do
+ describe 'GET :id/merge_requests/:merge_request_iid/closes_issues' do
it 'returns the issue that will be closed on merge' do
issue = create(:issue, project: project)
mr = merge_request.tap do |mr|
mr.update_attribute(:description, "Closes #{issue.to_reference(mr.project)}")
end
- get api("/projects/#{project.id}/merge_requests/#{mr.id}/closes_issues", user)
+ get api("/projects/#{project.id}/merge_requests/#{mr.iid}/closes_issues", user)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
@@ -633,7 +698,7 @@ describe API::MergeRequests, api: true do
end
it 'returns an empty array when there are no issues to be closed' do
- get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", user)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/closes_issues", user)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
@@ -647,7 +712,7 @@ describe API::MergeRequests, api: true do
merge_request = create(:merge_request, :simple, author: user, assignee: user, source_project: jira_project)
merge_request.update_attribute(:description, "Closes #{issue.to_reference(jira_project)}")
- get api("/projects/#{jira_project.id}/merge_requests/#{merge_request.id}/closes_issues", user)
+ get api("/projects/#{jira_project.id}/merge_requests/#{merge_request.iid}/closes_issues", user)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
@@ -663,22 +728,34 @@ describe API::MergeRequests, api: true do
guest = create(:user)
project.team << [guest, :guest]
- get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", guest)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/closes_issues", guest)
expect(response).to have_http_status(403)
end
+
+ it "returns 404 for an invalid merge request IID" do
+ get api("/projects/#{project.id}/merge_requests/12345/closes_issues", user)
+
+ expect(response).to have_http_status(404)
+ end
+
+ it "returns 404 if the merge request id is used instead of iid" do
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", user)
+
+ expect(response).to have_http_status(404)
+ end
end
- describe 'POST :id/merge_requests/:merge_request_id/subscribe' do
+ describe 'POST :id/merge_requests/:merge_request_iid/subscribe' do
it 'subscribes to a merge request' do
- post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscribe", admin)
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/subscribe", admin)
expect(response).to have_http_status(201)
expect(json_response['subscribed']).to eq(true)
end
it 'returns 304 if already subscribed' do
- post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscribe", user)
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/subscribe", user)
expect(response).to have_http_status(304)
end
@@ -689,26 +766,32 @@ describe API::MergeRequests, api: true do
expect(response).to have_http_status(404)
end
+ it 'returns 404 if the merge request id is used instead of iid' do
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscribe", user)
+
+ expect(response).to have_http_status(404)
+ end
+
it 'returns 403 if user has no access to read code' do
guest = create(:user)
project.team << [guest, :guest]
- post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscribe", guest)
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/subscribe", guest)
expect(response).to have_http_status(403)
end
end
- describe 'POST :id/merge_requests/:merge_request_id/unsubscribe' do
+ describe 'POST :id/merge_requests/:merge_request_iid/unsubscribe' do
it 'unsubscribes from a merge request' do
- post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/unsubscribe", user)
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/unsubscribe", user)
expect(response).to have_http_status(201)
expect(json_response['subscribed']).to eq(false)
end
it 'returns 304 if not subscribed' do
- post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/unsubscribe", admin)
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/unsubscribe", admin)
expect(response).to have_http_status(304)
end
@@ -719,11 +802,17 @@ describe API::MergeRequests, api: true do
expect(response).to have_http_status(404)
end
+ it 'returns 404 if the merge request id is used instead of iid' do
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/unsubscribe", user)
+
+ expect(response).to have_http_status(404)
+ end
+
it 'returns 403 if user has no access to read code' do
guest = create(:user)
project.team << [guest, :guest]
- post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/unsubscribe", guest)
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/unsubscribe", guest)
expect(response).to have_http_status(403)
end
diff --git a/spec/requests/api/oauth_tokens_spec.rb b/spec/requests/api/oauth_tokens_spec.rb
index 7e2cc50e591..367225df717 100644
--- a/spec/requests/api/oauth_tokens_spec.rb
+++ b/spec/requests/api/oauth_tokens_spec.rb
@@ -29,5 +29,27 @@ describe API::API, api: true do
expect(json_response['access_token']).not_to be_nil
end
end
+
+ context "when user is blocked" do
+ it "does not create an access token" do
+ user = create(:user)
+ user.block
+
+ request_oauth_token(user)
+
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context "when user is ldap_blocked" do
+ it "does not create an access token" do
+ user = create(:user)
+ user.ldap_block
+
+ request_oauth_token(user)
+
+ expect(response).to have_http_status(401)
+ end
+ end
end
end
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 7652606a491..4783d011d54 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -30,7 +30,7 @@ describe API::Repositories, api: true do
context 'when ref does not exist' do
it_behaves_like '404 response' do
- let(:request) { get api("#{route}?ref_name=foo", current_user) }
+ let(:request) { get api("#{route}?ref=foo", current_user) }
let(:message) { '404 Tree Not Found' }
end
end
@@ -66,7 +66,7 @@ describe API::Repositories, api: true do
context 'when ref does not exist' do
it_behaves_like '404 response' do
- let(:request) { get api("#{route}?recursive=1&ref_name=foo", current_user) }
+ let(:request) { get api("#{route}?recursive=1&ref=foo", current_user) }
let(:message) { '404 Tree Not Found' }
end
end
@@ -100,82 +100,70 @@ describe API::Repositories, api: true do
end
end
- {
- 'blobs/:sha' => 'blobs/master',
- 'commits/:sha/blob' => 'commits/master/blob'
- }.each do |desc_path, example_path|
- describe "GET /projects/:id/repository/#{desc_path}" do
- let(:route) { "/projects/#{project.id}/repository/#{example_path}?filepath=README.md" }
+ describe "GET /projects/:id/repository/blobs/:sha" do
+ let(:route) { "/projects/#{project.id}/repository/blobs/#{sample_blob.oid}" }
- shared_examples_for 'repository blob' do
- it 'returns the repository blob' do
- get api(route, current_user)
-
- expect(response).to have_http_status(200)
- end
-
- context 'when sha does not exist' do
- it_behaves_like '404 response' do
- let(:request) { get api(route.sub('master', 'invalid_branch_name'), current_user) }
- let(:message) { '404 Commit Not Found' }
- end
- end
+ shared_examples_for 'repository blob' do
+ it 'returns blob attributes as json' do
+ get api(route, current_user)
- context 'when filepath does not exist' do
- it_behaves_like '404 response' do
- let(:request) { get api(route.sub('README.md', 'README.invalid'), current_user) }
- let(:message) { '404 File Not Found' }
- end
- end
+ expect(response).to have_http_status(200)
+ expect(json_response['size']).to eq(111)
+ expect(json_response['encoding']).to eq("base64")
+ expect(Base64.decode64(json_response['content']).lines.first).to eq("class Commit\n")
+ expect(json_response['sha']).to eq(sample_blob.oid)
+ end
- context 'when no filepath is given' do
- it_behaves_like '400 response' do
- let(:request) { get api(route.sub('?filepath=README.md', ''), current_user) }
- end
+ context 'when sha does not exist' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(route.sub(sample_blob.oid, '123456'), current_user) }
+ let(:message) { '404 Blob Not Found' }
end
+ end
- context 'when repository is disabled' do
- include_context 'disabled repository'
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
- it_behaves_like '403 response' do
- let(:request) { get api(route, current_user) }
- end
+ it_behaves_like '403 response' do
+ let(:request) { get api(route, current_user) }
end
end
+ end
- context 'when unauthenticated', 'and project is public' do
- it_behaves_like 'repository blob' do
- let(:project) { create(:project, :public, :repository) }
- let(:current_user) { nil }
- end
+ context 'when unauthenticated', 'and project is public' do
+ it_behaves_like 'repository blob' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:current_user) { nil }
end
+ end
- context 'when unauthenticated', 'and project is private' do
- it_behaves_like '404 response' do
- let(:request) { get api(route) }
- let(:message) { '404 Project Not Found' }
- end
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(route) }
+ let(:message) { '404 Project Not Found' }
end
+ end
- context 'when authenticated', 'as a developer' do
- it_behaves_like 'repository blob' do
- let(:current_user) { user }
- end
+ context 'when authenticated', 'as a developer' do
+ it_behaves_like 'repository blob' do
+ let(:current_user) { user }
end
+ end
- context 'when authenticated', 'as a guest' do
- it_behaves_like '403 response' do
- let(:request) { get api(route, guest) }
- end
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:request) { get api(route, guest) }
end
end
end
- describe "GET /projects/:id/repository/raw_blobs/:sha" do
- let(:route) { "/projects/#{project.id}/repository/raw_blobs/#{sample_blob.oid}" }
+ describe "GET /projects/:id/repository/blobs/:sha/raw" do
+ let(:route) { "/projects/#{project.id}/repository/blobs/#{sample_blob.oid}/raw" }
shared_examples_for 'repository raw blob' do
it 'returns the repository raw blob' do
+ expect(Gitlab::Workhorse).to receive(:send_git_blob)
+
get api(route, current_user)
expect(response).to have_http_status(200)
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index e83202e4196..15d458e0795 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -16,6 +16,7 @@ describe API::Runner do
context 'when no token is provided' do
it 'returns 400 error' do
post api('/runners')
+
expect(response).to have_http_status 400
end
end
@@ -23,6 +24,7 @@ describe API::Runner do
context 'when invalid token is provided' do
it 'returns 403 error' do
post api('/runners'), token: 'invalid'
+
expect(response).to have_http_status 403
end
end
@@ -108,7 +110,7 @@ describe API::Runner do
context "when info parameter '#{param}' info is present" do
let(:value) { "#{param}_value" }
- it %q(updates provided Runner's parameter) do
+ it "updates provided Runner's parameter" do
post api('/runners'), token: registration_token,
info: { param => value }
@@ -148,4 +150,874 @@ describe API::Runner do
end
end
end
+
+ describe '/api/v4/jobs' do
+ let(:project) { create(:empty_project, shared_runners_enabled: false) }
+ let(:pipeline) { create(:ci_pipeline_without_jobs, project: project, ref: 'master') }
+ let(:runner) { create(:ci_runner) }
+ let!(:job) do
+ create(:ci_build, :artifacts, :extended_options,
+ pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0, commands: "ls\ndate")
+ end
+
+ before { project.runners << runner }
+
+ describe 'POST /api/v4/jobs/request' do
+ let!(:last_update) {}
+ let!(:new_update) { }
+ let(:user_agent) { 'gitlab-runner 9.0.0 (9-0-stable; go1.7.4; linux/amd64)' }
+
+ before { stub_container_registry_config(enabled: false) }
+
+ shared_examples 'no jobs available' do
+ before { request_job }
+
+ context 'when runner sends version in User-Agent' do
+ context 'for stable version' do
+ it 'gives 204 and set X-GitLab-Last-Update' do
+ expect(response).to have_http_status(204)
+ expect(response.header).to have_key('X-GitLab-Last-Update')
+ end
+ end
+
+ context 'when last_update is up-to-date' do
+ let(:last_update) { runner.ensure_runner_queue_value }
+
+ it 'gives 204 and set the same X-GitLab-Last-Update' do
+ expect(response).to have_http_status(204)
+ expect(response.header['X-GitLab-Last-Update']).to eq(last_update)
+ end
+ end
+
+ context 'when last_update is outdated' do
+ let(:last_update) { runner.ensure_runner_queue_value }
+ let(:new_update) { runner.tick_runner_queue }
+
+ it 'gives 204 and set a new X-GitLab-Last-Update' do
+ expect(response).to have_http_status(204)
+ expect(response.header['X-GitLab-Last-Update']).to eq(new_update)
+ end
+ end
+
+ context 'when beta version is sent' do
+ let(:user_agent) { 'gitlab-runner 9.0.0~beta.167.g2b2bacc (master; go1.7.4; linux/amd64)' }
+
+ it { expect(response).to have_http_status(204) }
+ end
+
+ context 'when pre-9-0 version is sent' do
+ let(:user_agent) { 'gitlab-ci-multi-runner 1.6.0 (1-6-stable; go1.6.3; linux/amd64)' }
+
+ it { expect(response).to have_http_status(204) }
+ end
+
+ context 'when pre-9-0 beta version is sent' do
+ let(:user_agent) { 'gitlab-ci-multi-runner 1.6.0~beta.167.g2b2bacc (master; go1.6.3; linux/amd64)' }
+
+ it { expect(response).to have_http_status(204) }
+ end
+ end
+
+ context "when runner doesn't send version in User-Agent" do
+ let(:user_agent) { 'Go-http-client/1.1' }
+
+ it { expect(response).to have_http_status(404) }
+ end
+
+ context "when runner doesn't have a User-Agent" do
+ let(:user_agent) { nil }
+
+ it { expect(response).to have_http_status(404) }
+ end
+ end
+
+ context 'when no token is provided' do
+ it 'returns 400 error' do
+ post api('/jobs/request')
+
+ expect(response).to have_http_status 400
+ end
+ end
+
+ context 'when invalid token is provided' do
+ it 'returns 403 error' do
+ post api('/jobs/request'), token: 'invalid'
+
+ expect(response).to have_http_status 403
+ end
+ end
+
+ context 'when valid token is provided' do
+ context 'when Runner is not active' do
+ let(:runner) { create(:ci_runner, :inactive) }
+
+ it 'returns 404 error' do
+ request_job
+
+ expect(response).to have_http_status 404
+ end
+ end
+
+ context 'when jobs are finished' do
+ before { job.success }
+
+ it_behaves_like 'no jobs available'
+ end
+
+ context 'when other projects have pending jobs' do
+ before do
+ job.success
+ create(:ci_build, :pending)
+ end
+
+ it_behaves_like 'no jobs available'
+ end
+
+ context 'when shared runner requests job for project without shared_runners_enabled' do
+ let(:runner) { create(:ci_runner, :shared) }
+
+ it_behaves_like 'no jobs available'
+ end
+
+ context 'when there is a pending job' do
+ let(:expected_job_info) do
+ { 'name' => job.name,
+ 'stage' => job.stage,
+ 'project_id' => job.project.id,
+ 'project_name' => job.project.name }
+ end
+
+ let(:expected_git_info) do
+ { 'repo_url' => job.repo_url,
+ 'ref' => job.ref,
+ 'sha' => job.sha,
+ 'before_sha' => job.before_sha,
+ 'ref_type' => 'branch' }
+ end
+
+ let(:expected_steps) do
+ [{ 'name' => 'script',
+ 'script' => %w(ls date),
+ 'timeout' => job.timeout,
+ 'when' => 'on_success',
+ 'allow_failure' => false },
+ { 'name' => 'after_script',
+ 'script' => %w(ls date),
+ 'timeout' => job.timeout,
+ 'when' => 'always',
+ 'allow_failure' => true }]
+ end
+
+ let(:expected_variables) do
+ [{ 'key' => 'CI_BUILD_NAME', 'value' => 'spinach', 'public' => true },
+ { 'key' => 'CI_BUILD_STAGE', 'value' => 'test', 'public' => true },
+ { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true }]
+ end
+
+ let(:expected_artifacts) do
+ [{ 'name' => 'artifacts_file',
+ 'untracked' => false,
+ 'paths' => %w(out/),
+ 'when' => 'always',
+ 'expire_in' => '7d' }]
+ end
+
+ let(:expected_cache) do
+ [{ 'key' => 'cache_key',
+ 'untracked' => false,
+ 'paths' => ['vendor/*'] }]
+ end
+
+ it 'picks a job' do
+ request_job info: { platform: :darwin }
+
+ expect(response).to have_http_status(201)
+ expect(response.headers).not_to have_key('X-GitLab-Last-Update')
+ expect(runner.reload.platform).to eq('darwin')
+ expect(json_response['id']).to eq(job.id)
+ expect(json_response['token']).to eq(job.token)
+ expect(json_response['job_info']).to eq(expected_job_info)
+ expect(json_response['git_info']).to eq(expected_git_info)
+ expect(json_response['image']).to eq({ 'name' => 'ruby:2.1' })
+ expect(json_response['services']).to eq([{ 'name' => 'postgres' }])
+ expect(json_response['steps']).to eq(expected_steps)
+ expect(json_response['artifacts']).to eq(expected_artifacts)
+ expect(json_response['cache']).to eq(expected_cache)
+ expect(json_response['variables']).to include(*expected_variables)
+ end
+
+ context 'when job is made for tag' do
+ let!(:job) { create(:ci_build_tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) }
+
+ it 'sets branch as ref_type' do
+ request_job
+
+ expect(response).to have_http_status(201)
+ expect(json_response['git_info']['ref_type']).to eq('tag')
+ end
+ end
+
+ context 'when job is made for branch' do
+ it 'sets tag as ref_type' do
+ request_job
+
+ expect(response).to have_http_status(201)
+ expect(json_response['git_info']['ref_type']).to eq('branch')
+ end
+ end
+
+ it 'updates runner info' do
+ expect { request_job }.to change { runner.reload.contacted_at }
+ end
+
+ %w(name version revision platform architecture).each do |param|
+ context "when info parameter '#{param}' is present" do
+ let(:value) { "#{param}_value" }
+
+ it "updates provided Runner's parameter" do
+ request_job info: { param => value }
+
+ expect(response).to have_http_status(201)
+ expect(runner.reload.read_attribute(param.to_sym)).to eq(value)
+ end
+ end
+ end
+
+ context 'when concurrently updating a job' do
+ before do
+ expect_any_instance_of(Ci::Build).to receive(:run!).
+ and_raise(ActiveRecord::StaleObjectError.new(nil, nil))
+ end
+
+ it 'returns a conflict' do
+ request_job
+
+ expect(response).to have_http_status(409)
+ expect(response.headers).not_to have_key('X-GitLab-Last-Update')
+ end
+ end
+
+ context 'when project and pipeline have multiple jobs' do
+ let!(:test_job) { create(:ci_build, pipeline: pipeline, name: 'deploy', stage: 'deploy', stage_idx: 1) }
+
+ before { job.success }
+
+ it 'returns dependent jobs' do
+ request_job
+
+ expect(response).to have_http_status(201)
+ expect(json_response['id']).to eq(test_job.id)
+ expect(json_response['dependencies'].count).to eq(1)
+ expect(json_response['dependencies'][0]).to include('id' => job.id, 'name' => 'spinach')
+ end
+ end
+
+ context 'when job has no tags' do
+ before { job.update(tags: []) }
+
+ context 'when runner is allowed to pick untagged jobs' do
+ before { runner.update_column(:run_untagged, true) }
+
+ it 'picks job' do
+ request_job
+
+ expect(response).to have_http_status 201
+ end
+ end
+
+ context 'when runner is not allowed to pick untagged jobs' do
+ before { runner.update_column(:run_untagged, false) }
+
+ it_behaves_like 'no jobs available'
+ end
+ end
+
+ context 'when triggered job is available' do
+ let(:expected_variables) do
+ [{ 'key' => 'CI_BUILD_NAME', 'value' => 'spinach', 'public' => true },
+ { 'key' => 'CI_BUILD_STAGE', 'value' => 'test', 'public' => true },
+ { 'key' => 'CI_BUILD_TRIGGERED', 'value' => 'true', 'public' => true },
+ { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true },
+ { 'key' => 'SECRET_KEY', 'value' => 'secret_value', 'public' => false },
+ { 'key' => 'TRIGGER_KEY_1', 'value' => 'TRIGGER_VALUE_1', 'public' => false }]
+ end
+
+ before do
+ trigger = create(:ci_trigger, project: project)
+ create(:ci_trigger_request_with_variables, pipeline: pipeline, builds: [job], trigger: trigger)
+ project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
+ end
+
+ it 'returns variables for triggers' do
+ request_job
+
+ expect(response).to have_http_status(201)
+ expect(json_response['variables']).to include(*expected_variables)
+ end
+ end
+
+ describe 'registry credentials support' do
+ let(:registry_url) { 'registry.example.com:5005' }
+ let(:registry_credentials) do
+ { 'type' => 'registry',
+ 'url' => registry_url,
+ 'username' => 'gitlab-ci-token',
+ 'password' => job.token }
+ end
+
+ context 'when registry is enabled' do
+ before { stub_container_registry_config(enabled: true, host_port: registry_url) }
+
+ it 'sends registry credentials key' do
+ request_job
+
+ expect(json_response).to have_key('credentials')
+ expect(json_response['credentials']).to include(registry_credentials)
+ end
+ end
+
+ context 'when registry is disabled' do
+ before { stub_container_registry_config(enabled: false, host_port: registry_url) }
+
+ it 'does not send registry credentials' do
+ request_job
+
+ expect(json_response).to have_key('credentials')
+ expect(json_response['credentials']).not_to include(registry_credentials)
+ end
+ end
+ end
+ end
+
+ def request_job(token = runner.token, **params)
+ new_params = params.merge(token: token, last_update: last_update)
+ post api('/jobs/request'), new_params, { 'User-Agent' => user_agent }
+ end
+ end
+ end
+
+ describe 'PUT /api/v4/jobs/:id' do
+ let(:job) { create(:ci_build, :pending, :trace, pipeline: pipeline, runner_id: runner.id) }
+
+ before { job.run! }
+
+ context 'when status is given' do
+ it 'mark job as succeeded' do
+ update_job(state: 'success')
+
+ expect(job.reload.status).to eq 'success'
+ end
+
+ it 'mark job as failed' do
+ update_job(state: 'failed')
+
+ expect(job.reload.status).to eq 'failed'
+ end
+ end
+
+ context 'when tace is given' do
+ it 'updates a running build' do
+ update_job(trace: 'BUILD TRACE UPDATED')
+
+ expect(response).to have_http_status(200)
+ expect(job.reload.trace).to eq 'BUILD TRACE UPDATED'
+ end
+ end
+
+ context 'when no trace is given' do
+ it 'does not override trace information' do
+ update_job
+
+ expect(job.reload.trace).to eq 'BUILD TRACE'
+ end
+ end
+
+ context 'when job has been erased' do
+ let(:job) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) }
+
+ it 'responds with forbidden' do
+ update_job
+
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ def update_job(token = job.token, **params)
+ new_params = params.merge(token: token)
+ put api("/jobs/#{job.id}"), new_params
+ end
+ end
+
+ describe 'PATCH /api/v4/jobs/:id/trace' do
+ let(:job) { create(:ci_build, :running, :trace, runner_id: runner.id, pipeline: pipeline) }
+ let(:headers) { { API::Helpers::Runner::JOB_TOKEN_HEADER => job.token, 'Content-Type' => 'text/plain' } }
+ let(:headers_with_range) { headers.merge({ 'Content-Range' => '11-20' }) }
+ let(:update_interval) { 10.seconds.to_i }
+
+ before { initial_patch_the_trace }
+
+ context 'when request is valid' do
+ it 'gets correct response' do
+ expect(response.status).to eq 202
+ expect(job.reload.trace).to eq 'BUILD TRACE appended'
+ expect(response.header).to have_key 'Range'
+ expect(response.header).to have_key 'Job-Status'
+ end
+
+ context 'when job has been updated recently' do
+ it { expect{ patch_the_trace }.not_to change { job.updated_at }}
+
+ it "changes the job's trace" do
+ patch_the_trace
+
+ expect(job.reload.trace).to eq 'BUILD TRACE appended appended'
+ end
+
+ context 'when Runner makes a force-patch' do
+ it { expect{ force_patch_the_trace }.not_to change { job.updated_at }}
+
+ it "doesn't change the build.trace" do
+ force_patch_the_trace
+
+ expect(job.reload.trace).to eq 'BUILD TRACE appended'
+ end
+ end
+ end
+
+ context 'when job was not updated recently' do
+ let(:update_interval) { 15.minutes.to_i }
+
+ it { expect { patch_the_trace }.to change { job.updated_at } }
+
+ it 'changes the job.trace' do
+ patch_the_trace
+
+ expect(job.reload.trace).to eq 'BUILD TRACE appended appended'
+ end
+
+ context 'when Runner makes a force-patch' do
+ it { expect { force_patch_the_trace }.to change { job.updated_at } }
+
+ it "doesn't change the job.trace" do
+ force_patch_the_trace
+
+ expect(job.reload.trace).to eq 'BUILD TRACE appended'
+ end
+ end
+ end
+
+ context 'when project for the build has been deleted' do
+ let(:job) do
+ create(:ci_build, :running, :trace, runner_id: runner.id, pipeline: pipeline) do |job|
+ job.project.update(pending_delete: true)
+ end
+ end
+
+ it 'responds with forbidden' do
+ expect(response.status).to eq(403)
+ end
+ end
+ end
+
+ context 'when Runner makes a force-patch' do
+ before do
+ force_patch_the_trace
+ end
+
+ it 'gets correct response' do
+ expect(response.status).to eq 202
+ expect(job.reload.trace).to eq 'BUILD TRACE appended'
+ expect(response.header).to have_key 'Range'
+ expect(response.header).to have_key 'Job-Status'
+ end
+ end
+
+ context 'when content-range start is too big' do
+ let(:headers_with_range) { headers.merge({ 'Content-Range' => '15-20' }) }
+
+ it 'gets 416 error response with range headers' do
+ expect(response.status).to eq 416
+ expect(response.header).to have_key 'Range'
+ expect(response.header['Range']).to eq '0-11'
+ end
+ end
+
+ context 'when content-range start is too small' do
+ let(:headers_with_range) { headers.merge({ 'Content-Range' => '8-20' }) }
+
+ it 'gets 416 error response with range headers' do
+ expect(response.status).to eq 416
+ expect(response.header).to have_key 'Range'
+ expect(response.header['Range']).to eq '0-11'
+ end
+ end
+
+ context 'when Content-Range header is missing' do
+ let(:headers_with_range) { headers }
+
+ it { expect(response.status).to eq 400 }
+ end
+
+ context 'when job has been errased' do
+ let(:job) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) }
+
+ it { expect(response.status).to eq 403 }
+ end
+
+ def patch_the_trace(content = ' appended', request_headers = nil)
+ unless request_headers
+ offset = job.trace_length
+ limit = offset + content.length - 1
+ request_headers = headers.merge({ 'Content-Range' => "#{offset}-#{limit}" })
+ end
+
+ Timecop.travel(job.updated_at + update_interval) do
+ patch api("/jobs/#{job.id}/trace"), content, request_headers
+ job.reload
+ end
+ end
+
+ def initial_patch_the_trace
+ patch_the_trace(' appended', headers_with_range)
+ end
+
+ def force_patch_the_trace
+ 2.times { patch_the_trace('') }
+ end
+ end
+
+ describe 'artifacts' do
+ let(:job) { create(:ci_build, :pending, pipeline: pipeline, runner_id: runner.id) }
+ let(:jwt_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let(:headers) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => jwt_token } }
+ let(:headers_with_token) { headers.merge(API::Helpers::Runner::JOB_TOKEN_HEADER => job.token) }
+ let(:file_upload) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
+ let(:file_upload2) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/gif') }
+
+ before { job.run! }
+
+ describe 'POST /api/v4/jobs/:id/artifacts/authorize' do
+ context 'when using token as parameter' do
+ it 'authorizes posting artifacts to running job' do
+ authorize_artifacts_with_token_in_params
+
+ expect(response).to have_http_status(200)
+ expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(json_response['TempPath']).not_to be_nil
+ end
+
+ it 'fails to post too large artifact' do
+ stub_application_setting(max_artifacts_size: 0)
+
+ authorize_artifacts_with_token_in_params(filesize: 100)
+
+ expect(response).to have_http_status(413)
+ end
+ end
+
+ context 'when using token as header' do
+ it 'authorizes posting artifacts to running job' do
+ authorize_artifacts_with_token_in_headers
+
+ expect(response).to have_http_status(200)
+ expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(json_response['TempPath']).not_to be_nil
+ end
+
+ it 'fails to post too large artifact' do
+ stub_application_setting(max_artifacts_size: 0)
+
+ authorize_artifacts_with_token_in_headers(filesize: 100)
+
+ expect(response).to have_http_status(413)
+ end
+ end
+
+ context 'when using runners token' do
+ it 'fails to authorize artifacts posting' do
+ authorize_artifacts(token: job.project.runners_token)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ it 'reject requests that did not go through gitlab-workhorse' do
+ headers.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER)
+
+ authorize_artifacts
+
+ expect(response).to have_http_status(500)
+ end
+
+ context 'authorization token is invalid' do
+ it 'responds with forbidden' do
+ authorize_artifacts(token: 'invalid', filesize: 100 )
+
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ def authorize_artifacts(params = {}, request_headers = headers)
+ post api("/jobs/#{job.id}/artifacts/authorize"), params, request_headers
+ end
+
+ def authorize_artifacts_with_token_in_params(params = {}, request_headers = headers)
+ params = params.merge(token: job.token)
+ authorize_artifacts(params, request_headers)
+ end
+
+ def authorize_artifacts_with_token_in_headers(params = {}, request_headers = headers_with_token)
+ authorize_artifacts(params, request_headers)
+ end
+ end
+
+ describe 'POST /api/v4/jobs/:id/artifacts' do
+ context 'when artifacts are being stored inside of tmp path' do
+ before do
+ # by configuring this path we allow to pass temp file from any path
+ allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return('/')
+ end
+
+ context 'when job has been erased' do
+ let(:job) { create(:ci_build, erased_at: Time.now) }
+
+ before do
+ upload_artifacts(file_upload, headers_with_token)
+ end
+
+ it 'responds with forbidden' do
+ upload_artifacts(file_upload, headers_with_token)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context 'when job is running' do
+ shared_examples 'successful artifacts upload' do
+ it 'updates successfully' do
+ expect(response).to have_http_status(201)
+ end
+ end
+
+ context 'when uses regular file post' do
+ before { upload_artifacts(file_upload, headers_with_token, false) }
+
+ it_behaves_like 'successful artifacts upload'
+ end
+
+ context 'when uses accelerated file post' do
+ before { upload_artifacts(file_upload, headers_with_token, true) }
+
+ it_behaves_like 'successful artifacts upload'
+ end
+
+ context 'when updates artifact' do
+ before do
+ upload_artifacts(file_upload2, headers_with_token)
+ upload_artifacts(file_upload, headers_with_token)
+ end
+
+ it_behaves_like 'successful artifacts upload'
+ end
+
+ context 'when using runners token' do
+ it 'responds with forbidden' do
+ upload_artifacts(file_upload, headers.merge(API::Helpers::Runner::JOB_TOKEN_HEADER => job.project.runners_token))
+
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+
+ context 'when artifacts file is too large' do
+ it 'fails to post too large artifact' do
+ stub_application_setting(max_artifacts_size: 0)
+
+ upload_artifacts(file_upload, headers_with_token)
+
+ expect(response).to have_http_status(413)
+ end
+ end
+
+ context 'when artifacts post request does not contain file' do
+ it 'fails to post artifacts without file' do
+ post api("/jobs/#{job.id}/artifacts"), {}, headers_with_token
+
+ expect(response).to have_http_status(400)
+ end
+ end
+
+ context 'GitLab Workhorse is not configured' do
+ it 'fails to post artifacts without GitLab-Workhorse' do
+ post api("/jobs/#{job.id}/artifacts"), { token: job.token }, {}
+
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context 'when setting an expire date' do
+ let(:default_artifacts_expire_in) {}
+ let(:post_data) do
+ { 'file.path' => file_upload.path,
+ 'file.name' => file_upload.original_filename,
+ 'expire_in' => expire_in }
+ end
+
+ before do
+ stub_application_setting(default_artifacts_expire_in: default_artifacts_expire_in)
+
+ post(api("/jobs/#{job.id}/artifacts"), post_data, headers_with_token)
+ end
+
+ context 'when an expire_in is given' do
+ let(:expire_in) { '7 days' }
+
+ it 'updates when specified' do
+ expect(response).to have_http_status(201)
+ expect(job.reload.artifacts_expire_at).to be_within(5.minutes).of(7.days.from_now)
+ end
+ end
+
+ context 'when no expire_in is given' do
+ let(:expire_in) { nil }
+
+ it 'ignores if not specified' do
+ expect(response).to have_http_status(201)
+ expect(job.reload.artifacts_expire_at).to be_nil
+ end
+
+ context 'with application default' do
+ context 'when default is 5 days' do
+ let(:default_artifacts_expire_in) { '5 days' }
+
+ it 'sets to application default' do
+ expect(response).to have_http_status(201)
+ expect(job.reload.artifacts_expire_at).to be_within(5.minutes).of(5.days.from_now)
+ end
+ end
+
+ context 'when default is 0' do
+ let(:default_artifacts_expire_in) { '0' }
+
+ it 'does not set expire_in' do
+ expect(response).to have_http_status(201)
+ expect(job.reload.artifacts_expire_at).to be_nil
+ end
+ end
+ end
+ end
+ end
+
+ context 'posts artifacts file and metadata file' do
+ let!(:artifacts) { file_upload }
+ let!(:metadata) { file_upload2 }
+
+ let(:stored_artifacts_file) { job.reload.artifacts_file.file }
+ let(:stored_metadata_file) { job.reload.artifacts_metadata.file }
+ let(:stored_artifacts_size) { job.reload.artifacts_size }
+
+ before do
+ post(api("/jobs/#{job.id}/artifacts"), post_data, headers_with_token)
+ end
+
+ context 'when posts data accelerated by workhorse is correct' do
+ let(:post_data) do
+ { 'file.path' => artifacts.path,
+ 'file.name' => artifacts.original_filename,
+ 'metadata.path' => metadata.path,
+ 'metadata.name' => metadata.original_filename }
+ end
+
+ it 'stores artifacts and artifacts metadata' do
+ expect(response).to have_http_status(201)
+ expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename)
+ expect(stored_metadata_file.original_filename).to eq(metadata.original_filename)
+ expect(stored_artifacts_size).to eq(71759)
+ end
+ end
+
+ context 'when there is no artifacts file in post data' do
+ let(:post_data) do
+ { 'metadata' => metadata }
+ end
+
+ it 'is expected to respond with bad request' do
+ expect(response).to have_http_status(400)
+ end
+
+ it 'does not store metadata' do
+ expect(stored_metadata_file).to be_nil
+ end
+ end
+ end
+ end
+
+ context 'when artifacts are being stored outside of tmp path' do
+ before do
+ # by configuring this path we allow to pass file from @tmpdir only
+ # but all temporary files are stored in system tmp directory
+ @tmpdir = Dir.mktmpdir
+ allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return(@tmpdir)
+ end
+
+ after { FileUtils.remove_entry @tmpdir }
+
+ it' "fails to post artifacts for outside of tmp path"' do
+ upload_artifacts(file_upload, headers_with_token)
+
+ expect(response).to have_http_status(400)
+ end
+ end
+
+ def upload_artifacts(file, headers = {}, accelerated = true)
+ params = if accelerated
+ { 'file.path' => file.path, 'file.name' => file.original_filename }
+ else
+ { 'file' => file }
+ end
+ post api("/jobs/#{job.id}/artifacts"), params, headers
+ end
+ end
+
+ describe 'GET /api/v4/jobs/:id/artifacts' do
+ let(:token) { job.token }
+
+ before { download_artifact }
+
+ context 'when job has artifacts' do
+ let(:job) { create(:ci_build, :artifacts) }
+ let(:download_headers) do
+ { 'Content-Transfer-Encoding' => 'binary',
+ 'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' }
+ end
+
+ context 'when using job token' do
+ it 'download artifacts' do
+ expect(response).to have_http_status(200)
+ expect(response.headers).to include download_headers
+ end
+ end
+
+ context 'when using runnners token' do
+ let(:token) { job.project.runners_token }
+
+ it 'responds with forbidden' do
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+
+ context 'when job does not has artifacts' do
+ it 'responds with not found' do
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ def download_artifact(params = {}, request_headers = headers)
+ params = params.merge(token: token)
+ get api("/jobs/#{job.id}/artifacts"), params, request_headers
+ end
+ end
+ end
+ end
end
diff --git a/spec/requests/api/session_spec.rb b/spec/requests/api/session_spec.rb
index 794e2b5c04d..28fab2011a5 100644
--- a/spec/requests/api/session_spec.rb
+++ b/spec/requests/api/session_spec.rb
@@ -87,5 +87,23 @@ describe API::Session, api: true do
expect(response).to have_http_status(400)
end
end
+
+ context "when user is blocked" do
+ it "returns authentication error" do
+ user.block
+ post api("/session"), email: user.username, password: user.password
+
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context "when user is ldap_blocked" do
+ it "returns authentication error" do
+ user.ldap_block
+ post api("/session"), email: user.username, password: user.password
+
+ expect(response).to have_http_status(401)
+ end
+ end
end
end
diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb
index 1e401935662..b789284fa8d 100644
--- a/spec/requests/api/todos_spec.rb
+++ b/spec/requests/api/todos_spec.rb
@@ -163,7 +163,7 @@ describe API::Todos, api: true do
shared_examples 'an issuable' do |issuable_type|
it 'creates a todo on an issuable' do
- post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.id}/todo", john_doe)
+ post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.iid}/todo", john_doe)
expect(response.status).to eq(201)
expect(json_response['project']).to be_a Hash
@@ -180,7 +180,7 @@ describe API::Todos, api: true do
it 'returns 304 there already exist a todo on that issuable' do
create(:todo, project: project_1, author: author_1, user: john_doe, target: issuable)
- post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.id}/todo", john_doe)
+ post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.iid}/todo", john_doe)
expect(response.status).to eq(304)
end
@@ -195,7 +195,7 @@ describe API::Todos, api: true do
guest = create(:user)
project_1.team << [guest, :guest]
- post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.id}/todo", guest)
+ post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.iid}/todo", guest)
if issuable_type == 'merge_requests'
expect(response).to have_http_status(403)
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 881c48c75e0..04e7837fd7a 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -10,6 +10,8 @@ describe API::Users, api: true do
let(:omniauth_user) { create(:omniauth_user) }
let(:ldap_user) { create(:omniauth_user, provider: 'ldapmain') }
let(:ldap_blocked_user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') }
+ let(:not_existing_user_id) { (User.maximum('id') || 0 ) + 10 }
+ let(:not_existing_pat_id) { (PersonalAccessToken.maximum('id') || 0 ) + 10 }
describe "GET /users" do
context "when unauthenticated" do
@@ -1155,4 +1157,187 @@ describe API::Users, api: true do
expect(json_response['message']).to eq('404 User Not Found')
end
end
+
+ describe 'GET /users/:user_id/impersonation_tokens' do
+ let!(:active_personal_access_token) { create(:personal_access_token, user: user) }
+ let!(:revoked_personal_access_token) { create(:personal_access_token, :revoked, user: user) }
+ let!(:expired_personal_access_token) { create(:personal_access_token, :expired, user: user) }
+ let!(:impersonation_token) { create(:personal_access_token, :impersonation, user: user) }
+ let!(:revoked_impersonation_token) { create(:personal_access_token, :impersonation, :revoked, user: user) }
+
+ it 'returns a 404 error if user not found' do
+ get api("/users/#{not_existing_user_id}/impersonation_tokens", admin)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+
+ it 'returns a 403 error when authenticated as normal user' do
+ get api("/users/#{not_existing_user_id}/impersonation_tokens", user)
+
+ expect(response).to have_http_status(403)
+ expect(json_response['message']).to eq('403 Forbidden')
+ end
+
+ it 'returns an array of all impersonated tokens' do
+ get api("/users/#{user.id}/impersonation_tokens", admin)
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(2)
+ end
+
+ it 'returns an array of active impersonation tokens if state active' do
+ get api("/users/#{user.id}/impersonation_tokens?state=active", admin)
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+ expect(json_response).to all(include('active' => true))
+ end
+
+ it 'returns an array of inactive personal access tokens if active is set to false' do
+ get api("/users/#{user.id}/impersonation_tokens?state=inactive", admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+ expect(json_response).to all(include('active' => false))
+ end
+ end
+
+ describe 'POST /users/:user_id/impersonation_tokens' do
+ let(:name) { 'my new pat' }
+ let(:expires_at) { '2016-12-28' }
+ let(:scopes) { %w(api read_user) }
+ let(:impersonation) { true }
+
+ it 'returns validation error if impersonation token misses some attributes' do
+ post api("/users/#{user.id}/impersonation_tokens", admin)
+
+ expect(response).to have_http_status(400)
+ expect(json_response['error']).to eq('name is missing')
+ end
+
+ it 'returns a 404 error if user not found' do
+ post api("/users/#{not_existing_user_id}/impersonation_tokens", admin),
+ name: name,
+ expires_at: expires_at
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+
+ it 'returns a 403 error when authenticated as normal user' do
+ post api("/users/#{user.id}/impersonation_tokens", user),
+ name: name,
+ expires_at: expires_at
+
+ expect(response).to have_http_status(403)
+ expect(json_response['message']).to eq('403 Forbidden')
+ end
+
+ it 'creates a impersonation token' do
+ post api("/users/#{user.id}/impersonation_tokens", admin),
+ name: name,
+ expires_at: expires_at,
+ scopes: scopes,
+ impersonation: impersonation
+
+ expect(response).to have_http_status(201)
+ expect(json_response['name']).to eq(name)
+ expect(json_response['scopes']).to eq(scopes)
+ expect(json_response['expires_at']).to eq(expires_at)
+ expect(json_response['id']).to be_present
+ expect(json_response['created_at']).to be_present
+ expect(json_response['active']).to be_falsey
+ expect(json_response['revoked']).to be_falsey
+ expect(json_response['token']).to be_present
+ expect(json_response['impersonation']).to eq(impersonation)
+ end
+ end
+
+ describe 'GET /users/:user_id/impersonation_tokens/:impersonation_token_id' do
+ let!(:personal_access_token) { create(:personal_access_token, user: user) }
+ let!(:impersonation_token) { create(:personal_access_token, :impersonation, user: user) }
+
+ it 'returns 404 error if user not found' do
+ get api("/users/#{not_existing_user_id}/impersonation_tokens/1", admin)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+
+ it 'returns a 404 error if impersonation token not found' do
+ get api("/users/#{user.id}/impersonation_tokens/#{not_existing_pat_id}", admin)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 Impersonation Token Not Found')
+ end
+
+ it 'returns a 404 error if token is not impersonation token' do
+ get api("/users/#{user.id}/impersonation_tokens/#{personal_access_token.id}", admin)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 Impersonation Token Not Found')
+ end
+
+ it 'returns a 403 error when authenticated as normal user' do
+ get api("/users/#{user.id}/impersonation_tokens/#{impersonation_token.id}", user)
+
+ expect(response).to have_http_status(403)
+ expect(json_response['message']).to eq('403 Forbidden')
+ end
+
+ it 'returns a personal access token' do
+ get api("/users/#{user.id}/impersonation_tokens/#{impersonation_token.id}", admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['token']).to be_present
+ expect(json_response['impersonation']).to be_truthy
+ end
+ end
+
+ describe 'DELETE /users/:user_id/impersonation_tokens/:impersonation_token_id' do
+ let!(:personal_access_token) { create(:personal_access_token, user: user) }
+ let!(:impersonation_token) { create(:personal_access_token, :impersonation, user: user) }
+
+ it 'returns a 404 error if user not found' do
+ delete api("/users/#{not_existing_user_id}/impersonation_tokens/1", admin)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+
+ it 'returns a 404 error if impersonation token not found' do
+ delete api("/users/#{user.id}/impersonation_tokens/#{not_existing_pat_id}", admin)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 Impersonation Token Not Found')
+ end
+
+ it 'returns a 404 error if token is not impersonation token' do
+ delete api("/users/#{user.id}/impersonation_tokens/#{personal_access_token.id}", admin)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 Impersonation Token Not Found')
+ end
+
+ it 'returns a 403 error when authenticated as normal user' do
+ delete api("/users/#{user.id}/impersonation_tokens/#{impersonation_token.id}", user)
+
+ expect(response).to have_http_status(403)
+ expect(json_response['message']).to eq('403 Forbidden')
+ end
+
+ it 'revokes a impersonation token' do
+ delete api("/users/#{user.id}/impersonation_tokens/#{impersonation_token.id}", admin)
+
+ expect(response).to have_http_status(204)
+ expect(impersonation_token.revoked).to be_falsey
+ expect(impersonation_token.reload.revoked).to be_truthy
+ end
+ end
end
diff --git a/spec/requests/api/v3/award_emoji_spec.rb b/spec/requests/api/v3/award_emoji_spec.rb
index 91145c8e72c..eeb4d128c1b 100644
--- a/spec/requests/api/v3/award_emoji_spec.rb
+++ b/spec/requests/api/v3/award_emoji_spec.rb
@@ -13,6 +13,231 @@ describe API::V3::AwardEmoji, api: true do
before { project.team << [user, :master] }
+ describe "GET /projects/:id/awardable/:awardable_id/award_emoji" do
+ context 'on an issue' do
+ it "returns an array of award_emoji" do
+ get v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).to eq(award_emoji.name)
+ end
+
+ it "returns a 404 error when issue id not found" do
+ get v3_api("/projects/#{project.id}/issues/12345/award_emoji", user)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'on a merge request' do
+ it "returns an array of award_emoji" do
+ get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji", user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).to eq(downvote.name)
+ end
+ end
+
+ context 'on a snippet' do
+ let(:snippet) { create(:project_snippet, :public, project: project) }
+ let!(:award) { create(:award_emoji, awardable: snippet) }
+
+ it 'returns the awarded emoji' do
+ get v3_api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).to eq(award.name)
+ end
+ end
+
+ context 'when the user has no access' do
+ it 'returns a status code 404' do
+ user1 = create(:user)
+
+ get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji", user1)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/awardable/:awardable_id/notes/:note_id/award_emoji' do
+ let!(:rocket) { create(:award_emoji, awardable: note, name: 'rocket') }
+
+ it 'returns an array of award emoji' do
+ get v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).to eq(rocket.name)
+ end
+ end
+
+ describe "GET /projects/:id/awardable/:awardable_id/award_emoji/:award_id" do
+ context 'on an issue' do
+ it "returns the award emoji" do
+ get v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/#{award_emoji.id}", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['name']).to eq(award_emoji.name)
+ expect(json_response['awardable_id']).to eq(issue.id)
+ expect(json_response['awardable_type']).to eq("Issue")
+ end
+
+ it "returns a 404 error if the award is not found" do
+ get v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/12345", user)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'on a merge request' do
+ it 'returns the award emoji' do
+ get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji/#{downvote.id}", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['name']).to eq(downvote.name)
+ expect(json_response['awardable_id']).to eq(merge_request.id)
+ expect(json_response['awardable_type']).to eq("MergeRequest")
+ end
+ end
+
+ context 'on a snippet' do
+ let(:snippet) { create(:project_snippet, :public, project: project) }
+ let!(:award) { create(:award_emoji, awardable: snippet) }
+
+ it 'returns the awarded emoji' do
+ get v3_api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji/#{award.id}", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['name']).to eq(award.name)
+ expect(json_response['awardable_id']).to eq(snippet.id)
+ expect(json_response['awardable_type']).to eq("Snippet")
+ end
+ end
+
+ context 'when the user has no access' do
+ it 'returns a status code 404' do
+ user1 = create(:user)
+
+ get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji/#{downvote.id}", user1)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/awardable/:awardable_id/notes/:note_id/award_emoji/:award_id' do
+ let!(:rocket) { create(:award_emoji, awardable: note, name: 'rocket') }
+
+ it 'returns an award emoji' do
+ get v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji/#{rocket.id}", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).not_to be_an Array
+ expect(json_response['name']).to eq(rocket.name)
+ end
+ end
+
+ describe "POST /projects/:id/awardable/:awardable_id/award_emoji" do
+ let(:issue2) { create(:issue, project: project, author: user) }
+
+ context "on an issue" do
+ it "creates a new award emoji" do
+ post v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'blowfish'
+
+ expect(response).to have_http_status(201)
+ expect(json_response['name']).to eq('blowfish')
+ expect(json_response['user']['username']).to eq(user.username)
+ end
+
+ it "returns a 400 bad request error if the name is not given" do
+ post v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user)
+
+ expect(response).to have_http_status(400)
+ end
+
+ it "returns a 401 unauthorized error if the user is not authenticated" do
+ post v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji"), name: 'thumbsup'
+
+ expect(response).to have_http_status(401)
+ end
+
+ it "returns a 404 error if the user authored issue" do
+ post v3_api("/projects/#{project.id}/issues/#{issue2.id}/award_emoji", user), name: 'thumbsup'
+
+ expect(response).to have_http_status(404)
+ end
+
+ it "normalizes +1 as thumbsup award" do
+ post v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: '+1'
+
+ expect(issue.award_emoji.last.name).to eq("thumbsup")
+ end
+
+ context 'when the emoji already has been awarded' do
+ it 'returns a 404 status code' do
+ post v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'thumbsup'
+ post v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'thumbsup'
+
+ expect(response).to have_http_status(404)
+ expect(json_response["message"]).to match("has already been taken")
+ end
+ end
+ end
+
+ context 'on a snippet' do
+ it 'creates a new award emoji' do
+ snippet = create(:project_snippet, :public, project: project)
+
+ post v3_api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji", user), name: 'blowfish'
+
+ expect(response).to have_http_status(201)
+ expect(json_response['name']).to eq('blowfish')
+ expect(json_response['user']['username']).to eq(user.username)
+ end
+ end
+ end
+
+ describe "POST /projects/:id/awardable/:awardable_id/notes/:note_id/award_emoji" do
+ let(:note2) { create(:note, project: project, noteable: issue, author: user) }
+
+ it 'creates a new award emoji' do
+ expect do
+ post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket'
+ end.to change { note.award_emoji.count }.from(0).to(1)
+
+ expect(response).to have_http_status(201)
+ expect(json_response['user']['username']).to eq(user.username)
+ end
+
+ it "it returns 404 error when user authored note" do
+ post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note2.id}/award_emoji", user), name: 'thumbsup'
+
+ expect(response).to have_http_status(404)
+ end
+
+ it "normalizes +1 as thumbsup award" do
+ post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: '+1'
+
+ expect(note.award_emoji.last.name).to eq("thumbsup")
+ end
+
+ context 'when the emoji already has been awarded' do
+ it 'returns a 404 status code' do
+ post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket'
+ post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket'
+
+ expect(response).to have_http_status(404)
+ expect(json_response["message"]).to match("has already been taken")
+ end
+ end
+ end
+
describe 'DELETE /projects/:id/awardable/:awardable_id/award_emoji/:award_id' do
context 'when the awardable is an Issue' do
it 'deletes the award' do
diff --git a/spec/requests/api/v3/issues_spec.rb b/spec/requests/api/v3/issues_spec.rb
index 2a8105d5a2b..1941ca0d7d8 100644
--- a/spec/requests/api/v3/issues_spec.rb
+++ b/spec/requests/api/v3/issues_spec.rb
@@ -1288,6 +1288,6 @@ describe API::V3::Issues, api: true do
describe 'time tracking endpoints' do
let(:issuable) { issue }
- include_examples 'time tracking endpoints', 'issue'
+ include_examples 'V3 time tracking endpoints', 'issue'
end
end
diff --git a/spec/requests/api/v3/merge_request_diffs_spec.rb b/spec/requests/api/v3/merge_request_diffs_spec.rb
index e1887138aab..c53800eef30 100644
--- a/spec/requests/api/v3/merge_request_diffs_spec.rb
+++ b/spec/requests/api/v3/merge_request_diffs_spec.rb
@@ -1,6 +1,6 @@
require "spec_helper"
-describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do
+describe API::V3::MergeRequestDiffs, 'MergeRequestDiffs', api: true do
include ApiHelpers
let!(:user) { create(:user) }
@@ -15,7 +15,7 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do
describe 'GET /projects/:id/merge_requests/:merge_request_id/versions' do
it 'returns 200 for a valid merge request' do
- get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions", user)
+ get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions", user)
merge_request_diff = merge_request.merge_request_diffs.first
expect(response.status).to eq 200
@@ -25,7 +25,7 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do
end
it 'returns a 404 when merge_request_id not found' do
- get api("/projects/#{project.id}/merge_requests/999/versions", user)
+ get v3_api("/projects/#{project.id}/merge_requests/999/versions", user)
expect(response).to have_http_status(404)
end
end
@@ -33,7 +33,7 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do
describe 'GET /projects/:id/merge_requests/:merge_request_id/versions/:version_id' do
it 'returns a 200 for a valid merge request' do
merge_request_diff = merge_request.merge_request_diffs.first
- get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/#{merge_request_diff.id}", user)
+ get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/#{merge_request_diff.id}", user)
expect(response.status).to eq 200
expect(json_response['id']).to eq(merge_request_diff.id)
@@ -42,7 +42,8 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do
end
it 'returns a 404 when merge_request_id not found' do
- get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/999", user)
+ get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/999", user)
+
expect(response).to have_http_status(404)
end
end
diff --git a/spec/requests/api/v3/merge_requests_spec.rb b/spec/requests/api/v3/merge_requests_spec.rb
index b7ed643bc21..d73e9635c9b 100644
--- a/spec/requests/api/v3/merge_requests_spec.rb
+++ b/spec/requests/api/v3/merge_requests_spec.rb
@@ -712,7 +712,7 @@ describe API::MergeRequests, api: true do
describe 'Time tracking' do
let(:issuable) { merge_request }
- include_examples 'time tracking endpoints', 'merge_request'
+ include_examples 'V3 time tracking endpoints', 'merge_request'
end
def mr_with_later_created_and_updated_at_time
diff --git a/spec/requests/api/v3/repositories_spec.rb b/spec/requests/api/v3/repositories_spec.rb
index c696721c1c9..fef6fb641fa 100644
--- a/spec/requests/api/v3/repositories_spec.rb
+++ b/spec/requests/api/v3/repositories_spec.rb
@@ -3,6 +3,8 @@ require 'mime/types'
describe API::V3::Repositories, api: true do
include ApiHelpers
+ include RepoHelpers
+ include WorkhorseHelpers
let(:user) { create(:user) }
let(:guest) { create(:user).tap { |u| create(:project_member, :guest, user: u, project: project) } }
@@ -96,6 +98,226 @@ describe API::V3::Repositories, api: true do
end
end
+ {
+ 'blobs/:sha' => 'blobs/master',
+ 'commits/:sha/blob' => 'commits/master/blob'
+ }.each do |desc_path, example_path|
+ describe "GET /projects/:id/repository/#{desc_path}" do
+ let(:route) { "/projects/#{project.id}/repository/#{example_path}?filepath=README.md" }
+ shared_examples_for 'repository blob' do
+ it 'returns the repository blob' do
+ get v3_api(route, current_user)
+ expect(response).to have_http_status(200)
+ end
+ context 'when sha does not exist' do
+ it_behaves_like '404 response' do
+ let(:request) { get v3_api(route.sub('master', 'invalid_branch_name'), current_user) }
+ let(:message) { '404 Commit Not Found' }
+ end
+ end
+ context 'when filepath does not exist' do
+ it_behaves_like '404 response' do
+ let(:request) { get v3_api(route.sub('README.md', 'README.invalid'), current_user) }
+ let(:message) { '404 File Not Found' }
+ end
+ end
+ context 'when no filepath is given' do
+ it_behaves_like '400 response' do
+ let(:request) { get v3_api(route.sub('?filepath=README.md', ''), current_user) }
+ end
+ end
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
+ it_behaves_like '403 response' do
+ let(:request) { get v3_api(route, current_user) }
+ end
+ end
+ end
+ context 'when unauthenticated', 'and project is public' do
+ it_behaves_like 'repository blob' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:current_user) { nil }
+ end
+ end
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get v3_api(route) }
+ let(:message) { '404 Project Not Found' }
+ end
+ end
+ context 'when authenticated', 'as a developer' do
+ it_behaves_like 'repository blob' do
+ let(:current_user) { user }
+ end
+ end
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:request) { get v3_api(route, guest) }
+ end
+ end
+ end
+ end
+ describe "GET /projects/:id/repository/raw_blobs/:sha" do
+ let(:route) { "/projects/#{project.id}/repository/raw_blobs/#{sample_blob.oid}" }
+ shared_examples_for 'repository raw blob' do
+ it 'returns the repository raw blob' do
+ get v3_api(route, current_user)
+ expect(response).to have_http_status(200)
+ end
+ context 'when sha does not exist' do
+ it_behaves_like '404 response' do
+ let(:request) { get v3_api(route.sub(sample_blob.oid, '123456'), current_user) }
+ let(:message) { '404 Blob Not Found' }
+ end
+ end
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
+ it_behaves_like '403 response' do
+ let(:request) { get v3_api(route, current_user) }
+ end
+ end
+ end
+ context 'when unauthenticated', 'and project is public' do
+ it_behaves_like 'repository raw blob' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:current_user) { nil }
+ end
+ end
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get v3_api(route) }
+ let(:message) { '404 Project Not Found' }
+ end
+ end
+ context 'when authenticated', 'as a developer' do
+ it_behaves_like 'repository raw blob' do
+ let(:current_user) { user }
+ end
+ end
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:request) { get v3_api(route, guest) }
+ end
+ end
+ end
+ describe "GET /projects/:id/repository/archive(.:format)?:sha" do
+ let(:route) { "/projects/#{project.id}/repository/archive" }
+ shared_examples_for 'repository archive' do
+ it 'returns the repository archive' do
+ get v3_api(route, current_user)
+ expect(response).to have_http_status(200)
+ repo_name = project.repository.name.gsub("\.git", "")
+ type, params = workhorse_send_data
+ expect(type).to eq('git-archive')
+ expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.gz/)
+ end
+ it 'returns the repository archive archive.zip' do
+ get v3_api("/projects/#{project.id}/repository/archive.zip", user)
+ expect(response).to have_http_status(200)
+ repo_name = project.repository.name.gsub("\.git", "")
+ type, params = workhorse_send_data
+ expect(type).to eq('git-archive')
+ expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.zip/)
+ end
+ it 'returns the repository archive archive.tar.bz2' do
+ get v3_api("/projects/#{project.id}/repository/archive.tar.bz2", user)
+ expect(response).to have_http_status(200)
+ repo_name = project.repository.name.gsub("\.git", "")
+ type, params = workhorse_send_data
+ expect(type).to eq('git-archive')
+ expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.bz2/)
+ end
+ context 'when sha does not exist' do
+ it_behaves_like '404 response' do
+ let(:request) { get v3_api("#{route}?sha=xxx", current_user) }
+ let(:message) { '404 File Not Found' }
+ end
+ end
+ end
+ context 'when unauthenticated', 'and project is public' do
+ it_behaves_like 'repository archive' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:current_user) { nil }
+ end
+ end
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get v3_api(route) }
+ let(:message) { '404 Project Not Found' }
+ end
+ end
+ context 'when authenticated', 'as a developer' do
+ it_behaves_like 'repository archive' do
+ let(:current_user) { user }
+ end
+ end
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:request) { get v3_api(route, guest) }
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/repository/compare' do
+ let(:route) { "/projects/#{project.id}/repository/compare" }
+ shared_examples_for 'repository compare' do
+ it "compares branches" do
+ get v3_api(route, current_user), from: 'master', to: 'feature'
+ expect(response).to have_http_status(200)
+ expect(json_response['commits']).to be_present
+ expect(json_response['diffs']).to be_present
+ end
+ it "compares tags" do
+ get v3_api(route, current_user), from: 'v1.0.0', to: 'v1.1.0'
+ expect(response).to have_http_status(200)
+ expect(json_response['commits']).to be_present
+ expect(json_response['diffs']).to be_present
+ end
+ it "compares commits" do
+ get v3_api(route, current_user), from: sample_commit.id, to: sample_commit.parent_id
+ expect(response).to have_http_status(200)
+ expect(json_response['commits']).to be_empty
+ expect(json_response['diffs']).to be_empty
+ expect(json_response['compare_same_ref']).to be_falsey
+ end
+ it "compares commits in reverse order" do
+ get v3_api(route, current_user), from: sample_commit.parent_id, to: sample_commit.id
+ expect(response).to have_http_status(200)
+ expect(json_response['commits']).to be_present
+ expect(json_response['diffs']).to be_present
+ end
+ it "compares same refs" do
+ get v3_api(route, current_user), from: 'master', to: 'master'
+ expect(response).to have_http_status(200)
+ expect(json_response['commits']).to be_empty
+ expect(json_response['diffs']).to be_empty
+ expect(json_response['compare_same_ref']).to be_truthy
+ end
+ end
+ context 'when unauthenticated', 'and project is public' do
+ it_behaves_like 'repository compare' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:current_user) { nil }
+ end
+ end
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get v3_api(route) }
+ let(:message) { '404 Project Not Found' }
+ end
+ end
+ context 'when authenticated', 'as a developer' do
+ it_behaves_like 'repository compare' do
+ let(:current_user) { user }
+ end
+ end
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:request) { get v3_api(route, guest) }
+ end
+ end
+ end
+
describe 'GET /projects/:id/repository/contributors' do
let(:route) { "/projects/#{project.id}/repository/contributors" }
diff --git a/spec/requests/api/v3/services_spec.rb b/spec/requests/api/v3/services_spec.rb
index 7e8c8753d02..3a760a8f25c 100644
--- a/spec/requests/api/v3/services_spec.rb
+++ b/spec/requests/api/v3/services_spec.rb
@@ -6,7 +6,9 @@ describe API::V3::Services, api: true do
let(:user) { create(:user) }
let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) }
- Service.available_services_names.each do |service|
+ available_services = Service.available_services_names
+ available_services.delete('prometheus')
+ available_services.each do |service|
describe "DELETE /projects/:id/services/#{service.dasherize}" do
include_context service
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index 87786e85621..006d6a6af1c 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -221,12 +221,20 @@ describe 'Git HTTP requests', lib: true do
end
context "when the user is blocked" do
- it "responds with status 404" do
+ it "responds with status 401" do
user.block
project.team << [user, :master]
download(path, env) do |response|
- expect(response).to have_http_status(404)
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ it "responds with status 401 for unknown projects (no project existence information leak)" do
+ user.block
+
+ download('doesnt/exist.git', env) do |response|
+ expect(response).to have_http_status(401)
end
end
end
diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb
new file mode 100644
index 00000000000..5206634bca5
--- /dev/null
+++ b/spec/requests/openid_connect_spec.rb
@@ -0,0 +1,134 @@
+require 'spec_helper'
+
+describe 'OpenID Connect requests' do
+ include ApiHelpers
+
+ let(:user) { create :user }
+ let(:access_grant) { create :oauth_access_grant, application: application, resource_owner_id: user.id }
+ let(:access_token) { create :oauth_access_token, application: application, resource_owner_id: user.id }
+
+ def request_access_token
+ login_as user
+
+ post '/oauth/token',
+ grant_type: 'authorization_code',
+ code: access_grant.token,
+ redirect_uri: application.redirect_uri,
+ client_id: application.uid,
+ client_secret: application.secret
+ end
+
+ def request_user_info
+ get '/oauth/userinfo', nil, 'Authorization' => "Bearer #{access_token.token}"
+ end
+
+ def hashed_subject
+ Digest::SHA256.hexdigest("#{user.id}-#{Rails.application.secrets.secret_key_base}")
+ end
+
+ context 'Application without OpenID scope' do
+ let(:application) { create :oauth_application, scopes: 'api' }
+
+ it 'token response does not include an ID token' do
+ request_access_token
+
+ expect(json_response).to include 'access_token'
+ expect(json_response).not_to include 'id_token'
+ end
+
+ it 'userinfo response is unauthorized' do
+ request_user_info
+
+ expect(response).to have_http_status 403
+ expect(response.body).to be_blank
+ end
+ end
+
+ context 'Application with OpenID scope' do
+ let(:application) { create :oauth_application, scopes: 'openid' }
+
+ it 'token response includes an ID token' do
+ request_access_token
+
+ expect(json_response).to include 'id_token'
+ end
+
+ context 'UserInfo payload' do
+ let(:user) do
+ create(
+ :user,
+ name: 'Alice',
+ username: 'alice',
+ emails: [private_email, public_email],
+ email: private_email.email,
+ public_email: public_email.email,
+ website_url: 'https://example.com',
+ avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png"),
+ )
+ end
+
+ let(:public_email) { build :email, email: 'public@example.com' }
+ let(:private_email) { build :email, email: 'private@example.com' }
+
+ it 'includes all user information' do
+ request_user_info
+
+ expect(json_response).to eq({
+ 'sub' => hashed_subject,
+ 'name' => 'Alice',
+ 'nickname' => 'alice',
+ 'email' => 'public@example.com',
+ 'email_verified' => true,
+ 'website' => 'https://example.com',
+ 'profile' => 'http://localhost/alice',
+ 'picture' => "http://localhost/uploads/user/avatar/#{user.id}/dk.png",
+ })
+ end
+ end
+
+ context 'ID token payload' do
+ before do
+ request_access_token
+ @payload = JSON::JWT.decode(json_response['id_token'], :skip_verification)
+ end
+
+ it 'includes the Gitlab root URL' do
+ expect(@payload['iss']).to eq Gitlab.config.gitlab.url
+ end
+
+ it 'includes the hashed user ID' do
+ expect(@payload['sub']).to eq hashed_subject
+ end
+
+ it 'includes the time of the last authentication' do
+ expect(@payload['auth_time']).to eq user.current_sign_in_at.to_i
+ end
+
+ it 'does not include any unknown properties' do
+ expect(@payload.keys).to eq %w[iss sub aud exp iat auth_time]
+ end
+ end
+
+ context 'when user is blocked' do
+ it 'returns authentication error' do
+ access_grant
+ user.block
+
+ expect do
+ request_access_token
+ end.to throw_symbol :warden
+ end
+ end
+
+ context 'when user is ldap_blocked' do
+ it 'returns authentication error' do
+ access_grant
+ user.ldap_block
+
+ expect do
+ request_access_token
+ end.to throw_symbol :warden
+ end
+ end
+ end
+end
diff --git a/spec/routing/openid_connect_spec.rb b/spec/routing/openid_connect_spec.rb
new file mode 100644
index 00000000000..2c3bc08f1a1
--- /dev/null
+++ b/spec/routing/openid_connect_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+# oauth_discovery_keys GET /oauth/discovery/keys(.:format) doorkeeper/openid_connect/discovery#keys
+# oauth_discovery_provider GET /.well-known/openid-configuration(.:format) doorkeeper/openid_connect/discovery#provider
+# oauth_discovery_webfinger GET /.well-known/webfinger(.:format) doorkeeper/openid_connect/discovery#webfinger
+describe Doorkeeper::OpenidConnect::DiscoveryController, 'routing' do
+ it "to #provider" do
+ expect(get('/.well-known/openid-configuration')).to route_to('doorkeeper/openid_connect/discovery#provider')
+ end
+
+ it "to #webfinger" do
+ expect(get('/.well-known/webfinger')).to route_to('doorkeeper/openid_connect/discovery#webfinger')
+ end
+
+ it "to #keys" do
+ expect(get('/oauth/discovery/keys')).to route_to('doorkeeper/openid_connect/discovery#keys')
+ end
+end
+
+# oauth_userinfo GET /oauth/userinfo(.:format) doorkeeper/openid_connect/userinfo#show
+# POST /oauth/userinfo(.:format) doorkeeper/openid_connect/userinfo#show
+describe Doorkeeper::OpenidConnect::UserinfoController, 'routing' do
+ it "to #show" do
+ expect(get('/oauth/userinfo')).to route_to('doorkeeper/openid_connect/userinfo#show')
+ end
+
+ it "to #show" do
+ expect(post('/oauth/userinfo')).to route_to('doorkeeper/openid_connect/userinfo#show')
+ end
+end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index d31f1bdfb7c..4baccacd448 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -120,7 +120,6 @@ describe 'project routing' do
end
end
- # emojis_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/emojis(.:format) projects/autocomplete_sources#emojis
# members_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/members(.:format) projects/autocomplete_sources#members
# issues_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/issues(.:format) projects/autocomplete_sources#issues
# merge_requests_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/merge_requests(.:format) projects/autocomplete_sources#merge_requests
@@ -128,7 +127,7 @@ describe 'project routing' do
# milestones_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/milestones(.:format) projects/autocomplete_sources#milestones
# commands_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/commands(.:format) projects/autocomplete_sources#commands
describe Projects::AutocompleteSourcesController, 'routing' do
- [:emojis, :members, :issues, :merge_requests, :labels, :milestones, :commands].each do |action|
+ [:members, :issues, :merge_requests, :labels, :milestones, :commands].each do |action|
it "to ##{action}" do
expect(get("/gitlab/gitlabhq/autocomplete_sources/#{action}")).to route_to("projects/autocomplete_sources##{action}", namespace_id: 'gitlab', project_id: 'gitlabhq')
end
diff --git a/spec/rubocop/cop/migration/add_concurrent_index_spec.rb b/spec/rubocop/cop/migration/add_concurrent_index_spec.rb
new file mode 100644
index 00000000000..19a5718b0b1
--- /dev/null
+++ b/spec/rubocop/cop/migration/add_concurrent_index_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../../rubocop/cop/migration/add_concurrent_index'
+
+describe RuboCop::Cop::Migration::AddConcurrentIndex do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'in migration' do
+ before do
+ allow(cop).to receive(:in_migration?).and_return(true)
+ end
+
+ it 'registers an offense when add_concurrent_index is used inside a change method' do
+ inspect_source(cop, 'def change; add_concurrent_index :table, :column; end')
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ end
+ end
+
+ it 'registers no offense when add_concurrent_index is used inside an up method' do
+ inspect_source(cop, 'def up; add_concurrent_index :table, :column; end')
+
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+
+ context 'outside of migration' do
+ it 'registers no offense' do
+ inspect_source(cop, 'def change; add_concurrent_index :table, :column; end')
+
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+end
diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb
index 305278843f5..01baedc4761 100644
--- a/spec/services/boards/issues/list_service_spec.rb
+++ b/spec/services/boards/issues/list_service_spec.rb
@@ -43,32 +43,6 @@ describe Boards::Issues::ListService, services: true do
described_class.new(project, user, params).execute
end
- context 'sets default order to priority' do
- it 'returns opened issues when list id is missing' do
- params = { board_id: board.id }
-
- issues = described_class.new(project, user, params).execute
-
- expect(issues).to eq [opened_issue2, reopened_issue1, opened_issue1]
- end
-
- it 'returns closed issues when listing issues from Done' do
- params = { board_id: board.id, id: done.id }
-
- issues = described_class.new(project, user, params).execute
-
- expect(issues).to eq [closed_issue4, closed_issue2, closed_issue3, closed_issue1]
- end
-
- it 'returns opened issues that have label list applied when listing issues from a label list' do
- params = { board_id: board.id, id: list1.id }
-
- issues = described_class.new(project, user, params).execute
-
- expect(issues).to eq [list1_issue3, list1_issue1, list1_issue2]
- end
- end
-
context 'with list that does not belong to the board' do
it 'raises an error' do
list = create(:list)
diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb
index 77f75167b3d..727ea04ea5c 100644
--- a/spec/services/boards/issues/move_service_spec.rb
+++ b/spec/services/boards/issues/move_service_spec.rb
@@ -78,8 +78,10 @@ describe Boards::Issues::MoveService, services: true do
end
context 'when moving to same list' do
- let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) }
- let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list1.id } }
+ let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) }
+ let(:issue1) { create(:labeled_issue, project: project, labels: [bug, development]) }
+ let(:issue2) { create(:labeled_issue, project: project, labels: [bug, development]) }
+ let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list1.id } }
it 'returns false' do
expect(described_class.new(project, user, params).execute(issue)).to eq false
@@ -90,6 +92,18 @@ describe Boards::Issues::MoveService, services: true do
expect(issue.reload.labels).to contain_exactly(bug, development)
end
+
+ it 'sorts issues' do
+ [issue, issue1, issue2].each do |issue|
+ issue.move_to_end && issue.save!
+ end
+
+ params.merge!(move_after_iid: issue1.iid, move_before_iid: issue2.iid)
+
+ described_class.new(project, user, params).execute(issue)
+
+ expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
+ end
end
end
end
diff --git a/spec/services/ci/register_build_service_spec.rb b/spec/services/ci/register_job_service_spec.rb
index cd7dd53025c..62ba0b01339 100644
--- a/spec/services/ci/register_build_service_spec.rb
+++ b/spec/services/ci/register_job_service_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
module Ci
- describe RegisterBuildService, services: true do
+ describe RegisterJobService, services: true do
let!(:project) { FactoryGirl.create :empty_project, shared_runners_enabled: false }
let!(:pipeline) { FactoryGirl.create :ci_pipeline, project: project }
let!(:pending_build) { FactoryGirl.create :ci_build, pipeline: pipeline }
@@ -181,7 +181,7 @@ module Ci
let!(:other_build) { create :ci_build, pipeline: pipeline }
before do
- allow_any_instance_of(Ci::RegisterBuildService).to receive(:builds_for_specific_runner)
+ allow_any_instance_of(Ci::RegisterJobService).to receive(:builds_for_specific_runner)
.and_return([pending_build, other_build])
end
@@ -193,7 +193,7 @@ module Ci
context 'when single build is in queue' do
before do
- allow_any_instance_of(Ci::RegisterBuildService).to receive(:builds_for_specific_runner)
+ allow_any_instance_of(Ci::RegisterJobService).to receive(:builds_for_specific_runner)
.and_return([pending_build])
end
@@ -204,7 +204,7 @@ module Ci
context 'when there is no build in queue' do
before do
- allow_any_instance_of(Ci::RegisterBuildService).to receive(:builds_for_specific_runner)
+ allow_any_instance_of(Ci::RegisterJobService).to receive(:builds_for_specific_runner)
.and_return([])
end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index 2a0f00ce937..bd71618e6f4 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -150,6 +150,13 @@ describe GitPushService, services: true do
execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
end
end
+
+ context "Sends System Push data" do
+ it "when pushing on a branch" do
+ expect(SystemHookPushWorker).to receive(:perform_async).with(@push_data, :push_hooks)
+ execute_service(project, user, @oldrev, @newrev, @ref )
+ end
+ end
end
describe "Updates git attributes" do
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index d83b09fd32c..fa472f3e2c3 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -58,6 +58,22 @@ describe Issues::UpdateService, services: true do
expect(issue.due_date).to eq Date.tomorrow
end
+ it 'sorts issues as specified by parameters' do
+ issue1 = create(:issue, project: project, assignee_id: user3.id)
+ issue2 = create(:issue, project: project, assignee_id: user3.id)
+
+ [issue, issue1, issue2].each do |issue|
+ issue.move_to_end
+ issue.save
+ end
+
+ opts[:move_between_iids] = [issue1.iid, issue2.iid]
+
+ update_issue(opts)
+
+ expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
+ end
+
context 'when current user cannot admin issues in the project' do
let(:guest) { create(:user) }
before do
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index fb9a8462f84..a8395cb48ea 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -752,7 +752,7 @@ describe TodoService, services: true do
issue = create(:issue, project: project, assignee: john_doe, author: author, description: mentions)
expect(john_doe.todos_pending_count).to eq(0)
- expect(john_doe).to receive(:update_todos_count_cache)
+ expect(john_doe).to receive(:update_todos_count_cache).and_call_original
service.new_issue(issue, author)
diff --git a/spec/support/api/time_tracking_shared_examples.rb b/spec/support/api/time_tracking_shared_examples.rb
index 210cd5817e0..16a3cf06be7 100644
--- a/spec/support/api/time_tracking_shared_examples.rb
+++ b/spec/support/api/time_tracking_shared_examples.rb
@@ -7,13 +7,13 @@ shared_examples 'time tracking endpoints' do |issuable_name|
describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/time_estimate" do
context 'with an unauthorized user' do
- subject { post(api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", non_member), duration: '1w') }
+ subject { post(api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", non_member), duration: '1w') }
it_behaves_like 'an unauthorized API user'
end
it "sets the time estimate for #{issuable_name}" do
- post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: '1w'
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), duration: '1w'
expect(response).to have_http_status(200)
expect(json_response['human_time_estimate']).to eq('1w')
@@ -21,12 +21,12 @@ shared_examples 'time tracking endpoints' do |issuable_name|
describe 'updating the current estimate' do
before do
- post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: '1w'
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), duration: '1w'
end
context 'when duration has a bad format' do
it 'does not modify the original estimate' do
- post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: 'foo'
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), duration: 'foo'
expect(response).to have_http_status(400)
expect(issuable.reload.human_time_estimate).to eq('1w')
@@ -35,7 +35,7 @@ shared_examples 'time tracking endpoints' do |issuable_name|
context 'with a valid duration' do
it 'updates the estimate' do
- post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: '3w1h'
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), duration: '3w1h'
expect(response).to have_http_status(200)
expect(issuable.reload.human_time_estimate).to eq('3w 1h')
@@ -46,13 +46,13 @@ shared_examples 'time tracking endpoints' do |issuable_name|
describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/reset_time_estimate" do
context 'with an unauthorized user' do
- subject { post(api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_time_estimate", non_member)) }
+ subject { post(api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/reset_time_estimate", non_member)) }
it_behaves_like 'an unauthorized API user'
end
it "resets the time estimate for #{issuable_name}" do
- post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_time_estimate", user)
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/reset_time_estimate", user)
expect(response).to have_http_status(200)
expect(json_response['time_estimate']).to eq(0)
@@ -62,7 +62,7 @@ shared_examples 'time tracking endpoints' do |issuable_name|
describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/add_spent_time" do
context 'with an unauthorized user' do
subject do
- post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", non_member),
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", non_member),
duration: '2h'
end
@@ -70,7 +70,7 @@ shared_examples 'time tracking endpoints' do |issuable_name|
end
it "add spent time for #{issuable_name}" do
- post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user),
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user),
duration: '2h'
expect(response).to have_http_status(201)
@@ -81,7 +81,7 @@ shared_examples 'time tracking endpoints' do |issuable_name|
it 'subtracts time of the total spent time' do
issuable.update_attributes!(spend_time: { duration: 7200, user: user })
- post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user),
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user),
duration: '-1h'
expect(response).to have_http_status(201)
@@ -93,7 +93,7 @@ shared_examples 'time tracking endpoints' do |issuable_name|
it 'does not modify the total time spent' do
issuable.update_attributes!(spend_time: { duration: 7200, user: user })
- post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user),
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user),
duration: '-1w'
expect(response).to have_http_status(400)
@@ -104,13 +104,13 @@ shared_examples 'time tracking endpoints' do |issuable_name|
describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/reset_spent_time" do
context 'with an unauthorized user' do
- subject { post(api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_spent_time", non_member)) }
+ subject { post(api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/reset_spent_time", non_member)) }
it_behaves_like 'an unauthorized API user'
end
it "resets spent time for #{issuable_name}" do
- post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_spent_time", user)
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/reset_spent_time", user)
expect(response).to have_http_status(200)
expect(json_response['total_time_spent']).to eq(0)
@@ -122,7 +122,7 @@ shared_examples 'time tracking endpoints' do |issuable_name|
issuable.update_attributes!(spend_time: { duration: 1800, user: user },
time_estimate: 3600)
- get api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_stats", user)
+ get api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_stats", user)
expect(response).to have_http_status(200)
expect(json_response['total_time_spent']).to eq(1800)
diff --git a/spec/support/api/v3/time_tracking_shared_examples.rb b/spec/support/api/v3/time_tracking_shared_examples.rb
new file mode 100644
index 00000000000..f982b10d999
--- /dev/null
+++ b/spec/support/api/v3/time_tracking_shared_examples.rb
@@ -0,0 +1,128 @@
+shared_examples 'V3 time tracking endpoints' do |issuable_name|
+ issuable_collection_name = issuable_name.pluralize
+
+ describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/time_estimate" do
+ context 'with an unauthorized user' do
+ subject { post(v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", non_member), duration: '1w') }
+
+ it_behaves_like 'an unauthorized API user'
+ end
+
+ it "sets the time estimate for #{issuable_name}" do
+ post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: '1w'
+
+ expect(response).to have_http_status(200)
+ expect(json_response['human_time_estimate']).to eq('1w')
+ end
+
+ describe 'updating the current estimate' do
+ before do
+ post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: '1w'
+ end
+
+ context 'when duration has a bad format' do
+ it 'does not modify the original estimate' do
+ post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: 'foo'
+
+ expect(response).to have_http_status(400)
+ expect(issuable.reload.human_time_estimate).to eq('1w')
+ end
+ end
+
+ context 'with a valid duration' do
+ it 'updates the estimate' do
+ post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: '3w1h'
+
+ expect(response).to have_http_status(200)
+ expect(issuable.reload.human_time_estimate).to eq('3w 1h')
+ end
+ end
+ end
+ end
+
+ describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/reset_time_estimate" do
+ context 'with an unauthorized user' do
+ subject { post(v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_time_estimate", non_member)) }
+
+ it_behaves_like 'an unauthorized API user'
+ end
+
+ it "resets the time estimate for #{issuable_name}" do
+ post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_time_estimate", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['time_estimate']).to eq(0)
+ end
+ end
+
+ describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/add_spent_time" do
+ context 'with an unauthorized user' do
+ subject do
+ post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", non_member),
+ duration: '2h'
+ end
+
+ it_behaves_like 'an unauthorized API user'
+ end
+
+ it "add spent time for #{issuable_name}" do
+ post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user),
+ duration: '2h'
+
+ expect(response).to have_http_status(201)
+ expect(json_response['human_total_time_spent']).to eq('2h')
+ end
+
+ context 'when subtracting time' do
+ it 'subtracts time of the total spent time' do
+ issuable.update_attributes!(spend_time: { duration: 7200, user: user })
+
+ post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user),
+ duration: '-1h'
+
+ expect(response).to have_http_status(201)
+ expect(json_response['total_time_spent']).to eq(3600)
+ end
+ end
+
+ context 'when time to subtract is greater than the total spent time' do
+ it 'does not modify the total time spent' do
+ issuable.update_attributes!(spend_time: { duration: 7200, user: user })
+
+ post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user),
+ duration: '-1w'
+
+ expect(response).to have_http_status(400)
+ expect(json_response['message']['time_spent'].first).to match(/exceeds the total time spent/)
+ end
+ end
+ end
+
+ describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/reset_spent_time" do
+ context 'with an unauthorized user' do
+ subject { post(v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_spent_time", non_member)) }
+
+ it_behaves_like 'an unauthorized API user'
+ end
+
+ it "resets spent time for #{issuable_name}" do
+ post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_spent_time", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['total_time_spent']).to eq(0)
+ end
+ end
+
+ describe "GET /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/time_stats" do
+ it "returns the time stats for #{issuable_name}" do
+ issuable.update_attributes!(spend_time: { duration: 1800, user: user },
+ time_estimate: 3600)
+
+ get v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_stats", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['total_time_spent']).to eq(1800)
+ expect(json_response['time_estimate']).to eq(3600)
+ end
+ end
+end
diff --git a/spec/support/filtered_search_helpers.rb b/spec/support/filtered_search_helpers.rb
index 58f6636e680..f21b85ec10b 100644
--- a/spec/support/filtered_search_helpers.rb
+++ b/spec/support/filtered_search_helpers.rb
@@ -3,16 +3,20 @@ module FilteredSearchHelpers
page.find('.filtered-search')
end
+ # Enables input to be set (similar to copy and paste)
def input_filtered_search(search_term, submit: true)
- filtered_search.set(search_term)
+ # Add an extra space to engage visual tokens
+ filtered_search.set("#{search_term} ")
if submit
filtered_search.send_keys(:enter)
end
end
+ # Enables input to be added character by character
def input_filtered_search_keys(search_term)
- filtered_search.send_keys(search_term)
+ # Add an extra space to engage visual tokens
+ filtered_search.send_keys("#{search_term} ")
filtered_search.send_keys(:enter)
end
@@ -34,4 +38,32 @@ module FilteredSearchHelpers
# This ensures the dropdown is shown
expect(find('#js-dropdown-label')).not_to have_css('.filter-dropdown-loading')
end
+
+ def expect_filtered_search_input_empty
+ expect(find('.filtered-search').value).to eq('')
+ end
+
+ # Iterates through each visual token inside
+ # .tokens-container to make sure the correct names and values are rendered
+ def expect_tokens(tokens)
+ page.find '.filtered-search-input-container .tokens-container' do
+ page.all(:css, '.tokens-container li').each_with_index do |el, index|
+ token_name = tokens[index][:name]
+ token_value = tokens[index][:value]
+
+ expect(el.find('.name')).to have_content(token_name)
+ if token_value
+ expect(el.find('.value')).to have_content(token_value)
+ end
+ end
+ end
+ end
+
+ def default_placeholder
+ 'Search or filter results...'
+ end
+
+ def get_filtered_search_placeholder
+ find('.filtered-search')['placeholder']
+ end
end
diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb
index 97b8b342eb2..bbbbaf4c5e8 100644
--- a/spec/support/matchers/markdown_matchers.rb
+++ b/spec/support/matchers/markdown_matchers.rb
@@ -26,10 +26,11 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
- expect(actual).to have_selector('img.emoji', count: 10)
+ expect(actual).to have_selector('gl-emoji', count: 10)
- image = actual.at_css('img.emoji')
- expect(image['src'].to_s).to start_with(Gitlab.config.gitlab.url + '/assets')
+ emoji_element = actual.at_css('gl-emoji')
+ expect(emoji_element['data-name'].to_s).not_to be_empty
+ expect(emoji_element['data-unicode-version'].to_s).not_to be_empty
end
end
diff --git a/spec/support/prometheus_helpers.rb b/spec/support/prometheus_helpers.rb
new file mode 100644
index 00000000000..a52d8f37d14
--- /dev/null
+++ b/spec/support/prometheus_helpers.rb
@@ -0,0 +1,117 @@
+module PrometheusHelpers
+ def prometheus_memory_query(environment_slug)
+ %{sum(container_memory_usage_bytes{container_name="app",environment="#{environment_slug}"})/1024/1024}
+ end
+
+ def prometheus_cpu_query(environment_slug)
+ %{sum(rate(container_cpu_usage_seconds_total{container_name="app",environment="#{environment_slug}"}[2m]))}
+ end
+
+ def prometheus_query_url(prometheus_query)
+ query = { query: prometheus_query }.to_query
+
+ "https://prometheus.example.com/api/v1/query?#{query}"
+ end
+
+ def prometheus_query_range_url(prometheus_query, start: 8.hours.ago)
+ query = {
+ query: prometheus_query,
+ start: start.to_f,
+ end: Time.now.utc.to_f,
+ step: 1.minute.to_i
+ }.to_query
+
+ "https://prometheus.example.com/api/v1/query_range?#{query}"
+ end
+
+ def stub_prometheus_request(url, body: {}, status: 200)
+ WebMock.stub_request(:get, url)
+ .to_return({
+ status: status,
+ headers: { 'Content-Type' => 'application/json' },
+ body: body.to_json
+ })
+ end
+
+ def stub_all_prometheus_requests(environment_slug, body: nil, status: 200)
+ stub_prometheus_request(
+ prometheus_query_url(prometheus_memory_query(environment_slug)),
+ status: status,
+ body: body || prometheus_value_body
+ )
+ stub_prometheus_request(
+ prometheus_query_range_url(prometheus_memory_query(environment_slug)),
+ status: status,
+ body: body || prometheus_values_body
+ )
+ stub_prometheus_request(
+ prometheus_query_url(prometheus_cpu_query(environment_slug)),
+ status: status,
+ body: body || prometheus_value_body
+ )
+ stub_prometheus_request(
+ prometheus_query_range_url(prometheus_cpu_query(environment_slug)),
+ status: status,
+ body: body || prometheus_values_body
+ )
+ end
+
+ def prometheus_data(last_update: Time.now.utc)
+ {
+ success: true,
+ metrics: {
+ memory_values: prometheus_values_body('matrix').dig(:data, :result),
+ memory_current: prometheus_value_body('vector').dig(:data, :result),
+ cpu_values: prometheus_values_body('matrix').dig(:data, :result),
+ cpu_current: prometheus_value_body('vector').dig(:data, :result)
+ },
+ last_update: last_update
+ }
+ end
+
+ def prometheus_empty_body(type)
+ {
+ "status": "success",
+ "data": {
+ "resultType": type,
+ "result": []
+ }
+ }
+ end
+
+ def prometheus_value_body(type = 'vector')
+ {
+ "status": "success",
+ "data": {
+ "resultType": type,
+ "result": [
+ {
+ "metric": {},
+ "value": [
+ 1488772511.004,
+ "0.000041021495238095323"
+ ]
+ }
+ ]
+ }
+ }
+ end
+
+ def prometheus_values_body(type = 'matrix')
+ {
+ "status": "success",
+ "data": {
+ "resultType": type,
+ "result": [
+ {
+ "metric": {},
+ "values": [
+ [1488758662.506, "0.00002996364761904785"],
+ [1488758722.506, "0.00003090239047619091"]
+ ]
+ }
+ ]
+ }
+ }
+ end
+end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index c3aa3ef44c2..f1d226b6ae3 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -143,7 +143,7 @@ module TestEnv
end
def repos_path
- Gitlab.config.repositories.storages.default
+ Gitlab.config.repositories.storages.default['path']
end
def backup_path
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index dfbfbd05f43..10458966cb9 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -227,8 +227,8 @@ describe 'gitlab:app namespace rake task' do
FileUtils.mkdir('tmp/tests/default_storage')
FileUtils.mkdir('tmp/tests/custom_storage')
storages = {
- 'default' => 'tmp/tests/default_storage',
- 'custom' => 'tmp/tests/custom_storage'
+ 'default' => { 'path' => 'tmp/tests/default_storage' },
+ 'custom' => { 'path' => 'tmp/tests/custom_storage' }
}
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
diff --git a/spec/tasks/gitlab/info_rake_spec.rb b/spec/tasks/gitlab/info_rake_spec.rb
new file mode 100644
index 00000000000..ca74378a12a
--- /dev/null
+++ b/spec/tasks/gitlab/info_rake_spec.rb
@@ -0,0 +1,37 @@
+require 'rake_helper'
+
+describe 'gitlab:env:info' do
+ before do
+ Rake.application.rake_require 'tasks/gitlab/info'
+
+ stub_warn_user_is_not_gitlab
+ allow(Gitlab::Popen).to receive(:popen)
+ end
+
+ describe 'git version' do
+ before do
+ allow(Gitlab::Popen).to receive(:popen).with([Gitlab.config.git.bin_path, '--version'])
+ .and_return(git_version)
+ end
+
+ context 'when git installed' do
+ let(:git_version) { 'git version 2.10.0' }
+
+ it 'prints git version' do
+ run_rake_task('gitlab:env:info')
+
+ expect($stdout.string).to match(/Git Version:(.*)2.10.0/)
+ end
+ end
+
+ context 'when git not installed' do
+ let(:git_version) { '' }
+
+ it 'prints unknown' do
+ run_rake_task('gitlab:env:info')
+
+ expect($stdout.string).to match(/Git Version:(.*)unknown/)
+ end
+ end
+ end
+end
diff --git a/spec/views/projects/commit/_commit_box.html.haml_spec.rb b/spec/views/projects/commit/_commit_box.html.haml_spec.rb
index e741e3cf9b6..f2919f20e85 100644
--- a/spec/views/projects/commit/_commit_box.html.haml_spec.rb
+++ b/spec/views/projects/commit/_commit_box.html.haml_spec.rb
@@ -3,11 +3,13 @@ require 'spec_helper'
describe 'projects/commit/_commit_box.html.haml' do
include Devise::Test::ControllerHelpers
+ let(:user) { create(:user) }
let(:project) { create(:project) }
before do
assign(:project, project)
assign(:commit, project.commit)
+ allow(view).to receive(:can_collaborate_with_project?).and_return(false)
end
it 'shows the commit SHA' do
@@ -25,4 +27,30 @@ describe 'projects/commit/_commit_box.html.haml' do
expect(rendered).to have_text("Pipeline ##{third_pipeline.id} for #{Commit.truncate_sha(project.commit.sha)} failed")
end
+
+ context 'viewing a commit' do
+ context 'as a developer' do
+ before do
+ expect(view).to receive(:can_collaborate_with_project?).and_return(true)
+ end
+
+ it 'has a link to create a new tag' do
+ render
+
+ expect(rendered).to have_link('Tag')
+ end
+ end
+
+ context 'as a non-developer' do
+ before do
+ expect(view).to receive(:can_collaborate_with_project?).and_return(false)
+ end
+
+ it 'does not have a link to create a new tag' do
+ render
+
+ expect(rendered).not_to have_link('Tag')
+ end
+ end
+ end
end
diff --git a/spec/views/projects/pipelines/_stage.html.haml_spec.rb b/spec/views/projects/pipelines/_stage.html.haml_spec.rb
index d25de8af5d2..65f9d0125e6 100644
--- a/spec/views/projects/pipelines/_stage.html.haml_spec.rb
+++ b/spec/views/projects/pipelines/_stage.html.haml_spec.rb
@@ -50,4 +50,23 @@ describe 'projects/pipelines/_stage', :view do
expect(rendered).to have_text 'test:build', count: 1
end
end
+
+ context 'when there are multiple builds' do
+ before do
+ HasStatus::AVAILABLE_STATUSES.each do |status|
+ create_build(status)
+ end
+ end
+
+ it 'shows them in order' do
+ render
+
+ expect(rendered).to have_text(HasStatus::ORDERED_STATUSES.join(" "))
+ end
+
+ def create_build(status)
+ create(:ci_build, name: status, status: status,
+ pipeline: pipeline, stage: stage.name)
+ end
+ end
end
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 5919b99a6ed..7bcb5521202 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -105,6 +105,6 @@ describe PostReceive do
end
def pwd(project)
- File.join(Gitlab.config.repositories.storages.default, project.path_with_namespace)
+ File.join(Gitlab.config.repositories.storages.default['path'], project.path_with_namespace)
end
end
diff --git a/spec/workers/system_hook_push_worker_spec.rb b/spec/workers/system_hook_push_worker_spec.rb
new file mode 100644
index 00000000000..b1d446ed25f
--- /dev/null
+++ b/spec/workers/system_hook_push_worker_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe SystemHookPushWorker do
+ include RepoHelpers
+
+ subject { described_class.new }
+
+ describe '#perform' do
+ it 'executes SystemHooksService with expected values' do
+ push_data = double('push_data')
+ system_hook_service = double('system_hook_service')
+
+ expect(SystemHooksService).to receive(:new).and_return(system_hook_service)
+ expect(system_hook_service).to receive(:execute_hooks).with(push_data, :push_hooks)
+
+ subject.perform(push_data, :push_hooks)
+ end
+ end
+end
diff --git a/spec/workers/update_merge_requests_worker_spec.rb b/spec/workers/update_merge_requests_worker_spec.rb
index c78a69eda67..262d6e5a9ab 100644
--- a/spec/workers/update_merge_requests_worker_spec.rb
+++ b/spec/workers/update_merge_requests_worker_spec.rb
@@ -23,16 +23,5 @@ describe UpdateMergeRequestsWorker do
perform
end
-
- it 'executes SystemHooksService with expected values' do
- push_data = double('push_data')
- expect(Gitlab::DataBuilder::Push).to receive(:build).with(project, user, oldrev, newrev, ref, []).and_return(push_data)
-
- system_hook_service = double('system_hook_service')
- expect(SystemHooksService).to receive(:new).and_return(system_hook_service)
- expect(system_hook_service).to receive(:execute_hooks).with(push_data, :push_hooks)
-
- perform
- end
end
end
diff --git a/yarn.lock b/yarn.lock
index 9d38799fc35..55b8f1566ee 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1395,6 +1395,10 @@ doctrine@1.5.0, doctrine@^1.2.2:
esutils "^2.0.2"
isarray "^1.0.0"
+document-register-element@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/document-register-element/-/document-register-element-1.3.0.tgz#fb3babb523c74662be47be19c6bc33e71990d940"
+
dom-serialize@^2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b"
@@ -1439,6 +1443,10 @@ elliptic@^6.0.0:
hash.js "^1.0.0"
inherits "^2.0.1"
+emoji-unicode-version@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/emoji-unicode-version/-/emoji-unicode-version-0.2.1.tgz#0ebf3666b5414097971d34994e299fce75cdbafc"
+
emojis-list@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
@@ -4115,6 +4123,14 @@ string-width@^2.0.0:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^3.0.0"
+string.fromcodepoint@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/string.fromcodepoint/-/string.fromcodepoint-0.2.1.tgz#8d978333c0bc92538f50f383e4888f3e5619d653"
+
+string.prototype.codepointat@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/string.prototype.codepointat/-/string.prototype.codepointat-0.2.0.tgz#6b26e9bd3afcaa7be3b4269b526de1b82000ac78"
+
string_decoder@^0.10.25, string_decoder@~0.10.x:
version "0.10.31"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"