Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.scss-lint.yml3
-rw-r--r--CHANGELOG52
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock8
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee8
-rw-r--r--app/assets/javascripts/gl_dropdown.js.coffee170
-rw-r--r--app/assets/javascripts/issuable_context.js.coffee25
-rw-r--r--app/assets/javascripts/issues.js.coffee10
-rw-r--r--app/assets/javascripts/labels_select.js.coffee154
-rw-r--r--app/assets/javascripts/lib/animate.js.coffee13
-rw-r--r--app/assets/javascripts/lib/notify.js.coffee30
-rw-r--r--app/assets/javascripts/merge_request_widget.js.coffee72
-rw-r--r--app/assets/javascripts/milestone_select.js.coffee103
-rw-r--r--app/assets/javascripts/right_sidebar.js.coffee55
-rw-r--r--app/assets/javascripts/search_autocomplete.js.coffee279
-rw-r--r--app/assets/javascripts/sidebar.js.coffee1
-rw-r--r--app/assets/javascripts/todos.js.coffee5
-rw-r--r--app/assets/javascripts/users_select.js.coffee129
-rw-r--r--app/assets/stylesheets/application.scss1
-rw-r--r--app/assets/stylesheets/behaviors.scss4
-rw-r--r--app/assets/stylesheets/framework/common.scss4
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss68
-rw-r--r--app/assets/stylesheets/framework/files.scss4
-rw-r--r--app/assets/stylesheets/framework/filters.scss2
-rw-r--r--app/assets/stylesheets/framework/forms.scss36
-rw-r--r--app/assets/stylesheets/framework/gitlab-theme.scss6
-rw-r--r--app/assets/stylesheets/framework/header.scss32
-rw-r--r--app/assets/stylesheets/framework/mobile.scss4
-rw-r--r--app/assets/stylesheets/framework/nav.scss3
-rw-r--r--app/assets/stylesheets/framework/selects.scss2
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss131
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap_variables.scss4
-rw-r--r--app/assets/stylesheets/framework/variables.scss30
-rw-r--r--app/assets/stylesheets/pages/awards.scss2
-rw-r--r--app/assets/stylesheets/pages/ci_projects.scss2
-rw-r--r--app/assets/stylesheets/pages/commit.scss8
-rw-r--r--app/assets/stylesheets/pages/commits.scss3
-rw-r--r--app/assets/stylesheets/pages/editor.scss2
-rw-r--r--app/assets/stylesheets/pages/events.scss4
-rw-r--r--app/assets/stylesheets/pages/issuable.scss21
-rw-r--r--app/assets/stylesheets/pages/lint.scss4
-rw-r--r--app/assets/stylesheets/pages/login.scss2
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss6
-rw-r--r--app/assets/stylesheets/pages/note_form.scss2
-rw-r--r--app/assets/stylesheets/pages/notes.scss135
-rw-r--r--app/assets/stylesheets/pages/projects.scss4
-rw-r--r--app/assets/stylesheets/pages/search.scss142
-rw-r--r--app/assets/stylesheets/pages/todos.scss8
-rw-r--r--app/controllers/admin/application_settings_controller.rb1
-rw-r--r--app/controllers/ci/projects_controller.rb10
-rw-r--r--app/controllers/projects/badges_controller.rb5
-rw-r--r--app/controllers/projects/issues_controller.rb13
-rw-r--r--app/controllers/projects/merge_requests_controller.rb25
-rw-r--r--app/controllers/projects/milestones_controller.rb3
-rw-r--r--app/controllers/projects/snippets_controller.rb6
-rw-r--r--app/controllers/projects_controller.rb15
-rw-r--r--app/finders/issuable_finder.rb4
-rw-r--r--app/helpers/application_settings_helper.rb4
-rw-r--r--app/helpers/dropdowns_helper.rb2
-rw-r--r--app/helpers/events_helper.rb8
-rw-r--r--app/helpers/issuables_helper.rb18
-rw-r--r--app/helpers/notes_helper.rb6
-rw-r--r--app/helpers/search_helper.rb57
-rw-r--r--app/mailers/notify.rb4
-rw-r--r--app/models/ability.rb56
-rw-r--r--app/models/application_setting.rb2
-rw-r--r--app/models/commit.rb4
-rw-r--r--app/models/commit_range.rb4
-rw-r--r--app/models/concerns/issuable.rb3
-rw-r--r--app/models/external_issue.rb2
-rw-r--r--app/models/global_milestone.rb1
-rw-r--r--app/models/issue.rb4
-rw-r--r--app/models/label.rb2
-rw-r--r--app/models/merge_request.rb15
-rw-r--r--app/models/milestone.rb4
-rw-r--r--app/models/note.rb2
-rw-r--r--app/models/project.rb16
-rw-r--r--app/models/project_services/gitlab_issue_tracker_service.rb2
-rw-r--r--app/models/project_services/jira_service.rb2
-rw-r--r--app/models/project_services/slack_service/issue_message.rb2
-rw-r--r--app/models/repository.rb20
-rw-r--r--app/models/snippet.rb4
-rw-r--r--app/models/user.rb2
-rw-r--r--app/services/issues/move_service.rb28
-rw-r--r--app/services/projects/unlink_fork_service.rb19
-rw-r--r--app/services/system_hooks_service.rb22
-rw-r--r--app/services/system_note_service.rb2
-rw-r--r--app/services/todo_service.rb26
-rw-r--r--app/uploaders/file_uploader.rb17
-rw-r--r--app/views/admin/application_settings/_form.html.haml7
-rw-r--r--app/views/ci/projects/index.html.haml20
-rw-r--r--app/views/dashboard/todos/_todo.html.haml7
-rw-r--r--app/views/events/_event.html.haml2
-rw-r--r--app/views/events/event/_created_project.html.haml18
-rw-r--r--app/views/layouts/_collapse_button.html.haml4
-rw-r--r--app/views/layouts/_page.html.haml18
-rw-r--r--app/views/layouts/_search.html.haml40
-rw-r--r--app/views/layouts/nav/_admin.html.haml2
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml5
-rw-r--r--app/views/layouts/nav/_group.html.haml10
-rw-r--r--app/views/layouts/nav/_profile.html.haml8
-rw-r--r--app/views/layouts/nav/_project.html.haml17
-rw-r--r--app/views/profiles/notifications/show.html.haml33
-rw-r--r--app/views/projects/buttons/_dropdown.html.haml17
-rw-r--r--app/views/projects/buttons/_fork.html.haml2
-rw-r--r--app/views/projects/diffs/_image.html.haml7
-rw-r--r--app/views/projects/find_file/show.html.haml2
-rw-r--r--app/views/projects/merge_requests/_show.html.haml2
-rw-r--r--app/views/projects/merge_requests/widget/_heading.html.haml24
-rw-r--r--app/views/projects/merge_requests/widget/_show.html.haml17
-rw-r--r--app/views/projects/notes/_note.html.haml29
-rw-r--r--app/views/projects/notes/discussions/_active.html.haml16
-rw-r--r--app/views/projects/notes/discussions/_commit.html.haml16
-rw-r--r--app/views/projects/notes/discussions/_outdated.html.haml15
-rw-r--r--app/views/projects/tree/_tree_header.html.haml4
-rw-r--r--app/views/search/results/_note.html.haml18
-rw-r--r--app/views/shared/issuable/_filter.html.haml14
-rw-r--r--app/views/shared/issuable/_form.html.haml4
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/_milestone_dropdown.html.haml6
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml51
-rw-r--r--app/workers/project_cache_worker.rb3
-rw-r--r--app/workers/project_destroy_worker.rb2
-rw-r--r--config/gitlab.yml.example2
-rw-r--r--config/initializers/1_settings.rb1
-rw-r--r--config/mail_room.yml2
-rw-r--r--db/migrate/20130218141258_convert_closed_to_state_in_issue.rb18
-rw-r--r--db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb22
-rw-r--r--db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb18
-rw-r--r--db/migrate/20130220125544_convert_merge_status_in_merge_request.rb22
-rw-r--r--db/migrate/20130419190306_allow_merges_for_forks.rb8
-rw-r--r--db/migrate/20160324020319_remove_todos_for_deleted_issues.rb17
-rw-r--r--db/migrate/20160329144452_add_index_on_pending_delete_projects.rb6
-rw-r--r--db/migrate/20160331133914_remove_todos_for_deleted_merge_requests.rb17
-rw-r--r--db/migrate/20160331223143_remove_twitter_sharing_enabled_from_application_settings.rb5
-rw-r--r--db/schema.rb5
-rw-r--r--doc/README.md4
-rw-r--r--doc/administration/auth/README.md11
-rw-r--r--doc/administration/auth/ldap.md277
-rw-r--r--doc/api/issues.md1
-rw-r--r--doc/api/labels.md71
-rw-r--r--doc/api/projects.md166
-rw-r--r--doc/api/settings.md3
-rw-r--r--doc/incoming_email/README.md108
-rw-r--r--doc/install/installation.md4
-rw-r--r--doc/integration/github.md2
-rw-r--r--doc/integration/ldap.md227
-rw-r--r--doc/update/8.5-to-8.6.md42
-rw-r--r--doc/update/8.6-to-8.7.md146
-rw-r--r--features/dashboard/dashboard.feature8
-rw-r--r--features/dashboard/todos.feature5
-rw-r--r--features/groups.feature4
-rw-r--r--features/project/project.feature9
-rw-r--r--features/steps/dashboard/dashboard.rb19
-rw-r--r--features/steps/dashboard/issues.rb9
-rw-r--r--features/steps/dashboard/todos.rb8
-rw-r--r--features/steps/group/milestones.rb4
-rw-r--r--features/steps/groups.rb4
-rw-r--r--features/steps/project/active_tab.rb4
-rw-r--r--features/steps/project/fork.rb2
-rw-r--r--features/steps/project/merge_requests.rb8
-rw-r--r--features/steps/project/project.rb12
-rw-r--r--features/steps/shared/project_tab.rb2
-rw-r--r--fixtures/emojis/digests.json8597
-rw-r--r--lib/api/entities.rb7
-rw-r--r--lib/api/issues.rb16
-rw-r--r--lib/api/labels.rb24
-rw-r--r--lib/api/projects.rb28
-rw-r--r--lib/award_emoji.rb19
-rw-r--r--lib/banzai/filter.rb2
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb140
-rw-r--r--lib/banzai/filter/autolink_filter.rb1
-rw-r--r--lib/banzai/filter/commit_range_reference_filter.rb2
-rw-r--r--lib/banzai/filter/commit_reference_filter.rb2
-rw-r--r--lib/banzai/filter/emoji_filter.rb4
-rw-r--r--lib/banzai/filter/external_issue_reference_filter.rb41
-rw-r--r--lib/banzai/filter/external_link_filter.rb2
-rw-r--r--lib/banzai/filter/gollum_tags_filter.rb3
-rw-r--r--lib/banzai/filter/label_reference_filter.rb2
-rw-r--r--lib/banzai/filter/markdown_filter.rb2
-rw-r--r--lib/banzai/filter/merge_request_reference_filter.rb2
-rw-r--r--lib/banzai/filter/milestone_reference_filter.rb4
-rw-r--r--lib/banzai/filter/redactor_filter.rb2
-rw-r--r--lib/banzai/filter/reference_filter.rb151
-rw-r--r--lib/banzai/filter/reference_gatherer_filter.rb2
-rw-r--r--lib/banzai/filter/relative_link_filter.rb1
-rw-r--r--lib/banzai/filter/sanitization_filter.rb3
-rw-r--r--lib/banzai/filter/snippet_reference_filter.rb2
-rw-r--r--lib/banzai/filter/syntax_highlight_filter.rb1
-rw-r--r--lib/banzai/filter/table_of_contents_filter.rb2
-rw-r--r--lib/banzai/filter/upload_link_filter.rb1
-rw-r--r--lib/banzai/filter/user_reference_filter.rb27
-rw-r--r--lib/banzai/filter/yaml_front_matter_filter.rb3
-rw-r--r--lib/banzai/pipeline/base_pipeline.rb2
-rw-r--r--lib/banzai/pipeline/wiki_pipeline.rb2
-rw-r--r--lib/gitlab/badge/build.rb24
-rw-r--r--lib/gitlab/current_settings.rb1
-rw-r--r--lib/gitlab/email/message/repository_push.rb2
-rw-r--r--lib/gitlab/email/receiver.rb15
-rw-r--r--lib/gitlab/fogbugz_import/client.rb2
-rw-r--r--lib/gitlab/gfm/reference_rewriter.rb9
-rw-r--r--lib/gitlab/gfm/uploads_rewriter.rb51
-rw-r--r--lib/gitlab/incoming_email.rb16
-rw-r--r--lib/gitlab/note_data_builder.rb2
-rw-r--r--lib/gitlab/routing.rb13
-rw-r--r--lib/gitlab/url_builder.rb16
-rw-r--r--lib/tasks/gemojione.rake48
-rw-r--r--lib/tasks/gitlab/check.rake15
-rw-r--r--spec/controllers/admin/users_controller_spec.rb15
-rw-r--r--spec/controllers/ci/projects_controller_spec.rb21
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb2
-rw-r--r--spec/controllers/projects/snippets_controller_spec.rb107
-rw-r--r--spec/factories/file_uploader.rb20
-rw-r--r--spec/factories_spec.rb16
-rw-r--r--spec/features/issues/filter_by_milestone_spec.rb42
-rw-r--r--spec/features/issues_spec.rb77
-rw-r--r--spec/features/merge_requests/create_new_mr_spec.rb28
-rw-r--r--spec/features/merge_requests/filter_by_milestone_spec.rb38
-rw-r--r--spec/features/search_spec.rb43
-rw-r--r--spec/features/security/project/snippet/internal_access_spec.rb78
-rw-r--r--spec/features/security/project/snippet/private_access_spec.rb63
-rw-r--r--spec/features/security/project/snippet/public_access_spec.rb93
-rw-r--r--spec/fixtures/emails/reply_without_subaddressing_and_key_inside_references.eml42
-rw-r--r--spec/fixtures/emails/valid_reply.eml4
-rw-r--r--spec/lib/award_emoji_spec.rb19
-rw-r--r--spec/lib/extracts_path_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/build_spec.rb72
-rw-r--r--spec/lib/gitlab/closing_issue_extractor_spec.rb2
-rw-r--r--spec/lib/gitlab/email/receiver_spec.rb23
-rw-r--r--spec/lib/gitlab/fogbugz_import/client_spec.rb24
-rw-r--r--spec/lib/gitlab/gfm/uploads_rewriter_spec.rb66
-rw-r--r--spec/lib/gitlab/incoming_email_spec.rb26
-rw-r--r--spec/mailers/notify_spec.rb66
-rw-r--r--spec/mailers/shared/notify.rb76
-rw-r--r--spec/models/application_setting_spec.rb1
-rw-r--r--spec/models/concerns/issuable_spec.rb1
-rw-r--r--spec/models/hooks/system_hook_spec.rb54
-rw-r--r--spec/models/merge_request_spec.rb8
-rw-r--r--spec/models/project_services/slack_service/issue_message_spec.rb10
-rw-r--r--spec/models/project_spec.rb9
-rw-r--r--spec/models/repository_spec.rb27
-rw-r--r--spec/models/user_spec.rb7
-rw-r--r--spec/requests/api/issues_spec.rb11
-rw-r--r--spec/requests/api/labels_spec.rb29
-rw-r--r--spec/requests/api/merge_requests_spec.rb11
-rw-r--r--spec/requests/api/projects_spec.rb72
-rw-r--r--spec/services/issues/move_service_spec.rb14
-rw-r--r--spec/services/projects/unlink_fork_service_spec.rb32
-rw-r--r--spec/services/todo_service_spec.rb77
-rw-r--r--spec/support/carrierwave.rb7
-rw-r--r--spec/support/filter_spec_helper.rb2
-rw-r--r--spec/support/markdown_feature.rb2
-rw-r--r--spec/support/test_env.rb1
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb4
-rw-r--r--spec/workers/merge_worker_spec.rb2
-rw-r--r--spec/workers/project_cache_worker_spec.rb27
-rw-r--r--vendor/assets/stylesheets/animate.css11
258 files changed, 13143 insertions, 1636 deletions
diff --git a/.scss-lint.yml b/.scss-lint.yml
index 937d3407b60..3ce0c4901bd 100644
--- a/.scss-lint.yml
+++ b/.scss-lint.yml
@@ -132,8 +132,9 @@ linters:
SpaceAroundOperator:
enabled: false
+ # Opening braces should be preceded by a single space.
SpaceBeforeBrace:
- enabled: false
+ enabled: true
StringQuotes:
enabled: false
diff --git a/CHANGELOG b/CHANGELOG
index 7e9a447a8f6..f72bb670ece 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,16 +1,61 @@
Please view this file on the master branch, on stable branches it's out of date.
v 8.7.0 (unreleased)
+ - Improved Markdown rendering performance !3389 (Yorick Peterse)
- Don't attempt to look up an avatar in repo if repo directory does not exist (Stan hu)
- Preserve time notes/comments have been updated at when moving issue
- Make HTTP(s) label consistent on clone bar (Stan Hu)
+ - Expose label description in API (Mariusz Jachimowicz)
+ - Allow back dating on issues when created through the API
- Fix avatar stretching by providing a cropping feature
+ - Add endpoints to archive or unarchive a project !3372
- Add links to CI setup documentation from project settings and builds pages
+ - Handle nil descriptions in Slack issue messages (Stan Hu)
+ - Add default scope to projects to exclude projects pending deletion
- Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.)
- Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.)
-
-v 8.6.2 (unreleased)
- - Comments on confidential issues don't show up in activity feed to non-members
+ - Gracefully handle notes on deleted commits in merge requests (Stan Hu)
+ - Fix creation of merge requests for orphaned branches (Stan Hu)
+ - Fall back to `In-Reply-To` and `References` headers when sub-addressing is not available (David Padilla)
+ - Remove "Congratulations!" tweet button on newly-created project. (Connor Shea)
+
+v 8.6.4
+ - Don't attempt to fetch any tags from a forked repo (Stan Hu)
+
+v 8.6.3
+ - Mentions on confidential issues doesn't create todos for non-members. !3374
+ - Destroy related todos when an Issue/MR is deleted. !3376
+ - Fix error 500 when target is nil on todo list. !3376
+ - Fix copying uploads when moving issue to another project. !3382
+ - Ensuring Merge Request API returns boolean values for work_in_progress (Abhi Rao). !3432
+ - Fix raw/rendered diff producing different results on merge requests. !3450
+ - Fix commit comment alignment (Stan Hu). !3466
+ - Fix Error 500 when searching for a comment in a project snippet. !3468
+ - Allow temporary email as notification email. !3477
+ - Fix issue with dropdowns not selecting values. !3478
+ - Update gitlab-shell version and doc to 2.6.12. gitlab-org/gitlab-ee!280
+
+v 8.6.2
+ - Fix dropdown alignment. !3298
+ - Fix issuable sidebar overlaps on tablet. !3299
+ - Make dropdowns pixel perfect. !3337
+ - Fix order of steps to prevent PostgreSQL errors when running migration. !3355
+ - Fix bold text in issuable sidebar. !3358
+ - Fix error with anonymous token in applications settings. !3362
+ - Fix the milestone 'upcoming' filter. !3364 + !3368
+ - Fix comments on confidential issues showing up in activity feed to non-members. !3375
+ - Fix `NoMethodError` when visiting CI root path at `/ci`. !3377
+ - Add a tooltip to new branch button in issue page. !3380
+ - Fix an issue hiding the password form when signed-in with a linked account. !3381
+ - Add links to CI setup documentation from project settings and builds pages. !3384
+ - Fix an issue with width of project select dropdown. !3386
+ - Remove redundant `require`s from Banzai files. !3391
+ - Fix error 500 with cancel button on issuable edit form. !3392 + !3417
+ - Fix background when editing a highlighted note. !3423
+ - Remove tabstop from the WIP toggle links. !3426
+ - Ensure private project snippets are not viewable by unauthorized people.
+ - Gracefully handle notes on deleted commits in merge requests (Stan Hu). !3402
+ - Fixed issue with notification settings not saving. !3452
v 8.6.1
- Add option to reload the schema before restoring a database backup. !2807
@@ -83,6 +128,7 @@ v 8.6.0
- Add main language of a project in the list of projects (Tiago Botelho)
- Add #upcoming filter to Milestone filter (Tiago Botelho)
- Add ability to show archived projects on dashboard, explore and group pages
+ - Remove fork link closes all merge requests opened on source project (Florent Baldino)
- Move group activity to separate page
- Create external users which are excluded of internal and private projects unless access was explicitly granted
- Continue parameters are checked to ensure redirection goes to the same instance
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index bc02b8685c1..24ba9a38de6 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-2.6.11
+2.7.0
diff --git a/Gemfile b/Gemfile
index 006e53e0c10..6327227282a 100644
--- a/Gemfile
+++ b/Gemfile
@@ -214,7 +214,7 @@ gem 'jquery-rails', '~> 4.0.0'
gem 'jquery-scrollto-rails', '~> 1.4.3'
gem 'jquery-ui-rails', '~> 5.0.0'
gem 'raphael-rails', '~> 2.1.2'
-gem 'request_store', '~> 1.2.0'
+gem 'request_store', '~> 1.3.0'
gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1'
gem 'net-ssh', '~> 3.0.1'
diff --git a/Gemfile.lock b/Gemfile.lock
index da27c62acbf..229089f431d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -126,9 +126,9 @@ GEM
coderay (1.1.0)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
- coffee-rails (4.1.0)
+ coffee-rails (4.1.1)
coffee-script (>= 2.2.0)
- railties (>= 4.0.0, < 5.0)
+ railties (>= 4.0.0, < 5.1.x)
coffee-script (2.4.1)
coffee-script-source
execjs
@@ -652,7 +652,7 @@ GEM
redis-store (~> 1.1.0)
redis-store (1.1.7)
redis (>= 2.2)
- request_store (1.2.1)
+ request_store (1.3.0)
rerun (0.11.0)
listen (~> 3.0)
responders (2.1.1)
@@ -1011,7 +1011,7 @@ DEPENDENCIES
redcarpet (~> 3.3.3)
redis-namespace
redis-rails (~> 4.0.0)
- request_store (~> 1.2.0)
+ request_store (~> 1.3.0)
rerun (~> 0.11.0)
responders (~> 2.0)
rouge (~> 1.10.1)
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index f5e1ca9860d..70fd6f50e9c 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -146,15 +146,11 @@ class Dispatcher
when 'project_members', 'deploy_keys', 'hooks', 'services', 'protected_branches'
shortcut_handler = new ShortcutsNavigation()
-
# If we haven't installed a custom shortcut handler, install the default one
if not shortcut_handler
new Shortcuts()
initSearch: ->
- opts = $('.search-autocomplete-opts')
- path = opts.data('autocomplete-path')
- project_id = opts.data('autocomplete-project-id')
- project_ref = opts.data('autocomplete-project-ref')
- new SearchAutocomplete(path, project_id, project_ref)
+ # Only when search form is present
+ new SearchAutocomplete() if $('.search').length
diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee
index 4b78bcde774..4f032a82e58 100644
--- a/app/assets/javascripts/gl_dropdown.js.coffee
+++ b/app/assets/javascripts/gl_dropdown.js.coffee
@@ -3,6 +3,10 @@ class GitLabDropdownFilter
HAS_VALUE_CLASS = "has-value"
constructor: (@input, @options) ->
+ {
+ @filterInputBlur = true
+ } = @options
+
$inputContainer = @input.parent()
$clearButton = $inputContainer.find('.js-dropdown-input-clear')
@@ -33,7 +37,7 @@ class GitLabDropdownFilter
blur_field = @shouldBlur e.keyCode
search_text = @input.val()
- if blur_field
+ if blur_field and @filterInputBlur
@input.blur()
if @options.remote
@@ -93,27 +97,48 @@ class GitLabDropdown
PAGE_TWO_CLASS = "is-page-two"
ACTIVE_CLASS = "is-active"
+ FILTER_INPUT = '.dropdown-input .dropdown-input-field'
+
constructor: (@el, @options) ->
- self = @
@dropdown = $(@el).parent()
+
+ # Set Defaults
+ {
+ # If no input is passed create a default one
+ @filterInput = @getElement(FILTER_INPUT)
+ @highlight = false
+ @filterInputBlur = true
+ @enterCallback = true
+ } = @options
+
+ self = @
+
+ # If selector was passed
+ if _.isString(@filterInput)
+ @filterInput = @getElement(@filterInput)
+
search_fields = if @options.search then @options.search.fields else [];
if @options.data
- # Remote data
- @remote = new GitLabDropdownRemote @options.data, {
- dataType: @options.dataType,
- beforeSend: @toggleLoading.bind(@)
- success: (data) =>
- @fullData = data
+ # If data is an array
+ if _.isArray @options.data
+ @fullData = @options.data
+ @parseData @options.data
+ else
+ # Remote data
+ @remote = new GitLabDropdownRemote @options.data, {
+ dataType: @options.dataType,
+ beforeSend: @toggleLoading.bind(@)
+ success: (data) =>
+ @fullData = data
- @parseData @fullData
- }
+ @parseData @fullData
+ }
- # Init filiterable
+ # Init filterable
if @options.filterable
- @input = @dropdown.find('.dropdown-input .dropdown-input-field')
-
- @filter = new GitLabDropdownFilter @input,
+ @filter = new GitLabDropdownFilter @filterInput,
+ filterInputBlur: @filterInputBlur
remote: @options.filterRemote
query: @options.data
keys: @options.search.fields
@@ -123,11 +148,14 @@ class GitLabDropdown
@parseData data
@highlightRow 1
enterCallback: =>
- @selectFirstRow()
+ if @enterCallback
+ @selectFirstRow()
# Event listeners
+
@dropdown.on "shown.bs.dropdown", @opened
@dropdown.on "hidden.bs.dropdown", @hidden
+ @dropdown.on "click", ".dropdown-menu, .dropdown-menu-close", @shouldPropagate
if @dropdown.find(".dropdown-toggle-page").length
@dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on "click", (e) =>
@@ -143,10 +171,14 @@ class GitLabDropdown
selector = ".dropdown-page-one .dropdown-content a"
@dropdown.on "click", selector, (e) ->
- self.rowClicked $(@)
+ selected = self.rowClicked $(@)
if self.options.clicked
- self.options.clicked()
+ self.options.clicked(selected)
+
+ # Finds an element inside wrapper element
+ getElement: (selector) ->
+ @dropdown.find selector
toggleLoading: ->
$('.dropdown-menu', @dropdown).toggleClass LOADING_CLASS
@@ -176,15 +208,26 @@ class GitLabDropdown
@appendMenu(full_html)
+ shouldPropagate: (e) =>
+ if @options.multiSelect
+ $target = $(e.target)
+ if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon')
+ e.stopPropagation()
+ return false
+ else
+ return true
+
opened: =>
contentHtml = $('.dropdown-content', @dropdown).html()
if @remote && contentHtml is ""
@remote.execute()
if @options.filterable
- @dropdown.find(".dropdown-input-field").focus()
+ @filterInput.focus()
- hidden: =>
+ @dropdown.trigger('shown.gl.dropdown')
+
+ hidden: (e) =>
if @options.filterable
@dropdown
.find(".dropdown-input-field")
@@ -195,6 +238,11 @@ class GitLabDropdown
if @dropdown.find(".dropdown-toggle-page").length
$('.dropdown-menu', @dropdown).removeClass PAGE_TWO_CLASS
+ if @options.hidden
+ @options.hidden.call(@,e)
+
+ @dropdown.trigger('hidden.gl.dropdown')
+
# Render the full menu
renderMenu: (html) ->
@@ -219,20 +267,46 @@ class GitLabDropdown
renderItem: (data) ->
html = ""
+ # Divider
return "<li class='divider'></li>" if data is "divider"
+ # Separator is a full-width divider
+ return "<li class='separator'></li>" if data is "separator"
+
+ # Header
+ return "<li class='dropdown-header'>#{data.header}</li>" if data.header?
+
if @options.renderRow
# Call the render function
html = @options.renderRow(data)
else
- selected = if @options.isSelected then @options.isSelected(data) else false
- url = if @options.url then @options.url(data) else "#"
- text = if @options.text then @options.text(data) else ""
+ if not selected
+ value = if @options.id then @options.id(data) else data.id
+ fieldName = @options.fieldName
+ field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
+ if field.length
+ selected = true
+
+ # Set URL
+ if @options.url?
+ url = @options.url(data)
+ else
+ url = if data.url? then data.url else '#'
+
+ # Set Text
+ if @options.text?
+ text = @options.text(data)
+ else
+ text = if data.text? then data.text else ''
+
cssClass = "";
if selected
cssClass = "is-active"
+ if @highlight
+ text = @highlightTextMatches(text, @filterInput.val())
+
html = "<li>"
html += "<a href='#{url}' class='#{cssClass}'>"
html += text
@@ -241,58 +315,68 @@ class GitLabDropdown
return html
+ highlightTextMatches: (text, term) ->
+ occurrences = fuzzaldrinPlus.match(text, term)
+ text.split('').map((character, i) ->
+ if i in occurrences then "<b>#{character}</b>" else character
+ ).join('')
+
noResults: ->
html = "<li>"
- html += "<a href='#' class='dropdown-menu-empty-link is-focused'>"
+ html += "<a class='dropdown-menu-empty-link is-focused'>"
html += "No matching results."
html += "</a>"
html += "</li>"
highlightRow: (index) ->
- if @input.val() isnt ""
+ if @filterInput.val() isnt ""
selector = '.dropdown-content li:first-child a'
if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one .dropdown-content li:first-child a"
- $(selector).addClass 'is-focused'
+ @getElement(selector).addClass 'is-focused'
rowClicked: (el) ->
fieldName = @options.fieldName
- field = @dropdown.parent().find("input[name='#{fieldName}']")
+ selectedIndex = el.parent().index()
+ if @renderedData
+ selectedObject = @renderedData[selectedIndex]
+ value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
+ field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
if el.hasClass(ACTIVE_CLASS)
+ el.removeClass(ACTIVE_CLASS)
field.remove()
- else
- fieldName = @options.fieldName
- selectedIndex = el.parent().index()
- if @renderedData
- selectedObject = @renderedData[selectedIndex]
- value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
+ # Toggle the dropdown label
+ if @options.toggleLabel
+ $(@el).find(".dropdown-toggle-text").text @options.toggleLabel
+ else
if !value?
field.remove()
- if @options.multiSelect
- oldValue = field.val()
- if oldValue
- value = "#{oldValue},#{value}"
- else
+ if not @options.multiSelect
@dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS
+ @dropdown.parent().find("input[name='#{fieldName}']").remove()
# Toggle active class for the tick mark
- el.toggleClass "is-active"
+ el.addClass ACTIVE_CLASS
# Toggle the dropdown label
if @options.toggleLabel
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject)
-
if value?
if !field.length
# Create hidden input for form
- input = "<input type='hidden' name='#{fieldName}' />"
+ input = "<input type='hidden' name='#{fieldName}' value='#{value}' />"
+ if @options.inputId?
+ input = $(input)
+ .attr('id', @options.inputId)
@dropdown.before input
+ else
+ field.val value
- @dropdown.parent().find("input[name='#{fieldName}']").val value
+ return selectedObject
selectFirstRow: ->
selector = '.dropdown-content li:first-child a'
@@ -304,4 +388,6 @@ class GitLabDropdown
$.fn.glDropdown = (opts) ->
return @.each ->
- new GitLabDropdown @, opts
+ if (!$.data @, 'glDropdown')
+ $.data(@, 'glDropdown', new GitLabDropdown @, opts)
+
diff --git a/app/assets/javascripts/issuable_context.js.coffee b/app/assets/javascripts/issuable_context.js.coffee
index d6d09b36d8d..2f19513a831 100644
--- a/app/assets/javascripts/issuable_context.js.coffee
+++ b/app/assets/javascripts/issuable_context.js.coffee
@@ -1,8 +1,7 @@
class @IssuableContext
- constructor: ->
+ constructor: (currentUser) ->
@initParticipants()
-
- new UsersSelect()
+ new UsersSelect(currentUser)
$('select.select2').select2({width: 'resolve', dropdownAutoWidth: true})
$(".issuable-sidebar .inline-update").on "change", "select", ->
@@ -10,11 +9,21 @@ class @IssuableContext
$(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
$(this).submit()
- $(document).on "click",".edit-link", (e) ->
- block = $(@).parents('.block')
- block.find('.selectbox').show()
- block.find('.value').hide()
- block.find('.js-select2').select2("open")
+ $(document).off("click", ".edit-link").on "click",".edit-link", (e) ->
+ $block = $(@).parents('.block')
+ $selectbox = $block.find('.selectbox')
+ if $selectbox.is(':visible')
+ $selectbox.hide()
+ $block.find('.value').show()
+ else
+ $selectbox.show()
+ $block.find('.value').hide()
+
+ if $selectbox.is(':visible')
+ setTimeout (->
+ $block.find('.dropdown-menu-toggle').trigger 'click'
+ ), 0
+
$(".right-sidebar").niceScroll()
diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee
index 1127b289264..b1479bfb449 100644
--- a/app/assets/javascripts/issues.js.coffee
+++ b/app/assets/javascripts/issues.js.coffee
@@ -1,7 +1,6 @@
@Issues =
init: ->
Issues.initSearch()
- Issues.initSelects()
Issues.initChecks()
$("body").on "ajax:success", ".close_issue, .reopen_issue", ->
@@ -17,18 +16,9 @@
$(this).html totalIssues - 1
reload: ->
- Issues.initSelects()
Issues.initChecks()
$('#filter_issue_search').val($('#issue_search').val())
- initSelects: ->
- $("select#update_state_event").select2(width: 'resolve', dropdownAutoWidth: true)
- $("select#update_assignee_id").select2(width: 'resolve', dropdownAutoWidth: true)
- $("select#update_milestone_id").select2(width: 'resolve', dropdownAutoWidth: true)
- $("select#label_name").select2(width: 'resolve', dropdownAutoWidth: true)
- $("#milestone_id, #assignee_id, #label_name").on "change", ->
- $(this).closest("form").submit()
-
initChecks: ->
$(".check_all_issues").click ->
$(".selected_issue").prop("checked", @checked)
diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee
index e08648d583b..d1fe116397a 100644
--- a/app/assets/javascripts/labels_select.js.coffee
+++ b/app/assets/javascripts/labels_select.js.coffee
@@ -4,14 +4,21 @@ class @LabelsSelect
$dropdown = $(dropdown)
projectId = $dropdown.data('project-id')
labelUrl = $dropdown.data('labels')
+ issueUpdateURL = $dropdown.data('issueUpdate')
selectedLabel = $dropdown.data('selected')
- if selectedLabel
- selectedLabel = selectedLabel.toString().split(',')
+ if selectedLabel?
+ selectedLabel = selectedLabel.split(',')
newLabelField = $('#new_label_name')
newColorField = $('#new_label_color')
showNo = $dropdown.data('show-no')
showAny = $dropdown.data('show-any')
defaultLabel = $dropdown.data('default-label')
+ abilityName = $dropdown.data('ability-name')
+ $selectbox = $dropdown.closest('.selectbox')
+ $block = $selectbox.closest('.block')
+ $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span')
+ $value = $block.find('.value')
+ $loading = $block.find('.block-loading').fadeOut()
if newLabelField.length
$newLabelCreateButton = $('.js-new-label-btn')
@@ -21,6 +28,22 @@ class @LabelsSelect
# Suggested colors in the dropdown to chose from pre-chosen colors
$('.suggest-colors-dropdown a').on 'click', (e) ->
+
+ issueURLSplit = issueUpdateURL.split('/') if issueUpdateURL?
+ if issueUpdateURL
+ labelHTMLTemplate = _.template(
+ '<% _.each(labels, function(label){ %>
+ <a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name=<%= label.title %>">
+ <span class="label color-label" style="background-color: <%= label.color %>;">
+ <%= label.title %>
+ </span>
+ </a>
+ <% }); %>'
+ );
+ labelNoneHTMLTemplate = _.template('<div class="light">None</div>')
+
+ if newLabelField.length and $dropdown.hasClass 'js-extra-options'
+ $('.suggest-colors-dropdown a').on "click", (e) ->
e.preventDefault()
e.stopPropagation()
newColorField
@@ -57,6 +80,23 @@ class @LabelsSelect
# This allows us to enable the button when ready
enableLabelCreateButton = ->
if newLabelField.val() isnt '' and newColorField.val() isnt ''
+ $newLabelError.hide()
+ $('.js-new-label-btn').disable()
+
+ # Create new label with API
+ Api.newLabel projectId, {
+ name: newLabelField.val()
+ color: newColorField.val()
+ }, (label) ->
+ $('.js-new-label-btn').enable()
+
+ if label.message?
+ $newLabelError
+ .text label.message
+ .show()
+ else
+ $('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
+
$newLabelCreateButton.enable()
else
$newLabelCreateButton.disable()
@@ -90,41 +130,84 @@ class @LabelsSelect
else
$('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
+ saveLabelData = ->
+ selected = $dropdown
+ .closest('.selectbox')
+ .find("input[name='#{$dropdown.data('field-name')}']")
+ .map(->
+ @value
+ ).get()
+ data = {}
+ data[abilityName] = {}
+ data[abilityName].label_ids = selected
+ if not selected.length
+ data[abilityName].label_ids = ['']
+ $loading.fadeIn()
+ $dropdown.trigger('loading.gl.dropdown')
+ $.ajax(
+ type: 'PUT'
+ url: issueUpdateURL
+ dataType: 'JSON'
+ data: data
+ ).done (data) ->
+ $loading.fadeOut()
+ $dropdown.trigger('loaded.gl.dropdown')
+ $selectbox.hide()
+ data.issueURLSplit = issueURLSplit
+ labelCount = 0
+ if data.labels.length
+ template = labelHTMLTemplate(data)
+ labelCount = data.labels.length
+ else
+ template = labelNoneHTMLTemplate()
+ $value
+ .removeAttr('style')
+ .html(template)
+ $sidebarCollapsedValue.text(labelCount)
+
+ $value
+ .find('a')
+ .each((i) ->
+ setTimeout(=>
+ glAnimate($(@), 'pulse')
+ ,200 * i
+ )
+ )
+
+
$dropdown.glDropdown(
data: (term, callback) ->
$.ajax(
url: labelUrl
).done (data) ->
- if showNo
- data.unshift(
- id: 0
- title: 'No Label'
- )
+ if $dropdown.hasClass 'js-extra-options'
+ if showNo
+ data.unshift(
+ id: 0
+ title: 'No Label'
+ )
- if showAny
- data.unshift(
- isAny: true
- title: 'Any Label'
- )
-
- if data.length > 2
- data.splice 2, 0, 'divider'
+ if showAny
+ data.unshift(
+ isAny: true
+ title: 'Any Label'
+ )
+ if data.length > 2
+ data.splice 2, 0, 'divider'
callback data
+
renderRow: (label) ->
- if $.isArray(selectedLabel)
- selected = ''
- $.each selectedLabel, (i, selectedLbl) ->
- selectedLbl = selectedLbl.trim()
- if selected is '' and label.title is selectedLbl
- selected = 'is-active'
- else
- selected = if label.title is selectedLabel then 'is-active' else ''
+ selectedClass = ''
+ if $selectbox.find("input[type='hidden']\
+ [name='#{$dropdown.data('field-name')}']\
+ [value='#{label.id}']").length
+ selectedClass = 'is-active'
color = if label.color? then "<span class='dropdown-label-box' style='background-color: #{label.color}'></span>" else ""
"<li>
- <a href='#' class='#{selected}'>
+ <a href='#' class='#{selectedClass}'>
#{color}
#{label.title}
</a>
@@ -133,6 +216,7 @@ class @LabelsSelect
search:
fields: ['title']
selectable: true
+
toggleLabel: (selected) ->
if selected and selected.title isnt 'Any Label'
selected.title
@@ -142,15 +226,33 @@ class @LabelsSelect
id: (label) ->
if label.isAny?
''
- else
+ else if $dropdown.hasClass "js-filter-submit"
label.title
- clicked: ->
+ else
+ label.id
+
+ hidden: ->
+ $selectbox.hide()
+ # display:block overrides the hide-collapse rule
+ $value.removeAttr('style')
+ if $dropdown.hasClass 'js-multiselect'
+ saveLabelData()
+
+ multiSelect: $dropdown.hasClass 'js-multiselect'
+ clicked: (label) ->
page = $('body').data 'page'
isIssueIndex = page is 'projects:issues:index'
isMRIndex = page is page is 'projects:merge_requests:index'
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
+ selectedLabel = label.title
+
Issues.filterResults $dropdown.closest('form')
else if $dropdown.hasClass 'js-filter-submit'
$dropdown.closest('form').submit()
+ else
+ if $dropdown.hasClass 'js-multiselect'
+ return
+ else
+ saveLabelData()
)
diff --git a/app/assets/javascripts/lib/animate.js.coffee b/app/assets/javascripts/lib/animate.js.coffee
new file mode 100644
index 00000000000..8f892b5a2b9
--- /dev/null
+++ b/app/assets/javascripts/lib/animate.js.coffee
@@ -0,0 +1,13 @@
+((w) ->
+
+ w.glAnimate = ($el, animation, done) ->
+ $el
+ .removeClass()
+ .addClass(animation + ' animated')
+ .one 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', ->
+ $(this).removeClass()
+ return
+ return
+ return
+
+) window \ No newline at end of file
diff --git a/app/assets/javascripts/lib/notify.js.coffee b/app/assets/javascripts/lib/notify.js.coffee
new file mode 100644
index 00000000000..3f9ca39912c
--- /dev/null
+++ b/app/assets/javascripts/lib/notify.js.coffee
@@ -0,0 +1,30 @@
+((w) ->
+ notificationGranted = (message, opts, onclick) ->
+ notification = new Notification(message, opts)
+
+ if onclick
+ notification.onclick = onclick
+
+ notifyPermissions = ->
+ if 'Notification' of window
+ Notification.requestPermission()
+
+ notifyMe = (message, body, icon, onclick) ->
+ opts =
+ body: body
+ icon: icon
+ # Let's check if the browser supports notifications
+ if !('Notification' of window)
+ # do nothing
+ else if Notification.permission == 'granted'
+ # If it's okay let's create a notification
+ notificationGranted message, opts, onclick
+ else if Notification.permission != 'denied'
+ Notification.requestPermission (permission) ->
+ # If the user accepts, let's create a notification
+ if permission == 'granted'
+ notificationGranted message, opts, onclick
+
+ w.notify = notifyMe
+ w.notifyPermissions = notifyPermissions
+) window
diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee
index 738ffc8343b..7102a0673e9 100644
--- a/app/assets/javascripts/merge_request_widget.js.coffee
+++ b/app/assets/javascripts/merge_request_widget.js.coffee
@@ -2,13 +2,18 @@ class @MergeRequestWidget
# Initialize MergeRequestWidget behavior
#
# check_enable - Boolean, whether to check automerge status
- # url_to_automerge_check - String, URL to use to check automerge status
- # current_status - String, current automerge status
- # ci_enable - Boolean, whether a CI service is enabled
- # url_to_ci_check - String, URL to use to check CI status
+ # merge_check_url - String, URL to use to check automerge status
+ # ci_status_url - String, URL to use to check CI status
#
+
constructor: (@opts) ->
- modal = $('#modal_merge_info').modal(show: false)
+ $('#modal_merge_info').modal(show: false)
+ @firstCICheck = true
+ @readyForCICheck = true
+ clearInterval @fetchBuildStatusInterval
+
+ @pollCIStatus()
+ notifyPermissions()
mergeInProgress: (deleteSourceBranch = false)->
$.ajax
@@ -27,18 +32,57 @@ class @MergeRequestWidget
dataType: 'json'
getMergeStatus: ->
- $.get @opts.url_to_automerge_check, (data) ->
+ $.get @opts.merge_check_url, (data) ->
$('.mr-state-widget').replaceWith(data)
- getCiStatus: ->
- if @opts.ci_enable
- $.get @opts.url_to_ci_check, (data) =>
- this.showCiState data.status
+ ciLabelForStatus: (status) ->
+ if status == 'success'
+ 'passed'
+ else
+ status
+
+ pollCIStatus: ->
+ @fetchBuildStatusInterval = setInterval ( =>
+ return if not @readyForCICheck
+
+ @getCIStatus(true)
+
+ @readyForCICheck = false
+ ), 5000
+
+ getCIStatus: (showNotification) ->
+ _this = @
+ $('.ci-widget-fetching').show()
+
+ $.getJSON @opts.ci_status_url, (data) =>
+ @readyForCICheck = true
+
+ if @firstCICheck
+ @firstCICheck = false
+ @opts.ci_status = data.status
+
+ if data.status isnt @opts.ci_status
+ @showCIStatus data.status
if data.coverage
- this.showCiCoverage data.coverage
- , 'json'
+ @showCICoverage data.coverage
+
+ if showNotification
+ message = @opts.ci_message.replace('{{status}}', @ciLabelForStatus(data.status))
+ message = message.replace('{{sha}}', data.sha)
+ message = message.replace('{{title}}', data.title)
+
+ notify(
+ "Build #{@ciLabelForStatus(data.status)}",
+ message,
+ @opts.gitlab_icon,
+ ->
+ @close()
+ Turbolinks.visit _this.opts.builds_path
+ )
+
+ @opts.ci_status = data.status
- showCiState: (state) ->
+ showCIStatus: (state) ->
$('.ci_widget').hide()
allowed_states = ["failed", "canceled", "running", "pending", "success", "skipped", "not_found"]
if state in allowed_states
@@ -52,7 +96,7 @@ class @MergeRequestWidget
$('.ci_widget.ci-error').show()
@setMergeButtonClass('btn-danger')
- showCiCoverage: (coverage) ->
+ showCICoverage: (coverage) ->
text = 'Coverage ' + coverage + '%'
$('.ci_widget:visible .ci-coverage').text(text)
diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee
index e17a1adb648..f73127f49f0 100644
--- a/app/assets/javascripts/milestone_select.js.coffee
+++ b/app/assets/javascripts/milestone_select.js.coffee
@@ -1,36 +1,65 @@
class @MilestoneSelect
- constructor: ->
+ constructor: (currentProject) ->
+ if currentProject?
+ _this = @
+ @currentProject = JSON.parse(currentProject)
$('.js-milestone-select').each (i, dropdown) ->
$dropdown = $(dropdown)
projectId = $dropdown.data('project-id')
milestonesUrl = $dropdown.data('milestones')
+ issueUpdateURL = $dropdown.data('issueUpdate')
selectedMilestone = $dropdown.data('selected')
showNo = $dropdown.data('show-no')
showAny = $dropdown.data('show-any')
+ showUpcoming = $dropdown.data('show-upcoming')
useId = $dropdown.data('use-id')
defaultLabel = $dropdown.data('default-label')
+ issuableId = $dropdown.data('issuable-id')
+ abilityName = $dropdown.data('ability-name')
+ $selectbox = $dropdown.closest('.selectbox')
+ $block = $selectbox.closest('.block')
+ $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon')
+ $value = $block.find('.value')
+ $loading = $block.find('.block-loading').fadeOut()
+
+ if issueUpdateURL
+ milestoneLinkTemplate = _.template(
+ '<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>"><%= title %></a>'
+ )
+
+ milestoneLinkNoneTemplate = '<div class="light">None</div>'
$dropdown.glDropdown(
data: (term, callback) ->
$.ajax(
url: milestonesUrl
).done (data) ->
+ extraOptions = []
+ if showAny
+ extraOptions.push(
+ id: 0
+ name: ''
+ title: 'Any Milestone'
+ )
+
if showNo
- data.unshift(
- id: '0'
+ extraOptions.push(
+ id: -1
+ name: 'No Milestone'
title: 'No Milestone'
)
- if showAny
- data.unshift(
- isAny: true
- title: 'Any Milestone'
+ if showUpcoming
+ extraOptions.push(
+ id: -2
+ name: '#upcoming'
+ title: 'Upcoming'
)
- if data.length > 2
- data.splice 2, 0, 'divider'
+ if extraOptions.length > 2
+ extraOptions.push 'divider'
- callback(data)
+ callback(extraOptions.concat(data))
filterable: true
search:
fields: ['title']
@@ -45,21 +74,51 @@ class @MilestoneSelect
milestone.title
id: (milestone) ->
if !useId
- if !milestone.isAny?
- milestone.title
- else
- ''
+ milestone.name
else
milestone.id
isSelected: (milestone) ->
- milestone.title is selectedMilestone
- clicked: ->
- page = $('body').data 'page'
- isIssueIndex = page is 'projects:issues:index'
- isMRIndex = page is page is 'projects:merge_requests:index'
+ milestone.name is selectedMilestone
+ hidden: ->
+ $selectbox.hide()
- if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
+ # display:block overrides the hide-collapse rule
+ $value.removeAttr('style')
+ clicked: (selected) ->
+ if $dropdown.hasClass 'js-filter-bulk-update'
+ return
+
+ if $dropdown.hasClass('js-filter-submit')
+ if selected.name?
+ selectedMilestone = selected.name
+ else
+ selectedMilestone = ''
Issues.filterResults $dropdown.closest('form')
- else if $dropdown.hasClass 'js-filter-submit'
- $dropdown.closest('form').submit()
+ else
+ selected = $selectbox
+ .find('input[type="hidden"]')
+ .val()
+ data = {}
+ data[abilityName] = {}
+ data[abilityName].milestone_id = selected
+ $loading
+ .fadeIn()
+ $dropdown.trigger('loading.gl.dropdown')
+ $.ajax(
+ type: 'PUT'
+ url: issueUpdateURL
+ data: data
+ ).done (data) ->
+ $dropdown.trigger('loaded.gl.dropdown')
+ $loading.fadeOut()
+ $selectbox.hide()
+ $value.removeAttr('style')
+ if data.milestone?
+ data.milestone.namespace = _this.currentProject.namespace
+ data.milestone.path = _this.currentProject.path
+ $value.html(milestoneLinkTemplate(data.milestone))
+ $sidebarCollapsedValue.find('span').text(data.milestone.title)
+ else
+ $value.html(milestoneLinkNoneTemplate)
+ $sidebarCollapsedValue.find('span').text('No')
)
diff --git a/app/assets/javascripts/right_sidebar.js.coffee b/app/assets/javascripts/right_sidebar.js.coffee
new file mode 100644
index 00000000000..67403554340
--- /dev/null
+++ b/app/assets/javascripts/right_sidebar.js.coffee
@@ -0,0 +1,55 @@
+class @Sidebar
+ constructor: (currentUser) ->
+ @addEventListeners()
+
+ addEventListeners: ->
+ $('aside').on('click', '.sidebar-collapsed-icon', @sidebarCollapseClicked)
+ $('.dropdown').on('hidden.gl.dropdown', @sidebarDropdownHidden)
+ $('.dropdown').on('loading.gl.dropdown', @sidebarDropdownLoading)
+ $('.dropdown').on('loaded.gl.dropdown', @sidebarDropdownLoaded)
+
+ sidebarDropdownLoading: (e) ->
+ $sidebarCollapsedIcon = $(@).closest('.block').find('.sidebar-collapsed-icon')
+ img = $sidebarCollapsedIcon.find('img')
+ i = $sidebarCollapsedIcon.find('i')
+ $loading = $('<i class="fa fa-spinner fa-spin"></i>')
+ if img.length
+ img.before($loading)
+ img.hide()
+ else if i.length
+ i.before($loading)
+ i.hide()
+
+ sidebarDropdownLoaded: (e) ->
+ $sidebarCollapsedIcon = $(@).closest('.block').find('.sidebar-collapsed-icon')
+ img = $sidebarCollapsedIcon.find('img')
+ $sidebarCollapsedIcon.find('i.fa-spin').remove()
+ i = $sidebarCollapsedIcon.find('i')
+ if img.length
+ img.show()
+ else
+ i.show()
+
+
+ sidebarCollapseClicked: (e) ->
+ e.preventDefault()
+ $block = $(@).closest('.block')
+
+ $('aside')
+ .find('.gutter-toggle')
+ .trigger('click')
+ $editLink = $block.find('.edit-link')
+
+ if $editLink.length
+ $editLink.trigger('click')
+ $block.addClass('collapse-after-update')
+ $('.page-with-sidebar').addClass('with-overlay')
+
+ sidebarDropdownHidden: (e) ->
+ $block = $(@).closest('.block')
+ if $block.hasClass('collapse-after-update')
+ $block.removeClass('collapse-after-update')
+ $('.page-with-sidebar').removeClass('with-overlay')
+ $('aside')
+ .find('.gutter-toggle')
+ .trigger('click') \ No newline at end of file
diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee
index c1801365266..030655491bf 100644
--- a/app/assets/javascripts/search_autocomplete.js.coffee
+++ b/app/assets/javascripts/search_autocomplete.js.coffee
@@ -1,11 +1,270 @@
class @SearchAutocomplete
- constructor: (search_autocomplete_path, project_id, project_ref) ->
- project_id = '' unless project_id
- project_ref = '' unless project_ref
- query = "?project_id=" + project_id + "&project_ref=" + project_ref
-
- $("#search").autocomplete
- source: search_autocomplete_path + query
- minLength: 1
- select: (event, ui) ->
- location.href = ui.item.url
+
+ KEYCODE =
+ ESCAPE: 27
+ BACKSPACE: 8
+ ENTER: 13
+
+ constructor: (opts = {}) ->
+ {
+ @wrap = $('.search')
+
+ @optsEl = @wrap.find('.search-autocomplete-opts')
+ @autocompletePath = @optsEl.data('autocomplete-path')
+ @projectId = @optsEl.data('autocomplete-project-id') || ''
+ @projectRef = @optsEl.data('autocomplete-project-ref') || ''
+
+ } = opts
+
+ # Dropdown Element
+ @dropdown = @wrap.find('.dropdown')
+ @dropdownContent = @dropdown.find('.dropdown-content')
+
+ @locationBadgeEl = @getElement('.search-location-badge')
+ @locationText = @getElement('.location-text')
+ @scopeInputEl = @getElement('#scope')
+ @searchInput = @getElement('.search-input')
+ @projectInputEl = @getElement('#search_project_id')
+ @groupInputEl = @getElement('#group_id')
+ @searchCodeInputEl = @getElement('#search_code')
+ @repositoryInputEl = @getElement('#repository_ref')
+ @clearInput = @getElement('.js-clear-input')
+
+ @saveOriginalState()
+
+ # Only when user is logged in
+ @createAutocomplete() if gon.current_user_id
+
+ @searchInput.addClass('disabled')
+
+ @saveTextLength()
+
+ @bindEvents()
+
+ # Finds an element inside wrapper element
+ getElement: (selector) ->
+ @wrap.find(selector)
+
+ saveOriginalState: ->
+ @originalState = @serializeState()
+
+ saveTextLength: ->
+ @lastTextLength = @searchInput.val().length
+
+ createAutocomplete: ->
+ @searchInput.glDropdown
+ filterInputBlur: false
+ filterable: true
+ filterRemote: true
+ highlight: true
+ enterCallback: false
+ filterInput: 'input#search'
+ search:
+ fields: ['text']
+ data: @getData.bind(@)
+
+ getData: (term, callback) ->
+ _this = @
+
+ # Do not trigger request if input is empty
+ return if @searchInput.val() is ''
+
+ # Prevent multiple ajax calls
+ return if @loadingSuggestions
+
+ @loadingSuggestions = true
+
+ jqXHR = $.get(@autocompletePath, {
+ project_id: @projectId
+ project_ref: @projectRef
+ term: term
+ }, (response) ->
+ # Hide dropdown menu if no suggestions returns
+ if !response.length
+ _this.disableAutocomplete()
+ return
+
+ data = []
+
+ # List results
+ firstCategory = true
+ for suggestion in response
+
+ # Add group header before list each group
+ if lastCategory isnt suggestion.category
+ data.push 'separator' if !firstCategory
+
+ firstCategory = false if firstCategory
+
+ data.push
+ header: suggestion.category
+
+ lastCategory = suggestion.category
+
+ data.push
+ text: suggestion.label
+ url: suggestion.url
+
+ # Add option to proceed with the search
+ if data.length
+ data.push('separator')
+ data.push
+ text: "Result name contains \"#{term}\""
+ url: "/search?\
+ search=#{term}\
+ &project_id=#{_this.projectInputEl.val()}\
+ &group_id=#{_this.groupInputEl.val()}"
+
+ callback(data)
+ ).always ->
+ _this.loadingSuggestions = false
+
+ serializeState: ->
+ {
+ # Search Criteria
+ search_project_id: @projectInputEl.val()
+ group_id: @groupInputEl.val()
+ search_code: @searchCodeInputEl.val()
+ repository_ref: @repositoryInputEl.val()
+ scope: @scopeInputEl.val()
+
+ # Location badge
+ _location: @locationText.text()
+ }
+
+ bindEvents: ->
+ @searchInput.on 'keydown', @onSearchInputKeyDown
+ @searchInput.on 'keyup', @onSearchInputKeyUp
+ @searchInput.on 'click', @onSearchInputClick
+ @searchInput.on 'focus', @onSearchInputFocus
+ @searchInput.on 'blur', @onSearchInputBlur
+ @clearInput.on 'click', @onRemoveLocationClick
+
+ enableAutocomplete: ->
+ # No need to enable anything if user is not logged in
+ return if !gon.current_user_id
+
+ _this = @
+ @loadingSuggestions = false
+
+ @dropdown.addClass('open')
+ @searchInput.removeClass('disabled')
+
+ onSearchInputKeyDown: =>
+ # Saves last length of the entered text
+ @saveTextLength()
+
+ onSearchInputKeyUp: (e) =>
+ switch e.keyCode
+ when KEYCODE.BACKSPACE
+ # when trying to remove the location badge
+ if @lastTextLength is 0 and @badgePresent()
+ @removeLocationBadge()
+
+ # When removing the last character and no badge is present
+ if @lastTextLength is 1
+ @disableAutocomplete()
+
+ # When removing any character from existin value
+ if @lastTextLength > 1
+ @enableAutocomplete()
+
+ when KEYCODE.ESCAPE
+ @restoreOriginalState()
+
+ else
+ # Handle the case when deleting the input value other than backspace
+ # e.g. Pressing ctrl + backspace or ctrl + x
+ if @searchInput.val() is ''
+ @disableAutocomplete()
+ else
+ # We should display the menu only when input is not empty
+ @enableAutocomplete()
+
+ # Avoid falsy value to be returned
+ return
+
+ onSearchInputClick: (e) =>
+ # Prevents closing the dropdown menu
+ e.stopImmediatePropagation()
+
+ onSearchInputFocus: =>
+ @wrap.addClass('search-active')
+
+ onRemoveLocationClick: (e) =>
+ e.preventDefault()
+ @removeLocationBadge()
+ @searchInput.val('').focus()
+ @skipBlurEvent = true
+
+ onSearchInputBlur: (e) =>
+ @skipBlurEvent = false
+
+ # We should wait to make sure we are not clearing the input instead
+ setTimeout( =>
+ return if @skipBlurEvent
+
+ @wrap.removeClass('search-active')
+
+ # If input is blank then restore state
+ if @searchInput.val() is ''
+ @restoreOriginalState()
+ , 150)
+
+ addLocationBadge: (item) ->
+ category = if item.category? then "#{item.category}: " else ''
+ value = if item.value? then item.value else ''
+
+ html = "<span class='location-badge'>
+ <i class='location-text'>#{category}#{value}</i>
+ </span>"
+ @locationBadgeEl.html(html)
+ @wrap.addClass('has-location-badge')
+
+ restoreOriginalState: ->
+ inputs = Object.keys @originalState
+
+ for input in inputs
+ @getElement("##{input}").val(@originalState[input])
+
+
+ if @originalState._location is ''
+ @locationBadgeEl.empty()
+ else
+ @addLocationBadge(
+ value: @originalState._location
+ )
+
+ @dropdown.removeClass 'open'
+
+ badgePresent: ->
+ @locationBadgeEl.children().length
+
+ resetSearchState: ->
+ inputs = Object.keys @originalState
+
+ for input in inputs
+
+ # _location isnt a input
+ break if input is '_location'
+
+ @getElement("##{input}").val('')
+
+ removeLocationBadge: ->
+ @locationBadgeEl.empty()
+
+ # Reset state
+ @resetSearchState()
+
+ @wrap.removeClass('has-location-badge')
+
+ disableAutocomplete: ->
+ @searchInput.addClass('disabled')
+ @dropdown.removeClass('open')
+ @restoreMenu()
+
+ restoreMenu: ->
+ html = "<ul>
+ <li><a class='dropdown-menu-empty-link is-focused'>Loading...</a></li>
+ </ul>"
+ @dropdownContent.html(html)
diff --git a/app/assets/javascripts/sidebar.js.coffee b/app/assets/javascripts/sidebar.js.coffee
index 860d4f438d0..e1778511240 100644
--- a/app/assets/javascripts/sidebar.js.coffee
+++ b/app/assets/javascripts/sidebar.js.coffee
@@ -4,7 +4,6 @@ expanded = 'page-sidebar-expanded'
toggleSidebar = ->
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
$('header').toggleClass("header-collapsed header-expanded")
- $('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
$.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
setTimeout ( ->
diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee
index b6b4bd90e6a..ec2df6c5b73 100644
--- a/app/assets/javascripts/todos.js.coffee
+++ b/app/assets/javascripts/todos.js.coffee
@@ -6,10 +6,12 @@ class @Todos
clearListeners: ->
$('.done-todo').off('click')
$('.js-todos-mark-all').off('click')
+ $('.todo').off('click')
initBtnListeners: ->
$('.done-todo').on('click', @doneClicked)
$('.js-todos-mark-all').on('click', @allDoneClicked)
+ $('.todo').on('click', @goToTodoUrl)
doneClicked: (e) =>
e.preventDefault()
@@ -54,3 +56,6 @@ class @Todos
updateBadges: (data) ->
$('.todos-pending .badge, .todos-pending-count').text data.count
$('.todos-done .badge').text data.done_count
+
+ goToTodoUrl: ->
+ Turbolinks.visit($(this).data('url'))
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index 84193400890..eee9b6e690e 100644
--- a/app/assets/javascripts/users_select.js.coffee
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -1,7 +1,9 @@
class @UsersSelect
- constructor: ->
+ constructor: (currentUser) ->
@usersPath = "/autocomplete/users.json"
@userPath = "/autocomplete/users/:id.json"
+ if currentUser?
+ @currentUser = JSON.parse(currentUser)
$('.js-user-search').each (i, dropdown) =>
$dropdown = $(dropdown)
@@ -12,6 +14,81 @@ class @UsersSelect
firstUser = $dropdown.data('first-user')
selectedId = $dropdown.data('selected')
defaultLabel = $dropdown.data('default-label')
+ issueURL = $dropdown.data('issueUpdate')
+ $selectbox = $dropdown.closest('.selectbox')
+ $block = $selectbox.closest('.block')
+ abilityName = $dropdown.data('ability-name')
+ $value = $block.find('.value')
+ $collapsedSidebar = $block.find('.sidebar-collapsed-user')
+ $loading = $block.find('.block-loading').fadeOut()
+
+ $block.on('click', '.js-assign-yourself', (e) =>
+ e.preventDefault()
+ assignTo(@currentUser.id)
+ )
+
+ assignTo = (selected) ->
+ data = {}
+ data[abilityName] = {}
+ data[abilityName].assignee_id = selected
+ $loading
+ .fadeIn()
+ $dropdown.trigger('loading.gl.dropdown')
+ $.ajax(
+ type: 'PUT'
+ dataType: 'json'
+ url: issueURL
+ data: data
+ ).done (data) ->
+ $dropdown.trigger('loaded.gl.dropdown')
+ $loading.fadeOut()
+ $selectbox.hide()
+
+ if data.assignee
+ user =
+ name: data.assignee.name
+ username: data.assignee.username
+ avatar: data.assignee.avatar_url
+ else
+ user =
+ name: 'Unassigned'
+ username: ''
+ avatar: ''
+ $value.html(assigneeTemplate(user))
+ $collapsedSidebar.html(collapsedAssigneeTemplate(user))
+
+
+ collapsedAssigneeTemplate = _.template(
+ '<% if( avatar ) { %>
+ <a class="author_link" href="/u/<%= username %>">
+ <img width="24" class="avatar avatar-inline s24" alt="" src="<%= avatar %>">
+ <span class="author">Toni Boehm</span>
+ </a>
+ <% } else { %>
+ <i class="fa fa-user"></i>
+ <% } %>'
+ )
+
+ assigneeTemplate = _.template(
+ '<% if (username) { %>
+ <a class="author_link " href="/u/<%= username %>">
+ <% if( avatar ) { %>
+ <img width="32" class="avatar avatar-inline s32" alt="" src="<%= avatar %>">
+ <% } %>
+ <span class="author"><%= name %></span>
+ <span class="username">
+ @<%= username %>
+ </span>
+ </a>
+ <% } else { %>
+ <span class="assign-yourself">
+ No assignee -
+ <a href="#" class="js-assign-yourself">
+ assign yourself
+ </a>
+ </span>
+ <% } %>'
+ )
$dropdown.glDropdown(
data: (term, callback) =>
@@ -57,20 +134,38 @@ class @UsersSelect
fields: ['name', 'username']
selectable: true
fieldName: $dropdown.data('field-name')
+
toggleLabel: (selected) ->
if selected && 'id' of selected
selected.name
else
defaultLabel
- clicked: ->
+
+ inputId: 'issue_assignee_id'
+
+ hidden: (e) ->
+ $selectbox.hide()
+ # display:block overrides the hide-collapse rule
+ $value.removeAttr('style')
+
+ clicked: (user) ->
page = $('body').data 'page'
isIssueIndex = page is 'projects:issues:index'
isMRIndex = page is page is 'projects:merge_requests:index'
+ if $dropdown.hasClass('js-filter-bulk-update')
+ return
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
+ selectedId = user.id
Issues.filterResults $dropdown.closest('form')
else if $dropdown.hasClass 'js-filter-submit'
$dropdown.closest('form').submit()
+ else
+ selected = $dropdown
+ .closest('.selectbox')
+ .find("input[name='#{$dropdown.data('field-name')}']").val()
+ assignTo(selected)
+
renderRow: (user) ->
username = if user.username then "@#{user.username}" else ""
avatar = if user.avatar_url then user.avatar_url else false
@@ -87,17 +182,25 @@ class @UsersSelect
if avatar
img = "<img src='#{avatar}' class='avatar avatar-inline' width='30' />"
- "<li>
- <a href='#' class='dropdown-menu-user-link #{selected}'>
- #{img}
- <strong class='dropdown-menu-user-full-name'>
- #{user.name}
- </strong>
- <span class='dropdown-menu-user-username'>
- #{username}
- </span>
- </a>
- </li>"
+ # 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>"
+
+ listWithUserName = "<span class='dropdown-menu-user-username'>
+ #{username}
+ </span>"
+ listClosingTags = "</a>
+ </li>"
+
+
+ if username is ''
+ listWithUserName = ''
+
+ listWithName + listWithUserName + listClosingTags
)
$('.ajax-users-select').each (i, select) =>
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index e2d590f4df4..69b3b6586de 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -10,6 +10,7 @@
*= require dropzone/basic
*= require cal-heatmap
*= require cropper.css
+ *= require animate
*/
/*
diff --git a/app/assets/stylesheets/behaviors.scss b/app/assets/stylesheets/behaviors.scss
index 469f4f296ae..542a53f0377 100644
--- a/app/assets/stylesheets/behaviors.scss
+++ b/app/assets/stylesheets/behaviors.scss
@@ -13,10 +13,10 @@
// Toggle between two states.
.js-toggler-container {
- .turn-on { display: block; }
+ .turn-on { display: block; }
.turn-off { display: none; }
&.on {
- .turn-on { display: none; }
+ .turn-on { display: none; }
.turn-off { display: block; }
}
}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 9b676d759e0..db1a8b1bf78 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -121,7 +121,7 @@ p.time {
text-shadow: none;
}
-.thin_area{
+.thin_area {
height: 150px;
}
@@ -148,7 +148,7 @@ li.note {
}
}
-.wiki_content code, .readme code{
+.wiki_content code, .readme code {
background-color: inherit;
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 2d616fc660c..82dc1acbd01 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -42,7 +42,7 @@
font-size: 15px;
text-align: left;
border: 1px solid $dropdown-toggle-border-color;
- border-radius: 2px;
+ border-radius: $dropdown-border-radius;
outline: 0;
text-overflow: ellipsis;
white-space: nowrap;
@@ -75,12 +75,12 @@
width: 240px;
margin-top: 2px;
margin-bottom: 0;
- padding: 10px;
- font-size: 14px;
+ font-size: 15px;
font-weight: normal;
+ padding: 10px 0;
background-color: $dropdown-bg;
border: 1px solid $dropdown-border-color;
- border-radius: $border-radius-base;
+ border-radius: $dropdown-border-radius;
box-shadow: 0 2px 4px $dropdown-shadow-color;
&.is-loading {
@@ -101,9 +101,17 @@
li {
text-align: left;
list-style: none;
+ padding: 0 10px;
}
.divider {
+ height: 1px;
+ margin: 8px 10px;
+ padding: 0;
+ background-color: $dropdown-divider-color;
+ }
+
+ .separator {
width: 100%;
height: 1px;
margin-top: 8px;
@@ -136,6 +144,21 @@
background-color: $dropdown-empty-row-bg;
}
}
+
+ &.dropdown-menu-user-link {
+ line-height: 16px;
+ }
+ }
+
+ .dropdown-header {
+ color: $dropdown-header-color;
+ font-size: 13px;
+ line-height: 22px;
+ padding: 0 10px 10px;
+ }
+
+ .separator + .dropdown-header {
+ padding-top: 2px;
}
}
@@ -154,6 +177,10 @@
.dropdown-menu-back {
display: block;
}
+
+ .dropdown-content {
+ padding: 0 10px;
+ }
}
}
@@ -167,13 +194,13 @@
}
.dropdown-menu-user-link {
- padding-top: 7px;
+ padding-top: 10px;
padding-bottom: 7px;
}
.dropdown-menu-user-full-name {
display: block;
- font-weight: 600;
+ font-weight: 500;
line-height: 16px;
text-overflow: ellipsis;
overflow: hidden;
@@ -189,7 +216,7 @@
}
.dropdown-select {
- width: 300px;
+ width: $dropdown-width;
}
.dropdown-menu-align-right {
@@ -218,20 +245,11 @@
}
}
-.dropdown-header {
- padding-left: 5px;
- padding-right: 5px;
- color: $dropdown-header-color;
- font-size: 13px;
- line-height: 22px;
-}
.dropdown-title {
position: relative;
- margin-bottom: 10px;
- padding-left: 30px;
- padding-right: 30px;
- padding-bottom: 10px;
+ padding: 0 0 15px;
+ margin: 0 10px 10px;
font-weight: 600;
line-height: 1;
text-align: center;
@@ -257,21 +275,26 @@
}
.dropdown-menu-close {
- right: 0;
+ right: 7px;
+ width: 20px;
+ height: 20px;
+ top: -1px;
}
.dropdown-menu-back {
- left: 0;
+ left: 7px;
+ top: 2px;
}
.dropdown-input {
position: relative;
margin-bottom: 10px;
+ padding: 0 10px;
.fa {
position: absolute;
top: 10px;
- right: 10px;
+ right: 20px;
color: #c7c7c7;
font-size: 12px;
pointer-events: none;
@@ -281,6 +304,9 @@
display: none;
cursor: pointer;
pointer-events: all;
+ right: 22px;
+ top: 9px;
+ font-size: 14px;
}
&.has-value {
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index ad0e88cda86..a26ace5cc19 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -3,12 +3,10 @@
*
*/
.file-holder {
- border: none;
border: 1px solid $border-color;
&.readme-holder {
- margin-top: 10px;
- border-bottom: 0;
+ margin: $gl-padding-top 0;
}
table {
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index b05c5df1bd8..9209347f9bc 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -3,7 +3,7 @@
vertical-align: top;
}
-@media (min-width: $screen-sm-min) {
+@media (min-width: $screen-sm-min) {
.issues-filters,
.issues_bulk_update {
.dropdown-menu-toggle {
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 4cb4129b71b..54cb5461113 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -6,40 +6,6 @@ input {
border-radius: $border-radius-base;
}
-input[type='search'] {
- background-color: white;
- padding-left: 10px;
-}
-
-input[type='search'].search-input {
- background-repeat: no-repeat;
- background-position: 10px;
- background-size: 16px;
- background-position-x: 30%;
- padding-left: 10px;
- background-color: $gray-light;
-
- &.search-input[value=""] {
- background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC');
- }
-
- &.search-input::-webkit-input-placeholder {
- text-align: center;
- }
-
- &.search-input:-moz-placeholder { /* Firefox 18- */
- text-align: center;
- }
-
- &.search-input::-moz-placeholder { /* Firefox 19+ */
- text-align: center;
- }
-
- &.search-input:-ms-input-placeholder {
- text-align: center;
- }
-}
-
input[type='text'].danger {
background: #f2dede!important;
border-color: #d66;
@@ -125,7 +91,7 @@ label {
}
.form-control::-webkit-input-placeholder {
- color: #7f8fa4;
+ color: $gl-placeholder-color;
}
.input-group {
diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss
index c83cf881596..fa9038ebaca 100644
--- a/app/assets/stylesheets/framework/gitlab-theme.scss
+++ b/app/assets/stylesheets/framework/gitlab-theme.scss
@@ -33,10 +33,15 @@
background: $color;
}
+ .complex-sidebar .nav-primary {
+ border-right: 1px solid lighten($color, 3%);
+ }
+
.sidebar-wrapper {
background: $color-darker;
.sidebar-user {
+ border-top: 1px solid lighten($color, 3%);
background: $color-darker;
color: $color-light;
@@ -62,7 +67,6 @@
.count {
color: $color-light;
- background: $color-dark;
}
}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 6a68bb5c115..724980b2208 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -36,7 +36,7 @@ header {
padding: 0;
.nav > li > a {
- color: #7f8fa4;
+ color: $gl-icon-color;
font-size: 18px;
padding: 0;
margin: ($header-height - 28) / 2 0;
@@ -62,7 +62,7 @@ header {
background-color: #eee;
}
&.active {
- color: #7f8fa4;
+ color: $gl-icon-color;
}
}
}
@@ -81,14 +81,14 @@ header {
font-size: 19px;
line-height: $header-height;
font-weight: normal;
- color: #4c4e54;
+ color: $gl-text-color;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: top;
white-space: nowrap;
a {
- color: #4c4e54;
+ color: $gl-text-color;
&:hover {
text-decoration: underline;
}
@@ -117,37 +117,17 @@ header {
}
}
- .search {
- margin-right: 10px;
- margin-left: 10px;
- margin-top: ($header-height - 36) / 2;
-
- form {
- margin: 0;
- padding: 0;
- }
-
- .search-input {
- width: 220px;
-
- &:focus {
- @include box-shadow(none);
- outline: none;
- }
- }
- }
-
.impersonation i {
color: $red-normal;
}
}
@mixin collapsed-header {
- margin-left: $sidebar_collapsed_width;
+ margin-left: 40px;
}
.header-collapsed {
- margin-left: $sidebar_collapsed_width;
+ margin-left: 40px;
@media (min-width: $screen-md-min) {
@include collapsed-header;
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index 5ea4f9a49db..66180f38a4f 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -107,7 +107,7 @@
}
.page-title {
- .note_created_ago, .new-issue-link {
+ .note-created-ago, .new-issue-link {
display: none;
}
}
@@ -116,7 +116,7 @@
display: none;
}
- aside:not(.right-sidebar){
+ aside:not(.right-sidebar) {
display: none;
}
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 95bdd6d1ea3..fc3b0a422a7 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -100,6 +100,7 @@
> form {
display: inline-block;
+ margin-top: -1px;
}
.icon-label {
@@ -110,7 +111,7 @@
height: 34px;
display: inline-block;
position: relative;
- top: 1px;
+ top: 2px;
margin-right: $gl-padding-top;
/* Medium devices (desktops, 992px and up) */
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index e82d052f45a..b2fab387e17 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -51,7 +51,7 @@
padding: 10px 15px;
}
-.select2-drop{
+.select2-drop {
color: #7f8fa4;
}
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 9d188317783..1d49249dd80 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -144,7 +144,7 @@
}
a {
- padding: 7px 15px;
+ padding: 7px 12px;
font-size: $gl-font-size;
line-height: 24px;
color: $gray;
@@ -169,10 +169,12 @@
}
.count {
- float: right;
- background: #eee;
- padding: 0 8px;
- @include border-radius(6px);
+ &:before {
+ content: '(';
+ }
+ &:after {
+ content: ')';
+ }
}
&.back-link i {
@@ -191,6 +193,27 @@
}
}
+.expand-nav a {
+ color: $gl-icon-color;
+ width: 60px;
+ position: fixed;
+ top: 0;
+ left: 0;
+ font-size: 20px;
+ background: #fff;
+ height: 59px;
+ text-align: center;
+ line-height: 59px;
+ border-bottom: 1px solid #eee;
+ transition-duration: .3s;
+ outline: none;
+ z-index: 100;
+
+ &:hover {
+ text-decoration: none;
+ }
+}
+
.collapse-nav a {
width: $sidebar_width;
position: fixed;
@@ -210,55 +233,12 @@
}
.page-sidebar-collapsed {
- padding-left: $sidebar_collapsed_width;
-
.sidebar-wrapper {
- width: $sidebar_collapsed_width;
-
- .header-logo {
- width: $sidebar_collapsed_width;
-
- a {
- padding-left: ($sidebar_collapsed_width - 36) / 2;
-
- .gitlab-text-container {
- display: none;
- }
- }
- }
-
- .nav-sidebar {
- width: $sidebar_collapsed_width;
-
- li {
- width: auto;
-
- a {
- span {
- display: none;
- }
- }
- }
- }
-
- .collapse-nav a {
- width: $sidebar_collapsed_width;
- }
-
- .sidebar-user {
- padding-left: ($sidebar_collapsed_width - 36) / 2;
- width: $sidebar_collapsed_width;
-
- .username {
- display: none;
- }
- }
+ display: none;
}
}
.page-sidebar-expanded {
- padding-left: $sidebar_collapsed_width;
-
@media (min-width: $screen-md-min) {
padding-left: $sidebar_width;
}
@@ -288,6 +268,10 @@
@media (min-width: $screen-sm-min) {
padding-right: $sidebar_collapsed_width;
}
+
+ .sidebar-collapsed-icon {
+ cursor: pointer;
+ }
}
.right-sidebar-expanded {
@@ -300,4 +284,53 @@
@media (min-width: $screen-md-min) {
padding-right: $gutter_width;
}
+
+ &.with-overlay {
+ padding-right: $sidebar_collapsed_width;
+ }
+}
+
+.complex-sidebar {
+ display: inline-block;
+
+ .nav-primary {
+ width: 61px;
+ float: left;
+ height: 100vh;
+
+ .nav-sidebar {
+ width: 60px;
+
+ li a {
+ width: 60px;
+
+ span {
+ display: none;
+ }
+ }
+ }
+ }
+
+ .nav-secondary {
+ $nav-secondary-width: 168px;
+
+ float: left;
+ width: $nav-secondary-width;
+
+ .nav-sidebar {
+ width: $nav-secondary-width;
+
+ li {
+ width: $nav-secondary-width;
+
+ a {
+ width: $nav-secondary-width;
+
+ i {
+ display: none;
+ }
+ }
+ }
+ }
+ }
}
diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
index f63ac033234..c72af5dad0a 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
@@ -56,8 +56,8 @@ $component-active-bg: $brand-info;
//##
$input-color: $text-color;
-$input-border: #e7e9ed;
-$input-border-focus: #7f8fa4;
+$input-border: $border-color;
+$input-border-focus: $focus-border-color;
$legend-color: $text-color;
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 61e0dd4d672..98fe794d362 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -11,6 +11,7 @@ $gutter_inner_width: 258px;
* UI elements
*/
$border-color: #efeff1;
+$focus-border-color: #3aabf0;
$table-border-color: #eef0f2;
$background-color: #faf9f9;
@@ -26,6 +27,7 @@ $gl-text-orange: #d90;
$gl-link-color: #3084bb;
$gl-dark-link-color: #333;
$gl-placeholder-color: #8f8f8f;
+$gl-icon-color: $gl-placeholder-color;
$gl-gray: $gl-text-color;
$gl-header-color: $gl-title-color;
@@ -66,7 +68,7 @@ $header-height: 58px;
$fixed-layout-width: 1280px;
$gl-avatar-size: 40px;
$error-exclamation-point: #e62958;
-$border-radius-default: 3px;
+$border-radius-default: 2px;
$btn-transparent-color: #8f8f8f;
$ssh-key-icon-color: #8f8f8f;
$ssh-key-icon-size: 18px;
@@ -166,6 +168,8 @@ $regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif
/*
* Dropdowns
*/
+$dropdown-border-radius: 2px;
+$dropdown-width: 300px;
$dropdown-bg: #fff;
$dropdown-link-color: #555;
$dropdown-link-hover-bg: $row-hover;
@@ -176,8 +180,8 @@ $dropdown-divider-color: rgba(#000, .1);
$dropdown-header-color: #959494;
$dropdown-title-btn-color: #bfbfbf;
$dropdown-input-color: #555;
-$dropdown-input-focus-border: rgb(58, 171, 240);
-$dropdown-input-focus-shadow: rgba(#000, .2);
+$dropdown-input-focus-border: $focus-border-color;
+$dropdown-input-focus-shadow: rgba($dropdown-input-focus-border, .4);
$dropdown-loading-bg: rgba(#fff, .6);
$dropdown-toggle-bg: #fff;
@@ -193,3 +197,23 @@ $dropdown-toggle-hover-icon-color: $dropdown-toggle-hover-border-color;
$award-emoji-menu-bg: #fff;
$award-emoji-menu-border: #f1f2f4;
$award-emoji-new-btn-icon-color: #dcdcdc;
+
+/*
+ * Search Box
+ */
+$search-input-border-color: $dropdown-input-focus-border;
+$search-input-focus-shadow-color: $dropdown-input-focus-shadow;
+$search-input-width: $dropdown-width;
+$location-badge-color: #aaa;
+$location-badge-bg: $gray-normal;
+$location-icon-color: #e7e9ed;
+$location-active-color: $gl-text-color;
+$location-active-bg: $search-input-border-color;
+
+/*
+ * Notes
+ */
+$notes-light-color: #8e8e8e;
+$notes-action-color: #c3c3c3;
+$notes-role-color: #8e8e8e;
+$notes-role-border-color: #e4e4e4;
diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss
index 28994e60baa..37bf38fa65d 100644
--- a/app/assets/stylesheets/pages/awards.scss
+++ b/app/assets/stylesheets/pages/awards.scss
@@ -37,7 +37,7 @@
height: 300px;
overflow-y: scroll;
- input.emoji-search{
+ input.emoji-search {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC");
background-repeat: no-repeat;
background-position: right 5px center;
diff --git a/app/assets/stylesheets/pages/ci_projects.scss b/app/assets/stylesheets/pages/ci_projects.scss
index 2a7b5cfc7fd..67a9d7d2cf7 100644
--- a/app/assets/stylesheets/pages/ci_projects.scss
+++ b/app/assets/stylesheets/pages/ci_projects.scss
@@ -42,7 +42,7 @@
}
}
- .loading{
+ .loading {
font-size: 20px;
}
diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss
index 971656feb42..082911bd118 100644
--- a/app/assets/stylesheets/pages/commit.scss
+++ b/app/assets/stylesheets/pages/commit.scss
@@ -1,15 +1,15 @@
-.commit-title{
+.commit-title {
display: block;
}
-.commit-author, .commit-committer{
+.commit-author, .commit-committer {
display: block;
color: #999;
font-weight: normal;
font-style: italic;
}
-.commit-author strong, .commit-committer strong{
+.commit-author strong, .commit-committer strong {
font-weight: bold;
font-style: normal;
}
@@ -74,7 +74,7 @@
color: $gl-text-red;
}
}
- .edit-file{
+ .edit-file {
a {
color: $gl-text-color;
}
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 5e91496679a..8272615768d 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -1,4 +1,4 @@
-.commits-compare-switch{
+.commits-compare-switch {
@include btn-default;
@include btn-white;
background: image-url("switch_icon.png") no-repeat center center;
@@ -93,7 +93,6 @@ li.commit {
.commit-row-info {
color: $gl-gray;
line-height: 24px;
- font-size: 13px;
a {
color: $gl-gray;
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index 43be5e38ba8..0f0592a0ab8 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -1,5 +1,5 @@
.file-editor {
- #editor{
+ #editor {
border: none;
@include border-radius(0);
height: 500px;
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index 84eefd01cfe..c66efe978cd 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -43,10 +43,6 @@
.md {
color: #7f8fa4;
font-size: $gl-font-size;
-
- iframe.twitter-share-button {
- vertical-align: bottom;
- }
}
pre {
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 5300bb52a1b..88c1b614c74 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -30,6 +30,10 @@
}
.issuable-sidebar {
+ a {
+ color: inherit;
+ }
+
.block {
@include clearfix;
padding: $gl-padding 0;
@@ -89,7 +93,7 @@
}
.cross-project-reference {
- color: $gl-link-color;
+ color: inherit;
span {
white-space: nowrap;
@@ -133,6 +137,12 @@
.value {
line-height: 1;
+
+ .assign-yourself {
+ margin-top: 10px;
+ font-weight: normal;
+ display: block;
+ }
}
.bold {
@@ -252,6 +262,15 @@
text-decoration: none;
}
}
+
+ .dropdown-menu-toggle {
+ width: 100%;
+ padding-top: 6px;
+ }
+
+ .open .dropdown-menu {
+ width: 100%;
+ }
}
.btn-default.gutter-toggle {
diff --git a/app/assets/stylesheets/pages/lint.scss b/app/assets/stylesheets/pages/lint.scss
index 6d2bd33b28b..6926448519e 100644
--- a/app/assets/stylesheets/pages/lint.scss
+++ b/app/assets/stylesheets/pages/lint.scss
@@ -1,9 +1,9 @@
.ci-body {
- .incorrect-syntax{
+ .incorrect-syntax {
font-size: 19px;
color: red;
}
- .correct-syntax{
+ .correct-syntax {
font-size: 19px;
color: #47a447;
}
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index 777bcbca5c3..403171d4532 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -36,7 +36,7 @@
}
}
- .login-box{
+ .login-box {
background: #fafafa;
border-radius: 10px;
box-shadow: 0 0 2px #ccc;
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index cee5c47cfb2..7ff63ca20b6 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -230,3 +230,9 @@
}
}
}
+
+.builds {
+ .table-holder {
+ overflow-x: scroll;
+ }
+}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index daf2651425f..655f88b0c2c 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -71,8 +71,6 @@
}
.note-form-actions {
- background: #fff;
-
.note-form-option {
margin-top: 8px;
margin-left: 30px;
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 4bd2016bdcf..92fcaaeeacf 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -22,7 +22,7 @@ ul.notes {
margin-left: 55px;
}
- .note_created_ago, .note-updated-at {
+ .note-created-ago, .note-updated-at {
white-space: nowrap;
}
@@ -39,53 +39,6 @@ ul.notes {
}
}
- .discussion-header,
- .note-header {
- @extend .cgray;
-
- a:hover {
- text-decoration: none;
- }
-
- .avatar {
- float: left;
- margin-right: 10px;
- }
-
- .discussion-last-update,
- .note-last-update {
- &:before {
- content: "\00b7";
- }
-
- a {
- color: $gl-gray;
-
- &:hover {
- text-decoration: underline;
- }
- }
- }
- .author {
- color: #4c4e54;
- margin-right: 3px;
-
- &:hover {
- color: $gl-link-color;
- }
- }
- .author-username {
- }
-
- .note-role {
- float: right;
- margin-top: 1px;
- border: 1px solid #bbb;
- background-color: transparent;
- color: $gl-gray;
- }
- }
-
.discussion-body {
padding-top: 15px;
}
@@ -198,40 +151,88 @@ ul.notes {
border-width: 1px 0;
padding-top: 0;
vertical-align: top;
- &.parallel{
+ &.parallel {
border-width: 1px;
}
}
}
}
+.discussion-header,
+.note-header {
+ a {
+ color: inherit;
+
+ &:hover {
+ color: $gl-link-color;
+ text-decoration: none;
+ }
+ }
+
+ .author_link {
+ font-weight: 600;
+ }
+}
+
+.note-headline-light,
+.discussion-headline-light {
+ color: $notes-light-color;
+}
+
/**
* Actions for Discussions/Notes
*/
-.discussion,
-.note {
- .discussion-actions,
- .note-actions {
- float: right;
- margin-left: 10px;
+.discussion-actions,
+.note-actions {
+ float: right;
+ margin-left: 10px;
+ color: $notes-action-color;
+}
- a {
- margin-left: 5px;
- color: $gl-gray;
+.note-action-button,
+.discussion-action-button {
+ display: inline-block;
+ margin-left: 10px;
+ line-height: 24px;
- i.fa {
- font-size: 16px;
- line-height: 16px;
- }
+ .fa {
+ position: relative;
+ top: 1px;
+ font-size: 17px;
+ }
- &:hover {
- @extend .cgray;
- &.danger { @extend .cred; }
- }
- }
+ .fa-trash-o {
+ top: 0;
+ font-size: 16px;
}
}
+
+.discussion-toggle-button {
+ line-height: 20px;
+ font-size: 13px;
+
+ .fa {
+ margin-right: 3px;
+ font-size: 10px;
+ line-height: 18px;
+ vertical-align: top;
+ }
+}
+
+.note-role {
+ position: relative;
+ top: -2px;
+ display: inline-block;
+ padding-left: 4px;
+ padding-right: 4px;
+ color: $notes-role-color;
+ font-size: 12px;
+ line-height: 20px;
+ border: 1px solid $notes-role-border-color;
+ border-radius: $border-radius-base;
+}
+
.diff-file .note .note-actions {
right: 0;
top: 0;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 71bde1174ee..4e6aa8cd1a6 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -162,7 +162,7 @@
margin-right: 12px;
a {
- margin: -1px !important;
+ margin: -1px;
}
}
@@ -222,7 +222,7 @@
padding: 0;
background: transparent;
border: none;
- line-height: 42px;
+ line-height: 36px;
margin: 0;
> li + li:before {
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index b6e45024644..3c74d25beb0 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -21,3 +21,145 @@
}
}
+.search {
+ margin-right: 10px;
+ margin-left: 10px;
+ margin-top: ($header-height - 35) / 2;
+
+ form {
+ @extend .form-control;
+ margin: 0;
+ padding: 4px;
+ width: $search-input-width;
+ line-height: 24px;
+ }
+
+ .location-text {
+ font-style: normal;
+ }
+
+ .search-input {
+ border: none;
+ font-size: 14px;
+ outline: none;
+ padding: 0;
+ margin-left: 5px;
+ line-height: 25px;
+ width: 98%;
+ }
+
+ .location-badge {
+ line-height: 25px;
+ padding: 0 5px;
+ border-radius: $border-radius-default;
+ font-size: 14px;
+ font-style: normal;
+ color: $location-badge-color;
+ display: inline-block;
+ background-color: $location-badge-bg;
+ vertical-align: top;
+ }
+
+ .search-input-container {
+ display: -webkit-flex;
+ display: flex;
+ position: relative;
+ }
+
+ .search-location-badge, .search-input-wrap {
+ // Fallback if flexbox is not supported
+ display: inline-block;
+ }
+
+ .search-input-wrap {
+ width: 100%;
+
+ .search-icon, .clear-icon {
+ position: absolute;
+ right: 5px;
+ top: 0;
+ color: $location-icon-color;
+
+ &:before {
+ font-family: FontAwesome;
+ font-weight: normal;
+ font-style: normal;
+ }
+ }
+
+ .search-icon {
+ @extend .fa-search;
+ @include transition(color .15s);
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ }
+
+ .clear-icon {
+ @extend .fa-times;
+ display: none;
+ }
+
+ // Rewrite position. Dropdown menu should be relative to .search-input-container
+ .dropdown {
+ position: static;
+ }
+
+ .dropdown-header {
+ text-transform: uppercase;
+ font-size: 11px;
+ }
+
+ // Custom dropdown positioning
+ .dropdown-menu {
+ top: 30px;
+ left: -5px;
+ padding: 0;
+
+ ul {
+ padding: 10px 0;
+ }
+ }
+
+ .dropdown-content {
+ max-height: 350px;
+ }
+ }
+
+ &.search-active {
+ form {
+ @extend .form-control:focus;
+ border-color: $dropdown-input-focus-border;
+ box-shadow: 0 0 4px $search-input-focus-shadow-color;
+ }
+
+ .location-badge {
+ @include transition(all .15s);
+ background-color: $location-active-bg;
+ color: $white-light;
+ }
+
+ .search-input-wrap {
+ i {
+ color: $location-active-color;
+ }
+ }
+
+ &.has-location-badge {
+ .search-icon {
+ display: none;
+ }
+
+ .clear-icon {
+ cursor: pointer;
+ display: block;
+ }
+ }
+ }
+
+ &.has-location-badge {
+ .search-input-wrap {
+ width: 78%;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index f983e9829e6..e83fa9e3d52 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -6,13 +6,19 @@
.navbar-nav {
li {
.badge.todos-pending-count {
- background-color: #7f8fa4;
+ background-color: $gl-icon-color;
margin-top: -5px;
font-weight: normal;
}
}
}
+.todo {
+ &:hover {
+ cursor: pointer;
+ }
+}
+
.todo-item {
.todo-title {
@include str-truncated(calc(100% - 174px));
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index ed9f6031389..f010436bd36 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -52,7 +52,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:require_two_factor_authentication,
:two_factor_grace_period,
:gravatar_enabled,
- :twitter_sharing_enabled,
:sign_in_text,
:help_page_text,
:home_page_url,
diff --git a/app/controllers/ci/projects_controller.rb b/app/controllers/ci/projects_controller.rb
index 081e01a75e0..8bf71a1adbb 100644
--- a/app/controllers/ci/projects_controller.rb
+++ b/app/controllers/ci/projects_controller.rb
@@ -1,11 +1,15 @@
module Ci
class ProjectsController < Ci::ApplicationController
before_action :project
- before_action :authorize_read_project!, except: [:badge]
before_action :no_cache, only: [:badge]
+ before_action :authorize_read_project!, except: [:badge, :index]
skip_before_action :authenticate_user!, only: [:badge]
protect_from_forgery
+ def index
+ redirect_to root_path
+ end
+
def show
# Temporary compatibility with CI badges pointing to CI project page
redirect_to namespace_project_path(project.namespace, project)
@@ -35,5 +39,9 @@ module Ci
response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
end
+
+ def authorize_read_project!
+ return access_denied! unless can?(current_user, :read_project, project)
+ end
end
end
diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb
index 6ff47c4033a..6d4d4360988 100644
--- a/app/controllers/projects/badges_controller.rb
+++ b/app/controllers/projects/badges_controller.rb
@@ -2,11 +2,12 @@ class Projects::BadgesController < Projects::ApplicationController
before_action :no_cache_headers
def build
+ badge = Gitlab::Badge::Build.new(project, params[:ref])
+
respond_to do |format|
format.html { render_404 }
format.svg do
- image = Ci::ImageForBuildService.new.execute(project, ref: params[:ref])
- send_file(image.path, filename: image.name, disposition: 'inline', type: 'image/svg+xml')
+ send_data(badge.data, type: badge.type, disposition: 'inline')
end
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 877b39c9b1b..6d649e72f84 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -68,7 +68,13 @@ class Projects::IssuesController < Projects::ApplicationController
@merge_requests = @issue.referenced_merge_requests(current_user)
@related_branches = @issue.related_branches - @merge_requests.map(&:source_branch)
- respond_with(@issue)
+ respond_to do |format|
+ format.html
+ format.json do
+ render json: @issue.to_json(include: [:milestone, :labels])
+ end
+ end
+
end
def create
@@ -107,10 +113,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
format.json do
- render json: {
- saved: @issue.valid?,
- assignee_avatar_url: @issue.assignee.try(:avatar_url)
- }
+ render json: @issue.to_json(include: [:milestone, :labels, assignee: { methods: :avatar_url }])
end
end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index b830d777752..49064f5d505 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -57,8 +57,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
respond_to do |format|
format.html
format.json { render json: @merge_request }
- format.diff { render text: @merge_request.to_diff(current_user) }
- format.patch { render text: @merge_request.to_patch(current_user) }
+ format.diff { render text: @merge_request.to_diff }
+ format.patch { render text: @merge_request.to_patch }
end
end
@@ -154,10 +154,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request.target_project, @merge_request])
end
format.json do
- render json: {
- saved: @merge_request.valid?,
- assignee_avatar_url: @merge_request.assignee.try(:avatar_url)
- }
+ render json: @merge_request.to_json(include: [:milestone, :labels, assignee: { methods: :avatar_url }])
end
end
else
@@ -227,14 +224,22 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def ci_status
- ci_service = @merge_request.source_project.ci_service
- status = ci_service.commit_status(merge_request.last_commit.sha, merge_request.source_branch)
+ ci_commit = @merge_request.ci_commit
+ if ci_commit
+ status = ci_commit.status
+ coverage = ci_commit.try(:coverage)
+ else
+ ci_service = @merge_request.source_project.ci_service
+ status = ci_service.commit_status(merge_request.last_commit.sha, merge_request.source_branch) if ci_service
- if ci_service.respond_to?(:commit_coverage)
- coverage = ci_service.commit_coverage(merge_request.last_commit.sha, merge_request.source_branch)
+ if ci_service.respond_to?(:commit_coverage)
+ coverage = ci_service.commit_coverage(merge_request.last_commit.sha, merge_request.source_branch)
+ end
end
response = {
+ title: merge_request.title,
+ sha: merge_request.last_commit_short_sha,
status: status,
coverage: coverage
}
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index b2e974eff17..f7b6d137bde 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -19,13 +19,12 @@ class Projects::MilestonesController < Projects::ApplicationController
end
@milestones = @milestones.includes(:project)
-
respond_to do |format|
format.html do
@milestones = @milestones.page(params[:page])
end
format.json do
- render json: @milestones
+ render json: @milestones.to_json(methods: :name)
end
end
end
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index b578b419a46..6d2901a24a4 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -3,7 +3,7 @@ class Projects::SnippetsController < Projects::ApplicationController
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
# Allow read any snippet
- before_action :authorize_read_project_snippet!
+ before_action :authorize_read_project_snippet!, except: [:new, :create, :index]
# Allow write(create) snippet
before_action :authorize_create_project_snippet!, only: [:new, :create]
@@ -81,6 +81,10 @@ class Projects::SnippetsController < Projects::ApplicationController
@snippet ||= @project.snippets.find(params[:id])
end
+ def authorize_read_project_snippet!
+ return render_404 unless can?(current_user, :read_project_snippet, @snippet)
+ end
+
def authorize_update_project_snippet!
return render_404 unless can?(current_user, :update_project_snippet, @snippet)
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 928817ba811..8c3a74c8236 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -71,7 +71,7 @@ class ProjectsController < Projects::ApplicationController
def remove_fork
return access_denied! unless can?(current_user, :remove_fork_project, @project)
- if @project.unlink_fork
+ if ::Projects::UnlinkForkService.new(@project, current_user).execute
flash[:notice] = 'The fork relationship has been removed.'
end
end
@@ -138,7 +138,7 @@ class ProjectsController < Projects::ApplicationController
participants = ::Projects::ParticipantsService.new(@project, current_user).execute(note_type, note_id)
@suggestions = {
- emojis: autocomplete_emojis,
+ emojis: AwardEmoji.urls,
issues: autocomplete.issues,
mergerequests: autocomplete.merge_requests,
members: participants
@@ -235,17 +235,6 @@ class ProjectsController < Projects::ApplicationController
)
end
- def autocomplete_emojis
- Rails.cache.fetch("autocomplete-emoji-#{Gemojione::VERSION}") do
- Emoji.emojis.map do |name, emoji|
- {
- name: name,
- path: view_context.image_url("#{emoji["unicode"]}.png")
- }
- end
- end
- end
-
def repo_exists?
project.repository_exists? && !project.empty_repo?
end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 046286dd9e1..f1df6832bf6 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -243,7 +243,7 @@ class IssuableFinder
end
def filter_by_upcoming_milestone?
- params[:milestone_title] == '#upcoming'
+ params[:milestone_title] == Milestone::Upcoming.name
end
def by_milestone(items)
@@ -252,7 +252,7 @@ class IssuableFinder
items = items.where(milestone_id: [-1, nil])
elsif filter_by_upcoming_milestone?
upcoming = Milestone.where(project_id: projects).upcoming
- items = items.joins(:milestone).where(milestones: { title: upcoming.title })
+ items = items.joins(:milestone).where(milestones: { title: upcoming.try(:title) })
else
items = items.joins(:milestone).where(milestones: { title: params[:milestone_title] })
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 23693629a4c..60a0ff32c9c 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -3,10 +3,6 @@ module ApplicationSettingsHelper
current_application_settings.gravatar_enabled?
end
- def twitter_sharing_enabled?
- current_application_settings.twitter_sharing_enabled?
- end
-
def signup_enabled?
current_application_settings.signup_enabled?
end
diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb
index 316a10b7da3..14697f774cc 100644
--- a/app/helpers/dropdowns_helper.rb
+++ b/app/helpers/dropdowns_helper.rb
@@ -60,7 +60,7 @@ module DropdownsHelper
title_output << content_tag(:span, title)
title_output << content_tag(:button, class: "dropdown-title-button dropdown-menu-close", aria: { label: "Close" }, type: "button") do
- icon('times')
+ icon('times', class: 'dropdown-menu-close-icon')
end
title_output.html_safe
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index d3e5e3aa8b9..592bad8ba24 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -214,4 +214,12 @@ module EventsHelper
end
end
end
+
+ def event_row_class(event)
+ if event.body?
+ "event-block"
+ else
+ "event-inline"
+ end
+ end
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 81df2094392..b14b8218d02 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -16,6 +16,16 @@ module IssuablesHelper
base_issuable_scope(issuable).where('iid > ?', issuable.iid).last
end
+ def issuable_json_path(issuable)
+ project = issuable.project
+
+ if issuable.kind_of?(MergeRequest)
+ namespace_project_merge_request_path(project.namespace, project, issuable.iid, :json)
+ else
+ namespace_project_issue_path(project.namespace, project, issuable.iid, :json)
+ end
+ end
+
def prev_issuable_for(issuable)
base_issuable_scope(issuable).where('iid < ?', issuable.iid).first
end
@@ -37,6 +47,14 @@ module IssuablesHelper
end
end
+ def milestone_dropdown_label(milestone_title, default_label = "Milestone")
+ if milestone_title == Milestone::Upcoming.name
+ milestone_title = Milestone::Upcoming.title
+ end
+
+ h(milestone_title.presence || default_label)
+ end
+
private
def sidebar_gutter_collapsed?
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index 53c543c28c5..698f90cb27a 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -5,8 +5,10 @@ module NotesHelper
end
def note_target_fields(note)
- hidden_field_tag(:target_type, note.noteable.class.name.underscore) +
- hidden_field_tag(:target_id, note.noteable.id)
+ if note.noteable
+ hidden_field_tag(:target_type, note.noteable.class.name.underscore) +
+ hidden_field_tag(:target_id, note.noteable.id)
+ end
end
def note_editable?(note)
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 494dad0b41e..8a97a74ad73 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -1,4 +1,5 @@
module SearchHelper
+
def search_autocomplete_opts(term)
return unless current_user
@@ -23,45 +24,44 @@ module SearchHelper
# Autocomplete results for various settings pages
def default_autocomplete
[
- { label: "Profile settings", url: profile_path },
- { label: "SSH Keys", url: profile_keys_path },
- { label: "Dashboard", url: root_path },
- { label: "Admin Section", url: admin_root_path },
+ { category: "Settings", label: "Profile settings", url: profile_path },
+ { category: "Settings", label: "SSH Keys", url: profile_keys_path },
+ { category: "Settings", label: "Dashboard", url: root_path },
+ { category: "Settings", label: "Admin Section", url: admin_root_path },
]
end
# Autocomplete results for internal help pages
def help_autocomplete
[
- { label: "help: API Help", url: help_page_path("api", "README") },
- { label: "help: Markdown Help", url: help_page_path("markdown", "markdown") },
- { label: "help: Permissions Help", url: help_page_path("permissions", "permissions") },
- { label: "help: Public Access Help", url: help_page_path("public_access", "public_access") },
- { label: "help: Rake Tasks Help", url: help_page_path("raketasks", "README") },
- { label: "help: SSH Keys Help", url: help_page_path("ssh", "README") },
- { label: "help: System Hooks Help", url: help_page_path("system_hooks", "system_hooks") },
- { label: "help: Webhooks Help", url: help_page_path("web_hooks", "web_hooks") },
- { label: "help: Workflow Help", url: help_page_path("workflow", "README") },
+ { category: "Help", label: "API Help", url: help_page_path("api", "README") },
+ { category: "Help", label: "Markdown Help", url: help_page_path("markdown", "markdown") },
+ { category: "Help", label: "Permissions Help", url: help_page_path("permissions", "permissions") },
+ { category: "Help", label: "Public Access Help", url: help_page_path("public_access", "public_access") },
+ { category: "Help", label: "Rake Tasks Help", url: help_page_path("raketasks", "README") },
+ { category: "Help", label: "SSH Keys Help", url: help_page_path("ssh", "README") },
+ { category: "Help", label: "System Hooks Help", url: help_page_path("system_hooks", "system_hooks") },
+ { category: "Help", label: "Webhooks Help", url: help_page_path("web_hooks", "web_hooks") },
+ { category: "Help", label: "Workflow Help", url: help_page_path("workflow", "README") },
]
end
# Autocomplete results for the current project, if it's defined
def project_autocomplete
if @project && @project.repository.exists? && @project.repository.root_ref
- prefix = search_result_sanitize(@project.name_with_namespace)
ref = @ref || @project.repository.root_ref
[
- { label: "#{prefix} - Files", url: namespace_project_tree_path(@project.namespace, @project, ref) },
- { label: "#{prefix} - Commits", url: namespace_project_commits_path(@project.namespace, @project, ref) },
- { label: "#{prefix} - Network", url: namespace_project_network_path(@project.namespace, @project, ref) },
- { label: "#{prefix} - Graph", url: namespace_project_graph_path(@project.namespace, @project, ref) },
- { label: "#{prefix} - Issues", url: namespace_project_issues_path(@project.namespace, @project) },
- { label: "#{prefix} - Merge Requests", url: namespace_project_merge_requests_path(@project.namespace, @project) },
- { label: "#{prefix} - Milestones", url: namespace_project_milestones_path(@project.namespace, @project) },
- { label: "#{prefix} - Snippets", url: namespace_project_snippets_path(@project.namespace, @project) },
- { label: "#{prefix} - Members", url: namespace_project_project_members_path(@project.namespace, @project) },
- { label: "#{prefix} - Wiki", url: namespace_project_wikis_path(@project.namespace, @project) },
+ { category: "Current Project", label: "Files", url: namespace_project_tree_path(@project.namespace, @project, ref) },
+ { category: "Current Project", label: "Commits", url: namespace_project_commits_path(@project.namespace, @project, ref) },
+ { category: "Current Project", label: "Network", url: namespace_project_network_path(@project.namespace, @project, ref) },
+ { category: "Current Project", label: "Graph", url: namespace_project_graph_path(@project.namespace, @project, ref) },
+ { category: "Current Project", label: "Issues", url: namespace_project_issues_path(@project.namespace, @project) },
+ { category: "Current Project", label: "Merge Requests", url: namespace_project_merge_requests_path(@project.namespace, @project) },
+ { category: "Current Project", label: "Milestones", url: namespace_project_milestones_path(@project.namespace, @project) },
+ { category: "Current Project", label: "Snippets", url: namespace_project_snippets_path(@project.namespace, @project) },
+ { category: "Current Project", label: "Members", url: namespace_project_project_members_path(@project.namespace, @project) },
+ { category: "Current Project", label: "Wiki", url: namespace_project_wikis_path(@project.namespace, @project) },
]
else
[]
@@ -72,7 +72,9 @@ module SearchHelper
def groups_autocomplete(term, limit = 5)
current_user.authorized_groups.search(term).limit(limit).map do |group|
{
- label: "group: #{search_result_sanitize(group.name)}",
+ category: "Groups",
+ id: group.id,
+ label: "#{search_result_sanitize(group.name)}",
url: group_path(group)
}
end
@@ -83,7 +85,10 @@ module SearchHelper
current_user.authorized_projects.search_by_title(term).
sorted_by_stars.non_archived.limit(limit).map do |p|
{
- label: "project: #{search_result_sanitize(p.name_with_namespace)}",
+ category: "Projects",
+ id: p.id,
+ value: "#{search_result_sanitize(p.name)}",
+ label: "#{search_result_sanitize(p.name_with_namespace)}",
url: namespace_project_path(p.namespace, p)
}
end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 8cbc9eefc7b..826e5f96fa1 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -110,6 +110,10 @@ class Notify < BaseMailer
headers['Reply-To'] = address
+ fallback_reply_message_id = "<reply-#{reply_key}@#{Gitlab.config.gitlab.host}>".freeze
+ headers['References'] ||= ''
+ headers['References'] << ' ' << fallback_reply_message_id
+
@reply_by_email = true
end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index fa2345f6faa..c0bf6def7c5 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -27,6 +27,8 @@ class Ability
case true
when subject.is_a?(PersonalSnippet)
anonymous_personal_snippet_abilities(subject)
+ when subject.is_a?(ProjectSnippet)
+ anonymous_project_snippet_abilities(subject)
when subject.is_a?(CommitStatus)
anonymous_commit_status_abilities(subject)
when subject.is_a?(Project) || subject.respond_to?(:project)
@@ -100,6 +102,14 @@ class Ability
end
end
+ def anonymous_project_snippet_abilities(snippet)
+ if snippet.public?
+ [:read_project_snippet]
+ else
+ []
+ end
+ end
+
def global_abilities(user)
rules = []
rules << :create_group if user.can_create_group
@@ -338,24 +348,22 @@ class Ability
end
end
- [:note, :project_snippet].each do |name|
- define_method "#{name}_abilities" do |user, subject|
- rules = []
-
- if subject.author == user
- rules += [
- :"read_#{name}",
- :"update_#{name}",
- :"admin_#{name}"
- ]
- end
+ def note_abilities(user, note)
+ rules = []
- if subject.respond_to?(:project) && subject.project
- rules += project_abilities(user, subject.project)
- end
+ if note.author == user
+ rules += [
+ :read_note,
+ :update_note,
+ :admin_note
+ ]
+ end
- rules
+ if note.respond_to?(:project) && note.project
+ rules += project_abilities(user, note.project)
end
+
+ rules
end
def personal_snippet_abilities(user, snippet)
@@ -376,6 +384,24 @@ class Ability
rules
end
+ def project_snippet_abilities(user, snippet)
+ rules = []
+
+ if snippet.author == user || user.admin?
+ rules += [
+ :read_project_snippet,
+ :update_project_snippet,
+ :admin_project_snippet
+ ]
+ end
+
+ if snippet.public? || (snippet.internal? && !user.external?) || (snippet.private? && snippet.project.team.member?(user))
+ rules << :read_project_snippet
+ end
+
+ rules
+ end
+
def group_member_abilities(user, subject)
rules = []
target_user = subject.user
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index c4879598c4e..052cd874733 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -12,7 +12,6 @@
# updated_at :datetime
# home_page_url :string(255)
# default_branch_protection :integer default(2)
-# twitter_sharing_enabled :boolean default(TRUE)
# restricted_visibility_levels :text
# version_check_enabled :boolean default(TRUE)
# max_attachment_size :integer default(10), not null
@@ -140,7 +139,6 @@ class ApplicationSetting < ActiveRecord::Base
default_branch_protection: Settings.gitlab['default_branch_protection'],
signup_enabled: Settings.gitlab['signup_enabled'],
signin_enabled: Settings.gitlab['signin_enabled'],
- twitter_sharing_enabled: Settings.gitlab['twitter_sharing_enabled'],
gravatar_enabled: Settings.gravatar['enabled'],
sign_in_text: Settings.extra['sign_in_text'],
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
diff --git a/app/models/commit.rb b/app/models/commit.rb
index d0dbe009d0d..d09876a07d9 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -74,14 +74,14 @@ class Commit
#
# This pattern supports cross-project references.
def self.reference_pattern
- %r{
+ @reference_pattern ||= %r{
(?:#{Project.reference_pattern}#{reference_prefix})?
(?<commit>\h{7,40})
}x
end
def self.link_reference_pattern
- super("commit", /(?<commit>\h{7,40})/)
+ @link_reference_pattern ||= super("commit", /(?<commit>\h{7,40})/)
end
def to_reference(from_project = nil)
diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb
index 289dbc57287..51673897d98 100644
--- a/app/models/commit_range.rb
+++ b/app/models/commit_range.rb
@@ -43,14 +43,14 @@ class CommitRange
#
# This pattern supports cross-project references.
def self.reference_pattern
- %r{
+ @reference_pattern ||= %r{
(?:#{Project.reference_pattern}#{reference_prefix})?
(?<commit_range>#{STRICT_PATTERN})
}x
end
def self.link_reference_pattern
- super("compare", /(?<commit_range>#{PATTERN})/)
+ @link_reference_pattern ||= super("compare", /(?<commit_range>#{PATTERN})/)
end
# Initialize a CommitRange
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index cf5b2c71675..afa2ca039ae 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -19,6 +19,7 @@ module Issuable
has_many :notes, as: :noteable, dependent: :destroy
has_many :label_links, as: :target, dependent: :destroy
has_many :labels, through: :label_links
+ has_many :todos, as: :target, dependent: :destroy
validates :author, presence: true
validates :title, presence: true, length: { within: 0..255 }
@@ -41,7 +42,7 @@ module Issuable
scope :join_project, -> { joins(:project) }
scope :references_project, -> { references(:project) }
- scope :non_archived, -> { join_project.merge(Project.non_archived.only(:where)) }
+ scope :non_archived, -> { join_project.where(projects: { archived: false }) }
delegate :name,
:email,
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
index 2ca79df0a29..b8585d4e577 100644
--- a/app/models/external_issue.rb
+++ b/app/models/external_issue.rb
@@ -31,7 +31,7 @@ class ExternalIssue
# Pattern used to extract `JIRA-123` issue references from text
def self.reference_pattern
- %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
+ @reference_pattern ||= %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
end
def to_reference(_from_project = nil)
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index 97bd79af083..da7c265a371 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -14,6 +14,7 @@ class GlobalMilestone
def initialize(title, milestones)
@title = title
+ @name = title
@milestones = milestones
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index ed960cb39f4..e064b0f8b95 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -73,14 +73,14 @@ class Issue < ActiveRecord::Base
#
# This pattern supports cross-project references.
def self.reference_pattern
- %r{
+ @reference_pattern ||= %r{
(#{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)}(?<issue>\d+)
}x
end
def self.link_reference_pattern
- super("issues", /(?<issue>\d+)/)
+ @link_reference_pattern ||= super("issues", /(?<issue>\d+)/)
end
def to_reference(from_project = nil)
diff --git a/app/models/label.rb b/app/models/label.rb
index 500d5a35521..55c01cae762 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -56,7 +56,7 @@ class Label < ActiveRecord::Base
# This pattern supports cross-project references.
#
def self.reference_pattern
- %r{
+ @reference_pattern ||= %r{
(#{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)}
(?:
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index ef48207f956..bf185cb5dd8 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -135,6 +135,7 @@ class MergeRequest < ActiveRecord::Base
scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
scope :of_projects, ->(ids) { where(target_project_id: ids) }
+ scope :from_project, ->(project) { where(source_project_id: project.id) }
scope :merged, -> { with_state(:merged) }
scope :closed_and_merged, -> { with_states(:closed, :merged) }
@@ -149,14 +150,14 @@ class MergeRequest < ActiveRecord::Base
#
# This pattern supports cross-project references.
def self.reference_pattern
- %r{
+ @reference_pattern ||= %r{
(#{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)}(?<merge_request>\d+)
}x
end
def self.link_reference_pattern
- super("merge_requests", /(?<merge_request>\d+)/)
+ @link_reference_pattern ||= super("merge_requests", /(?<merge_request>\d+)/)
end
# Returns all the merge requests from an ActiveRecord:Relation.
@@ -279,7 +280,7 @@ class MergeRequest < ActiveRecord::Base
WIP_REGEX = /\A\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze
def work_in_progress?
- title =~ WIP_REGEX
+ !!(title =~ WIP_REGEX)
end
def wipless_title
@@ -331,15 +332,15 @@ class MergeRequest < ActiveRecord::Base
# Returns the raw diff for this merge request
#
# see "git diff"
- def to_diff(current_user)
- target_project.repository.diff_text(target_branch, source_sha)
+ def to_diff
+ target_project.repository.diff_text(diff_base_commit.sha, source_sha)
end
# Returns the commit as a series of email patches.
#
# see "git format-patch"
- def to_patch(current_user)
- target_project.repository.format_patch(target_branch, source_sha)
+ def to_patch
+ target_project.repository.format_patch(diff_base_commit.sha, source_sha)
end
def hook_attrs
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index bbd59eab9ae..986184dd301 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -79,7 +79,7 @@ class Milestone < ActiveRecord::Base
end
def self.link_reference_pattern
- super("milestones", /(?<milestone>\d+)/)
+ @link_reference_pattern ||= super("milestones", /(?<milestone>\d+)/)
end
def self.upcoming
@@ -89,7 +89,7 @@ class Milestone < ActiveRecord::Base
def to_reference(from_project = nil)
escaped_title = self.title.gsub("]", "\\]")
- h = Gitlab::Application.routes.url_helpers
+ h = Gitlab::Routing.url_helpers
url = h.namespace_project_milestone_url(self.project.namespace, self.project, self)
"[#{escaped_title}](#{url})"
diff --git a/app/models/note.rb b/app/models/note.rb
index b0c33f2eec5..87ced65c650 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -311,7 +311,7 @@ class Note < ActiveRecord::Base
for_merge_request? && for_diff_line?
end
- def for_project_snippet?
+ def for_snippet?
noteable_type == "Snippet"
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 941e444a4f8..7b1188420ef 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -206,6 +206,8 @@ class Project < ActiveRecord::Base
mount_uploader :avatar, AvatarUploader
# Scopes
+ default_scope { where(pending_delete: false) }
+
scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) }
scope :sorted_by_stars, -> { reorder('projects.star_count DESC') }
scope :sorted_by_names, -> { joins(:namespace).reorder('namespaces.name ASC, projects.name ASC') }
@@ -491,7 +493,7 @@ class Project < ActiveRecord::Base
end
def web_url
- Gitlab::Application.routes.url_helpers.namespace_project_url(self.namespace, self)
+ Gitlab::Routing.url_helpers.namespace_project_url(self.namespace, self)
end
def web_url_without_protocol
@@ -612,7 +614,7 @@ class Project < ActiveRecord::Base
if avatar.present?
[gitlab_config.url, avatar.url].join
elsif avatar_in_git
- Gitlab::Application.routes.url_helpers.namespace_project_avatar_url(namespace, self)
+ Gitlab::Routing.url_helpers.namespace_project_avatar_url(namespace, self)
end
end
@@ -951,16 +953,6 @@ class Project < ActiveRecord::Base
self.builds_enabled = true
end
- def unlink_fork
- if forked?
- forked_from_project.lfs_objects.find_each do |lfs_object|
- lfs_object.projects << self
- end
-
- forked_project_link.destroy
- end
- end
-
def any_runners?(&block)
if runners.active.any?(&block)
return true
diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb
index 05436cd0f79..eaa5654b9c6 100644
--- a/app/models/project_services/gitlab_issue_tracker_service.rb
+++ b/app/models/project_services/gitlab_issue_tracker_service.rb
@@ -20,7 +20,7 @@
#
class GitlabIssueTrackerService < IssueTrackerService
- include Gitlab::Application.routes.url_helpers
+ include Gitlab::Routing.url_helpers
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index aba37921c09..1ed42c4f3e7 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -21,7 +21,7 @@
class JiraService < IssueTrackerService
include HTTParty
- include Gitlab::Application.routes.url_helpers
+ include Gitlab::Routing.url_helpers
DEFAULT_API_VERSION = 2
diff --git a/app/models/project_services/slack_service/issue_message.rb b/app/models/project_services/slack_service/issue_message.rb
index 5af24a80609..438ff33fdff 100644
--- a/app/models/project_services/slack_service/issue_message.rb
+++ b/app/models/project_services/slack_service/issue_message.rb
@@ -22,7 +22,7 @@ class SlackService
@issue_url = obj_attr[:url]
@action = obj_attr[:action]
@state = obj_attr[:state]
- @description = obj_attr[:description]
+ @description = obj_attr[:description] || ''
end
def attachments
diff --git a/app/models/repository.rb b/app/models/repository.rb
index c07e8072043..e80c2238402 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -72,7 +72,7 @@ class Repository
return @has_visible_content unless @has_visible_content.nil?
@has_visible_content = cache.fetch(:has_visible_content?) do
- raw_repository.branch_count > 0
+ branch_count > 0
end
end
@@ -173,7 +173,7 @@ class Repository
end
def branch_names
- cache.fetch(:branch_names) { raw_repository.branch_names }
+ cache.fetch(:branch_names) { branches.map(&:name) }
end
def tag_names
@@ -191,7 +191,7 @@ class Repository
end
def branch_count
- @branch_count ||= cache.fetch(:branch_count) { raw_repository.branch_count }
+ @branch_count ||= cache.fetch(:branch_count) { branches.size }
end
def tag_count
@@ -239,7 +239,7 @@ class Repository
def expire_branches_cache
cache.expire(:branch_names)
- @branches = nil
+ @local_branches = nil
end
def expire_cache(branch_name = nil, revision = nil)
@@ -335,6 +335,8 @@ class Repository
# Runs code just before a repository is deleted.
def before_delete
+ expire_exists_cache
+
expire_cache if exists?
expire_root_ref_cache
@@ -612,10 +614,14 @@ class Repository
refs_contains_sha('tag', sha)
end
- def branches
- @branches ||= raw_repository.branches
+ def local_branches
+ @local_branches ||= rugged.branches.each(:local).map do |branch|
+ Gitlab::Git::Branch.new(branch.name, branch.target)
+ end
end
+ alias_method :branches, :local_branches
+
def tags
@tags ||= raw_repository.tags
end
@@ -818,7 +824,7 @@ class Repository
end
def fetch_ref(source_path, source_ref, target_ref)
- args = %W(#{Gitlab.config.git.bin_path} fetch -f #{source_path} #{source_ref}:#{target_ref})
+ args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
Gitlab::Popen.popen(args, path_to_repo)
end
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index b9e835a4486..b96e3937281 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -56,14 +56,14 @@ class Snippet < ActiveRecord::Base
#
# This pattern supports cross-project references.
def self.reference_pattern
- %r{
+ @reference_pattern ||= %r{
(#{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)}(?<snippet>\d+)
}x
end
def self.link_reference_pattern
- super("snippets", /(?<snippet>\d+)/)
+ @link_reference_pattern ||= super("snippets", /(?<snippet>\d+)/)
end
def to_reference(from_project = nil)
diff --git a/app/models/user.rb b/app/models/user.rb
index 128ddc2a694..2b0bee2099f 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -408,6 +408,8 @@ class User < ActiveRecord::Base
end
def owns_notification_email
+ return if self.temp_oauth_email?
+
self.errors.add(:notification_email, "is not an email you own") unless self.all_emails.include?(self.notification_email)
end
diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb
index a5efb21fab6..82e7090f1ea 100644
--- a/app/services/issues/move_service.rb
+++ b/app/services/issues/move_service.rb
@@ -43,7 +43,7 @@ module Issues
def create_new_issue
new_params = { id: nil, iid: nil, label_ids: [], milestone: nil,
project: @new_project, author: @old_issue.author,
- description: unfold_references(@old_issue.description) }
+ description: rewrite_content(@old_issue.description) }
new_params = @old_issue.serializable_hash.merge(new_params)
CreateService.new(@new_project, @current_user, new_params).execute
@@ -53,7 +53,7 @@ module Issues
@old_issue.notes.find_each do |note|
new_note = note.dup
new_params = { project: @new_project, noteable: @new_issue,
- note: unfold_references(new_note.note),
+ note: rewrite_content(new_note.note),
created_at: note.created_at,
updated_at: note.updated_at }
@@ -61,6 +61,18 @@ module Issues
end
end
+ def rewrite_content(content)
+ return unless content
+
+ rewriters = [Gitlab::Gfm::ReferenceRewriter,
+ Gitlab::Gfm::UploadsRewriter]
+
+ rewriters.inject(content) do |text, klass|
+ rewriter = klass.new(text, @old_project, @current_user)
+ rewriter.rewrite(@new_project)
+ end
+ end
+
def close_issue
close_service = CloseService.new(@old_project, @current_user)
close_service.execute(@old_issue, notifications: false, system_note: false)
@@ -78,20 +90,12 @@ module Issues
direction: :to)
end
- def unfold_references(content)
- return unless content
-
- rewriter = Gitlab::Gfm::ReferenceRewriter.new(content, @old_project,
- @current_user)
- rewriter.rewrite(@new_project)
+ def mark_as_moved
+ @old_issue.update(moved_to: @new_issue)
end
def notify_participants
notification_service.issue_moved(@old_issue, @new_issue, @current_user)
end
-
- def mark_as_moved
- @old_issue.update(moved_to: @new_issue)
- end
end
end
diff --git a/app/services/projects/unlink_fork_service.rb b/app/services/projects/unlink_fork_service.rb
new file mode 100644
index 00000000000..315c3e16292
--- /dev/null
+++ b/app/services/projects/unlink_fork_service.rb
@@ -0,0 +1,19 @@
+module Projects
+ class UnlinkForkService < BaseService
+ def execute
+ return unless @project.forked?
+
+ @project.forked_from_project.lfs_objects.find_each do |lfs_object|
+ lfs_object.projects << @project
+ end
+
+ merge_requests = @project.forked_from_project.merge_requests.opened.from_project(@project)
+
+ merge_requests.each do |mr|
+ MergeRequests::CloseService.new(@project, @current_user).execute(mr)
+ end
+
+ @project.forked_project_link.destroy
+ end
+ end
+end
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index ea2b26ccb52..f0615ec7420 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -95,17 +95,19 @@ class SystemHooksService
end
def project_member_data(model)
+ project = model.project || Project.unscoped.find(model.source_id)
+
{
- project_name: model.project.name,
- project_path: model.project.path,
- project_path_with_namespace: model.project.path_with_namespace,
- project_id: model.project.id,
- user_username: model.user.username,
- user_name: model.user.name,
- user_email: model.user.email,
- user_id: model.user.id,
- access_level: model.human_access,
- project_visibility: Project.visibility_levels.key(model.project.visibility_level_field).downcase
+ project_name: project.name,
+ project_path: project.path,
+ project_path_with_namespace: project.path_with_namespace,
+ project_id: project.id,
+ user_username: model.user.username,
+ user_name: model.user.name,
+ user_email: model.user.email,
+ user_id: model.user.id,
+ access_level: model.human_access,
+ project_visibility: Project.visibility_levels.key(project.visibility_level_field).downcase
}
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index e022a046c48..658b086496f 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -224,7 +224,7 @@ class SystemNoteService
#
# "Started branch `issue-branch-button-201`"
def self.new_issue_branch(issue, project, author, branch)
- h = Gitlab::Application.routes.url_helpers
+ h = Gitlab::Routing.url_helpers
link = h.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch)
body = "Started branch [`#{branch}`](#{link})"
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index f2662922e90..42c5bca90fd 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -123,7 +123,7 @@ class TodoService
def handle_note(note, author)
# Skip system notes, and notes on project snippet
- return if note.system? || note.for_project_snippet?
+ return if note.system? || note.for_snippet?
project = note.project
target = note.noteable
@@ -170,14 +170,30 @@ class TodoService
end
def filter_mentioned_users(project, target, author)
- mentioned_users = target.mentioned_users.select do |user|
- user.can?(:read_project, project)
- end
-
+ mentioned_users = target.mentioned_users
+ mentioned_users = reject_users_without_access(mentioned_users, project, target)
mentioned_users.delete(author)
mentioned_users.uniq
end
+ def reject_users_without_access(users, project, target)
+ if target.is_a?(Note) && target.for_issue?
+ target = target.noteable
+ end
+
+ if target.is_a?(Issue)
+ select_users(users, :read_issue, target)
+ else
+ select_users(users, :read_project, project)
+ end
+ end
+
+ def select_users(users, ability, subject)
+ users.select do |user|
+ user.can?(ability.to_sym, subject)
+ end
+ end
+
def pending_todos(user, criteria = {})
valid_keys = [:project_id, :target_id, :target_type, :commit_id]
user.todos.pending.where(criteria.slice(*valid_keys))
diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb
index 86d24469e05..1af9e9b0edb 100644
--- a/app/uploaders/file_uploader.rb
+++ b/app/uploaders/file_uploader.rb
@@ -1,14 +1,15 @@
# encoding: utf-8
class FileUploader < CarrierWave::Uploader::Base
include UploaderHelper
+ MARKDOWN_PATTERN = %r{\!?\[.*?\]\(/uploads/(?<secret>[0-9a-f]{32})/(?<file>.*?)\)}
storage :file
attr_accessor :project, :secret
- def initialize(project, secret = self.class.generate_secret)
+ def initialize(project, secret = nil)
@project = project
- @secret = secret
+ @secret = secret || self.class.generate_secret
end
def base_dir
@@ -23,14 +24,14 @@ class FileUploader < CarrierWave::Uploader::Base
File.join(base_dir, 'tmp', @project.path_with_namespace, @secret)
end
- def self.generate_secret
- SecureRandom.hex
- end
-
def secure_url
File.join("/uploads", @secret, file.filename)
end
+ def to_markdown
+ to_h[:markdown]
+ end
+
def to_h
filename = image? ? self.file.basename : self.file.filename
escaped_filename = filename.gsub("]", "\\]")
@@ -45,4 +46,8 @@ class FileUploader < CarrierWave::Uploader::Base
markdown: markdown
}
end
+
+ def self.generate_secret
+ SecureRandom.hex
+ end
end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 0350995d03d..de86dacbb12 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -77,13 +77,6 @@
= f.check_box :gravatar_enabled
Gravatar enabled
.form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :twitter_sharing_enabled do
- = f.check_box :twitter_sharing_enabled, :'aria-describedby' => 'twitter_help_block'
- Twitter enabled
- %span.help-block#twitter_help_block Show users a button to share their newly created public or internal projects on twitter
- .form-group
= f.label :default_projects_limit, class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :default_projects_limit, class: 'form-control'
diff --git a/app/views/ci/projects/index.html.haml b/app/views/ci/projects/index.html.haml
deleted file mode 100644
index 9c2290bc4a5..00000000000
--- a/app/views/ci/projects/index.html.haml
+++ /dev/null
@@ -1,20 +0,0 @@
-.wiki
- %h1
- GitLab CI is now integrated in GitLab UI
- %h2 For existing projects
-
- %p
- Check the following pages to find the CI status you're looking for:
-
- %ul
- %li Projects page - shows CI status for each project.
- %li Project commits page - show CI status for each commit.
-
-
-
- %h2 For new projects
-
- %p
- If you want to enable CI for a new project it is easy as adding
- = link_to ".gitlab-ci.yml", "http://doc.gitlab.com/ce/ci/yaml/README.html"
- file to your repository
diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml
index e3a4d64df01..aa0aff86d4d 100644
--- a/app/views/dashboard/todos/_todo.html.haml
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -1,4 +1,4 @@
-%li{class: "todo todo-#{todo.done? ? 'done' : 'pending'}", id: dom_id(todo) }
+%li{class: "todo todo-#{todo.done? ? 'done' : 'pending'}", id: dom_id(todo), data:{url: todo_target_path(todo)} }
.todo-item.todo-block
= image_tag avatar_icon(todo.author_email, 40), class: 'avatar s40', alt:''
@@ -10,7 +10,10 @@
(removed)
%span.todo-label
= todo_action_name(todo)
- = todo_target_link(todo)
+ - if todo.target
+ = todo_target_link(todo)
+ - else
+ (removed)
&middot; #{time_ago_with_tooltip(todo.created_at)}
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index 42c2764e7e2..4d20dd5830e 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -1,5 +1,5 @@
- if event.visible_to_user?(current_user)
- .event-item{class: "#{event.body? ? "event-block" : "event-inline" }"}
+ .event-item{ class: event_row_class(event) }
.event-item-timestamp
#{time_ago_with_tooltip(event.created_at)}
diff --git a/app/views/events/event/_created_project.html.haml b/app/views/events/event/_created_project.html.haml
index 8cf36c711b4..5a2a469ba62 100644
--- a/app/views/events/event/_created_project.html.haml
+++ b/app/views/events/event/_created_project.html.haml
@@ -7,21 +7,3 @@
= link_to_project event.project
- else
= event.project_name
-
-- if !event.project.private? && twitter_sharing_enabled?
- .event-body{"data-user-is" => event.author_id}
- .event-note
- .md
- %p
- Congratulations! Why not share your accomplishment with the world?
-
- %a.twitter-share-button{ |
- href: "https://twitter.com/share", |
- "data-url" => event.project.web_url, |
- "data-text" => "I just #{event.action_name} a new project on GitLab! GitLab is version control on your server.", |
- "data-size" => "medium", |
- "data-related" => "gitlab", |
- "data-hashtags" => "gitlab", |
- "data-count" => "none"}
- Tweet
- %script{src: "//platform.twitter.com/widgets.js"}
diff --git a/app/views/layouts/_collapse_button.html.haml b/app/views/layouts/_collapse_button.html.haml
deleted file mode 100644
index 2ed51d87ca1..00000000000
--- a/app/views/layouts/_collapse_button.html.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-- if nav_menu_collapsed?
- = link_to icon('angle-right'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
-- else
- = link_to icon('angle-left'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index c799e9c588d..9be36273c7d 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -1,5 +1,7 @@
.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
= render "layouts/broadcast"
+ .expand-nav
+ = link_to icon('bars'), '#', class: 'toggle-nav-collapse', title: "Open sidebar"
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo
%a#logo
@@ -8,15 +10,19 @@
.gitlab-text-container
%h3 GitLab
- - if defined?(sidebar) && sidebar
- = render "layouts/nav/#{sidebar}"
- - elsif current_user
- = render 'layouts/nav/dashboard'
+ - primary_sidebar = current_user ? 'dashboard' : 'explore'
+
+ - if defined?(sidebar) && sidebar && sidebar != primary_sidebar
+ .complex-sidebar
+ .nav-primary
+ = render "layouts/nav/#{primary_sidebar}"
+ .nav-secondary
+ = render "layouts/nav/#{sidebar}"
- else
- = render 'layouts/nav/explore'
+ = render "layouts/nav/#{primary_sidebar}"
.collapse-nav
- = render partial: 'layouts/collapse_button'
+ = link_to icon('angle-left'), '#', class: 'toggle-nav-collapse', title: "Hide sidebar"
- if current_user
= link_to current_user, class: 'sidebar-user', title: "Profile" do
= image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 54af2c3063c..9d4ab9847a8 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -1,10 +1,33 @@
-.search
- = form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f|
- = search_field_tag "search", nil, placeholder: 'Search', class: "search-input form-control", spellcheck: false, tabindex: "1"
+- if controller.controller_path =~ /^groups/
+ - label = 'This group'
+- if controller.controller_path =~ /^projects/
+ - label = 'This project'
+
+.search.search-form{class: "#{'has-location-badge' if label.present?}"}
+ = form_tag search_path, method: :get, class: 'navbar-form' do |f|
+ .search-input-container
+ .search-location-badge
+ - if label.present?
+ %span.location-badge
+ %i.location-text
+ = label
+ .search-input-wrap
+ .dropdown{ data: {url: search_autocomplete_path } }
+ = search_field_tag "search", nil, placeholder: 'Search', class: "search-input dropdown-menu-toggle", spellcheck: false, tabindex: "1", autocomplete: 'off', data: { toggle: 'dropdown' }
+ .dropdown-menu.dropdown-select
+ = dropdown_content do
+ %ul
+ %li
+ %a.is-focused.dropdown-menu-empty-link
+ Loading...
+ = dropdown_loading
+ %i.search-icon
+ %i.clear-icon.js-clear-input
+
= hidden_field_tag :group_id, @group.try(:id)
- - if @project && @project.persisted?
- = hidden_field_tag :project_id, @project.id
+ = hidden_field_tag :project_id, @project && @project.persisted? ? @project.id : '', id: 'search_project_id'
+ - if @project && @project.persisted?
- if current_controller?(:issues)
= hidden_field_tag :scope, 'issues'
- elsif current_controller?(:merge_requests)
@@ -21,10 +44,3 @@
= hidden_field_tag :repository_ref, @ref
= button_tag 'Go' if ENV['RAILS_ENV'] == 'test'
.search-autocomplete-opts.hide{:'data-autocomplete-path' => search_autocomplete_path, :'data-autocomplete-project-id' => @project.try(:id), :'data-autocomplete-project-ref' => @ref }
-
-:javascript
- $('.search-input').on('keyup', function(e) {
- if (e.keyCode == 27) {
- $('.search-input').blur();
- }
- });
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index 280a1b93729..22d1d4d8597 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -95,7 +95,7 @@
Spam Logs
%span.count= number_with_delimiter(SpamLog.count(:all))
- = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
+ = nav_link(controller: :application_settings) do
= link_to admin_application_settings_path, title: 'Settings' do
= icon('cogs fw')
%span
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 4a0069f18f8..d1a180e4299 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -15,12 +15,12 @@
= icon('dashboard fw')
%span
Activity
- = nav_link(controller: :groups) do
+ = nav_link(path: ['dashboard/groups#index', 'explore/groups#index']) do
= link_to dashboard_groups_path, title: 'Groups' do
= icon('group fw')
%span
Groups
- = nav_link(controller: :milestones) do
+ = nav_link(path: 'dashboard#milestones') do
= link_to dashboard_milestones_path, title: 'Milestones' do
= icon('clock-o fw')
%span
@@ -48,7 +48,6 @@
%span
Help
- %li.separate-item
= nav_link(controller: :profile) do
= link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
= icon('user fw')
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index 55940741dc0..0b7de9633ec 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -1,12 +1,4 @@
%ul.nav.nav-sidebar
- = nav_link do
- = link_to root_path, title: 'Go to dashboard', class: 'back-link' do
- = icon('caret-square-o-left fw')
- %span
- Go to dashboard
-
- %li.separate-item
-
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: 'Home' do
= icon('group fw')
@@ -42,7 +34,7 @@
%span
Members
- if can?(current_user, :admin_group, @group)
- = nav_link(html_options: { class: "separate-item" }) do
+ = nav_link do
= link_to edit_group_path(@group), title: 'Settings' do
= icon ('cogs fw')
%span
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index 3b9d31a6fc5..cc119fd64e6 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -1,12 +1,4 @@
%ul.nav.nav-sidebar
- = nav_link do
- = link_to root_path, title: 'Go to dashboard', class: 'back-link' do
- = icon('caret-square-o-left fw')
- %span
- Go to dashboard
-
- %li.separate-item
-
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile Settings' do
= icon('user fw')
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 86b46e8c75e..d0f82b5f57f 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -1,19 +1,4 @@
%ul.nav.nav-sidebar
- - if @project.group
- = nav_link do
- = link_to group_path(@project.group), title: 'Go to group', class: 'back-link' do
- = icon('caret-square-o-left fw')
- %span
- Go to group
- - else
- = nav_link do
- = link_to root_path, title: 'Go to dashboard', class: 'back-link' do
- = icon('caret-square-o-left fw')
- %span
- Go to dashboard
-
- %li.separate-item
-
= nav_link(path: 'projects#show', html_options: {class: 'home'}) do
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
= icon('bookmark fw')
@@ -113,7 +98,7 @@
Snippets
- if project_nav_tab? :settings
- = nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do
+ = nav_link(html_options: {class: "#{project_tab_class}"}) do
= link_to edit_project_path(@project), title: 'Settings' do
= icon('cogs fw')
%span
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index de80abd7f4d..3d15c0d932b 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -56,19 +56,20 @@
.prepend-top-default
= f.submit 'Update settings', class: "btn btn-create"
%hr
- %h5
- Groups (#{@group_members.count})
- %div
- %ul.bordered-list
- - @group_members.each do |group_member|
- - notification = Notification.new(group_member)
- = render 'settings', type: 'group', membership: group_member, notification: notification
- %h5
- Projects (#{@project_members.count})
- %p.account-well
- To specify the notification level per project of a group you belong to, you need to be a member of the project itself, not only its group.
- .append-bottom-default
- %ul.bordered-list
- - @project_members.each do |project_member|
- - notification = Notification.new(project_member)
- = render 'settings', type: 'project', membership: project_member, notification: notification
+.col-lg-9.col-lg-push-3
+ %h5
+ Groups (#{@group_members.count})
+ %div
+ %ul.bordered-list
+ - @group_members.each do |group_member|
+ - notification = Notification.new(group_member)
+ = render 'settings', type: 'group', membership: group_member, notification: notification
+ %h5
+ Projects (#{@project_members.count})
+ %p.account-well
+ To specify the notification level per project of a group you belong to, you need to be a member of the project itself, not only its group.
+ .append-bottom-default
+ %ul.bordered-list
+ - @project_members.each do |project_member|
+ - notification = Notification.new(project_member)
+ = render 'settings', type: 'project', membership: project_member, notification: notification
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index e7c85edff96..1e4c46fca2f 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -3,25 +3,32 @@
%a.btn.dropdown-toggle{href: '#', "data-toggle" => "dropdown"}
= icon('plus')
%ul.dropdown-menu.dropdown-menu-right.project-home-dropdown
- - if can?(current_user, :create_issue, @project)
+ - can_create_issue = can?(current_user, :create_issue, @project)
+ - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
+ - can_create_snippet = can?(current_user, :create_snippet, @project)
+
+ - if can_create_issue
%li
= link_to url_for_new_issue(@project, only_path: true) do
= icon('exclamation-circle fw')
New issue
- - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
+
- if merge_project
%li
= link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project) do
= icon('tasks fw')
New merge request
- - if can?(current_user, :create_snippet, @project)
+
+ - if can_create_snippet
%li
= link_to new_namespace_project_snippet_path(@project.namespace, @project) do
= icon('file-text-o fw')
New snippet
- - if can?(current_user, :push_code, @project)
+ - if can_create_issue || merge_project || can_create_snippet
%li.divider
+
+ - if can?(current_user, :push_code, @project)
%li
= link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do
= icon('file fw')
@@ -35,13 +42,11 @@
= icon('tags fw')
New tag
- elsif current_user && current_user.already_forked?(@project)
- %li.divider
%li
= link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do
= icon('file fw')
New file
- elsif can?(current_user, :fork_project, @project)
- %li.divider
%li
- continue_params = { to: namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master'),
notice: edit_in_new_fork_notice,
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
index 88cbb7c03c5..5fb5fe5af2f 100644
--- a/app/views/projects/buttons/_fork.html.haml
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -12,7 +12,7 @@
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has-tooltip' do
= icon('code-fork fw')
Fork
- %div.count-with-arrow
+ = link_to namespace_project_forks_path(@project.namespace, @project), class: 'count-with-arrow' do
%span.arrow
%span.count
= @project.forks_count
diff --git a/app/views/projects/diffs/_image.html.haml b/app/views/projects/diffs/_image.html.haml
index 8367112a9cb..2731219ccad 100644
--- a/app/views/projects/diffs/_image.html.haml
+++ b/app/views/projects/diffs/_image.html.haml
@@ -1,7 +1,10 @@
- diff = diff_file.diff
- file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, diff.new_path))
-- old_commit_id = diff_refs.first.id
-- old_file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(old_commit_id, diff.old_path))
+// diff_refs will be nil for orphaned commits (e.g. first commit in repo)
+- if diff_refs
+ - old_commit_id = diff_refs.first.id
+ - old_file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(old_commit_id, diff.old_path))
+
- if diff.renamed_file || diff.new_file || diff.deleted_file
.image
%span.wrap
diff --git a/app/views/projects/find_file/show.html.haml b/app/views/projects/find_file/show.html.haml
index 905f6bbbd48..1fe1d98bf13 100644
--- a/app/views/projects/find_file/show.html.haml
+++ b/app/views/projects/find_file/show.html.haml
@@ -2,7 +2,7 @@
- header_title project_title(@project, "Files", project_files_path(@project))
.file-finder-holder.tree-holder.clearfix
- .gray-content-block.top-block
+ .nav-block
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'find_file', path: @path
%ul.breadcrumb.repo-breadcrumb
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index ee5b9fd95a8..1dd8f721f7e 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -10,7 +10,7 @@
.merge-request{'data-url' => merge_request_path(@merge_request)}
= render "projects/merge_requests/show/mr_title"
- .merge-request-details.issuable-details
+ .merge-request-details.issuable-details{data: {id: @merge_request.project.id}}
= render "projects/merge_requests/show/mr_box"
.append-bottom-default.mr-source-target.prepend-top-default
- if @merge_request.open?
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index b05ab869215..2ec0d20a879 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -1,15 +1,17 @@
- if @ci_commit
.mr-widget-heading
- .ci_widget{class: "ci-#{@ci_commit.status}"}
- = ci_status_icon(@ci_commit)
- %span
- Build
- = ci_status_label(@ci_commit)
- for
- = succeed "." do
- = link_to @ci_commit.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @ci_commit.sha), class: "monospace"
- %span.ci-coverage
- = link_to "View details", builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "js-show-tab", data: {action: 'builds'}
+ - %w[success skipped canceled failed running pending].each do |status|
+ .ci_widget{ class: "ci-#{status}", style: ("display:none" unless @ci_commit.status == status) }
+ = ci_icon_for_status(status)
+ %span
+ CI build
+ = ci_label_for_status(status)
+ for
+ - commit = @merge_request.last_commit
+ = succeed "." do
+ = link_to @ci_commit.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @ci_commit.sha), class: "monospace"
+ %span.ci-coverage
+ = link_to "View details", builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "js-show-tab", data: {action: 'builds'}
- elsif @merge_request.has_ci?
- # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX
@@ -43,5 +45,5 @@
:javascript
$(function() {
- merge_request_widget.getCiStatus();
+ merge_request_widget.getCIStatus(false);
});
diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml
index a489d4f9b24..2be06aebe6c 100644
--- a/app/views/projects/merge_requests/widget/_show.html.haml
+++ b/app/views/projects/merge_requests/widget/_show.html.haml
@@ -9,12 +9,17 @@
:javascript
var merge_request_widget;
-
- merge_request_widget = new MergeRequestWidget({
- url_to_automerge_check: "#{merge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
+ var opts = {
+ merge_check_url: "#{merge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
check_enable: #{@merge_request.unchecked? ? "true" : "false"},
- url_to_ci_check: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
+ ci_status_url: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
+ gitlab_icon: "#{asset_path 'gitlab_logo.png'}",
+ ci_status: "",
+ ci_message: "Build {{status}} for \"{{title}}\"",
ci_enable: #{@project.ci_service ? "true" : "false"},
- current_status: "#{@merge_request.gitlab_merge_status}",
- });
+ builds_path: "#{builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}"
+ };
+ if(typeof merge_request_widget === 'undefined') {
+ merge_request_widget = new MergeRequestWidget(opts);
+ }
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 2cf32e6093d..34fe1743f4b 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -5,28 +5,21 @@
= image_tag avatar_icon(note.author), alt: '', class: 'avatar s40'
.timeline-content
.note-header
+ = link_to_member(note.project, note.author, avatar: false)
+ .inline.note-headline-light
+ = "#{note.author.to_reference} commented"
+ %a{ href: "##{dom_id(note)}" }
+ = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
- if note_editable?(note)
.note-actions
- = link_to '#', title: 'Edit comment', class: 'js-note-edit' do
+ - access = note.project.team.human_max_access(note.author.id)
+ - if access
+ %span.note-role
+ = access
+ = link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
= icon('pencil-square-o')
-
- = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'js-note-delete danger' do
+ = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do
= icon('trash-o')
-
- - unless note.system
- - access = note.project.team.human_max_access(note.author.id)
- - if access
- %span.note-role.label
- = access
-
- = link_to_member(note.project, note.author, avatar: false)
-
- %span.author-username
- = '@' + note.author.username
-
- %span.note-last-update
- %a{name: dom_id(note), href: "##{dom_id(note)}", title: 'Link here'}
- = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note_created_ago')
.note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
.note-text
= preserve do
diff --git a/app/views/projects/notes/discussions/_active.html.haml b/app/views/projects/notes/discussions/_active.html.haml
index 4f15a99d061..cd8a5f0bd02 100644
--- a/app/views/projects/notes/discussions/_active.html.haml
+++ b/app/views/projects/notes/discussions/_active.html.haml
@@ -1,22 +1,20 @@
- note = discussion_notes.first
.discussion.js-toggle-container{ class: note.discussion_id }
.discussion-header
+ = link_to_member(@project, note.author, avatar: false)
+ .inline.discussion-headline-light
+ = "#{note.author.to_reference} started a discussion"
+ = link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do
+ on the diff
.discussion-actions
- = link_to "#", class: "js-toggle-button" do
+ = link_to "#", class: "discussion-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-up
Show/hide discussion
- %div
- = link_to_member(@project, note.author, avatar: false)
- started a discussion
- = link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do
- %strong on the diff
.last-update.hide.js-toggle-content
- last_note = discussion_notes.last
last updated by
= link_to_member(@project, last_note.author, avatar: false)
-
- %span.discussion-last-update
- #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
+ #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
.discussion-body.js-toggle-content
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
diff --git a/app/views/projects/notes/discussions/_commit.html.haml b/app/views/projects/notes/discussions/_commit.html.haml
index 3da2f2060b8..46f2ba4bbcf 100644
--- a/app/views/projects/notes/discussions/_commit.html.haml
+++ b/app/views/projects/notes/discussions/_commit.html.haml
@@ -1,20 +1,22 @@
- note = discussion_notes.first
+- commit = note.noteable
+- commit_description = commit ? 'commit' : 'a deleted commit'
.discussion.js-toggle-container{ class: note.discussion_id }
.discussion-header
+ = link_to_member(@project, note.author, avatar: false)
+ .inline.discussion-headline-light
+ = "#{note.author.to_reference} started a discussion on #{commit_description}"
+ - if commit
+ = link_to(commit.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable), class: 'monospace')
.discussion-actions
- = link_to "#", class: "js-toggle-button" do
+ = link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-up
Show/hide discussion
- %div
- = link_to_member(@project, note.author, avatar: false)
- started a discussion on commit
- = link_to(note.noteable.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable), class: 'monospace')
.last-update.hide.js-toggle-content
- last_note = discussion_notes.last
last updated by
= link_to_member(@project, last_note.author, avatar: false)
- %span.discussion-last-update
- #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
+ #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
.discussion-body.js-toggle-content
- if note.for_diff_line?
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
diff --git a/app/views/projects/notes/discussions/_outdated.html.haml b/app/views/projects/notes/discussions/_outdated.html.haml
index 218b0da3977..f8e000b424f 100644
--- a/app/views/projects/notes/discussions/_outdated.html.haml
+++ b/app/views/projects/notes/discussions/_outdated.html.haml
@@ -1,19 +1,18 @@
- note = discussion_notes.first
.discussion.js-toggle-container{ class: note.discussion_id }
.discussion-header
+ = link_to_member(@project, note.author, avatar: false)
+ .inline.discussion-headline-light
+ = "#{note.author.to_reference} started a discussion"
+ on the outdated diff
.discussion-actions
- = link_to "#", class: "js-toggle-button" do
+ = link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-down
Show/hide discussion
- %div
- = link_to_member(@project, note.author, avatar: false)
- started a discussion on the
- %strong outdated diff
- %div
+ .last-update.hide.js-toggle-content
- last_note = discussion_notes.last
last updated by
= link_to_member(@project, last_note.author, avatar: false)
- %span.discussion-last-update
- #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
+ #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
.discussion-body.js-toggle-content.hide
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index ba69569b1e7..1c5f8b3928b 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -15,11 +15,11 @@
- if current_user
%li
- if !on_top_of_branch?
- %span.btn.btn-sm.add-to-tree.disabled.has-tooltip{title: "You can only add files when you are on a branch", data: { container: 'body' }}
+ %span.btn.add-to-tree.disabled.has-tooltip{title: "You can only add files when you are on a branch", data: { container: 'body' }}
= icon('plus')
- else
%span.dropdown
- %a.dropdown-toggle.btn.btn-sm.add-to-tree{href: '#', "data-toggle" => "dropdown"}
+ %a.dropdown-toggle.btn.add-to-tree{href: '#', "data-toggle" => "dropdown"}
= icon('plus')
%ul.dropdown-menu
- if can_edit_tree?
diff --git a/app/views/search/results/_note.html.haml b/app/views/search/results/_note.html.haml
index 5fcba2b7e93..9544e3d3e17 100644
--- a/app/views/search/results/_note.html.haml
+++ b/app/views/search/results/_note.html.haml
@@ -1,24 +1,20 @@
- project = note.project
+- note_url = Gitlab::UrlBuilder.new(:note).build(note.id)
+- noteable_identifier = note.noteable.try(:iid) || note.noteable.id
.search-result-row
%h5.note-search-caption.str-truncated
%i.fa.fa-comment
= link_to_member(project, note.author, avatar: false)
commented on
+ = link_to project.name_with_namespace, project
+ &middot;
- if note.for_commit?
- = link_to project do
- = project.name_with_namespace
- &middot;
- = link_to namespace_project_commit_path(project.namespace, project, note.commit_id, anchor: dom_id(note)) do
- Commit #{truncate_sha(note.commit_id)}
+ = link_to "Commit #{truncate_sha(note.commit_id)}", note_url
- else
- = link_to project do
- = project.name_with_namespace
- &middot;
- %span #{note.noteable_type.titleize} ##{note.noteable.iid}
+ %span #{note.noteable_type.titleize} ##{noteable_identifier}
&middot;
- = link_to [project.namespace.becomes(Namespace), project, note.noteable, anchor: dom_id(note)] do
- = note.noteable.title
+ = link_to note.noteable.title, note_url
.note-search-result
.term
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index f91ff0e3694..921eaefd79a 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -7,15 +7,15 @@
class: "check_all_issues left"
.issues-other-filters
.filter-item.inline
- - if params[:author_id]
+ - if params[:author_id].present?
= hidden_field_tag(:author_id, params[:author_id])
- = dropdown_tag(user_dropdown_label(params[:author_id], "Author"), options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author",
+ = dropdown_tag(user_dropdown_label(params[:author_id], "Author"), options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit",
placeholder: "Search authors", data: { any_user: "Any Author", first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: "author_id", default_label: "Author" } })
.filter-item.inline
- - if params[:assignee_id]
+ - if params[:assignee_id].present?
= hidden_field_tag(:assignee_id, params[:assignee_id])
- = dropdown_tag(user_dropdown_label(params[:assignee_id], "Assignee"), options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee",
+ = dropdown_tag(user_dropdown_label(params[:assignee_id], "Assignee"), options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (@project.id if @project), selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } })
.filter-item.inline.milestone-filter
@@ -23,7 +23,6 @@
.filter-item.inline.labels-filter
= render "shared/issuable/label_dropdown"
-
.pull-right
= render 'shared/sort_dropdown'
@@ -38,11 +37,10 @@
%li
%a{href: "#", data: {id: "close"}} Closed
.filter-item.inline
- = dropdown_tag("Assignee", options: { toggle_class: "js-user-search js-update-assignee", title: "Assign to", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable",
+ = dropdown_tag("Assignee", options: { toggle_class: "js-user-search js-update-assignee js-filter-submit js-filter-bulk-update", title: "Assign to", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable",
placeholder: "Search authors", data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: @project.id, field_name: "update[assignee_id]" } })
.filter-item.inline
- = dropdown_tag("Milestone", options: { title: "Assign milestone", toggle_class: 'js-milestone-select', filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone",
- placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), use_id: true } })
+ = dropdown_tag("Milestone", options: { title: "Assign milestone", toggle_class: 'js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update', filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), use_id: true } })
= hidden_field_tag 'update[issues_ids]', []
= hidden_field_tag :state_event, params[:state_event]
.filter-item.inline
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 178223fb463..e2a9e5bfb92 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -14,7 +14,7 @@
- if issuable.is_a?(MergeRequest)
%p.help-block
.js-wip-explanation
- %a.js-toggle-wip{href: ""}
+ %a.js-toggle-wip{href: "", tabindex: -1}
Remove the
%code WIP:
prefix from the title
@@ -22,7 +22,7 @@
%strong Work In Progress
merge request to be merged when it's ready.
.js-no-wip-explanation
- %a.js-toggle-wip{href: ""}
+ %a.js-toggle-wip{href: "", tabindex: -1}
Start the title with
%code WIP:
to prevent a
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
index 006a34a11e3..fd5e58c1f1f 100644
--- a/app/views/shared/issuable/_label_dropdown.html.haml
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -1,4 +1,4 @@
-- if params[:label_name]
+- if params[:label_name].present?
= hidden_field_tag(:label_name, params[:label_name])
.dropdown
%button.dropdown-menu-toggle.js-label-select.js-filter-submit{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}}
diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml
index 0434506c8d7..2fcf40ece99 100644
--- a/app/views/shared/issuable/_milestone_dropdown.html.haml
+++ b/app/views/shared/issuable/_milestone_dropdown.html.haml
@@ -1,7 +1,7 @@
-- if params[:milestone_title]
+- if params[:milestone_title].present?
= hidden_field_tag(:milestone_title, params[:milestone_title])
-= dropdown_tag(h(params[:milestone_title].presence || "Milestone"), options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable",
- placeholder: "Search milestones", footer_content: @project.present?, data: { show_no: true, show_any: true, field_name: "milestone_title", selected: params[:milestone_title], project_id: @project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do
+= dropdown_tag(milestone_dropdown_label(params[:milestone_title]), options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable",
+ placeholder: "Search milestones", footer_content: @project.present?, data: { show_no: true, show_any: true, show_upcoming: true, field_name: "milestone_title", selected: params[:milestone_title], project_id: @project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do
- if @project
%ul.dropdown-footer-list
- if can? current_user, :admin_milestone, @project
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 0e20e86356d..47e544acf52 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -28,6 +28,7 @@
= icon('user')
.title.hide-collapsed
Assignee
+ = icon('spinner spin', class: 'block-loading')
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
= link_to 'Edit', '#', class: 'edit-link pull-right'
.value.bold.hide-collapsed
@@ -39,10 +40,14 @@
%span.username
= issuable.assignee.to_reference
- else
- .light None
+ %span.assign-yourself
+ No assignee -
+ %a.js-assign-yourself{ href: '#' }
+ assign yourself
.selectbox.hide-collapsed
- = users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true, current_user: true, first_user: true)
+ = f.hidden_field 'assignee_id', value: issuable.assignee_id, id: 'issue_assignee_id'
+ = dropdown_tag('Select assignee', options: { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), field_name: "#{issuable.to_ability_name}[assignee_id]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } })
.block.milestone
.sidebar-collapsed-icon
@@ -54,6 +59,7 @@
No
.title.hide-collapsed
Milestone
+ = icon('spinner spin', class: 'block-loading')
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
= link_to 'Edit', '#', class: 'edit-link pull-right'
.value.bold.hide-collapsed
@@ -62,10 +68,10 @@
= issuable.milestone.title
- else
.light None
+
.selectbox.hide-collapsed
- = f.select(:milestone_id, milestone_options(issuable), { include_blank: true }, { class: 'select2 select2-compact js-select2 js-milestone', data: { placeholder: 'Select milestone' }})
- = hidden_field_tag :issuable_context
- = f.submit class: 'btn hide'
+ = f.hidden_field 'milestone_id', value: issuable.milestone_id, id: nil
+ = dropdown_tag('Milestone', options: { title: 'Assign milestone', toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: 'Search milestones', data: { show_no: true, field_name: "#{issuable.to_ability_name}[milestone_id]", project_id: @project.id, issuable_id: issuable.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), use_id: true }})
- if issuable.project.labels.any?
.block.labels
@@ -75,6 +81,7 @@
= issuable.labels.count
.title.hide-collapsed
Labels
+ = icon('spinner spin', class: 'block-loading')
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
= link_to 'Edit', '#', class: 'edit-link pull-right'
.value.bold.issuable-show-labels.hide-collapsed{ class: ("has-labels" if issuable.labels.any?) }
@@ -84,8 +91,31 @@
- else
.light None
.selectbox.hide-collapsed
- = f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
- { selected: issuable.label_ids }, multiple: true, class: 'select2 js-select2', data: { placeholder: "Select labels" }
+ - issuable.labels.each do |label|
+ = hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil
+ .dropdown
+ %button.dropdown-menu-toggle.js-label-select.js-multiselect{type: "button", data: {toggle: "dropdown", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", project_id: (@project.id if @project), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}}
+ %span.dropdown-toggle-text
+ Label
+ = icon('chevron-down')
+ .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
+ .dropdown-page-one
+ = dropdown_title("Assign labels")
+ = dropdown_filter("Search labels")
+ = dropdown_content
+ - if @project
+ = dropdown_footer do
+ %ul.dropdown-footer-list
+ - if can? current_user, :admin_label, @project
+ %li
+ %a.dropdown-toggle-page{href: "#"}
+ Create new
+ %li
+ = link_to namespace_project_labels_path(@project.namespace, @project) do
+ - if can? current_user, :admin_label, @project
+ Manage labels
+ - else
+ View labels
= render "shared/issuable/participants", participants: issuable.participants(current_user)
- if current_user
@@ -116,5 +146,8 @@
= clipboard_button(clipboard_text: project_ref)
:javascript
- new Subscription('.subscription');
- new IssuableContext();
+ new MilestoneSelect('{"namespace":"#{@project.namespace.path}","path":"#{@project.path}"}');
+ new LabelsSelect();
+ new IssuableContext('#{current_user.to_json(only: [:username, :id, :name])}');
+ new Subscription('.subscription')
+ new Sidebar(); \ No newline at end of file
diff --git a/app/workers/project_cache_worker.rb b/app/workers/project_cache_worker.rb
index 55cb6af232e..ccefd0f71a0 100644
--- a/app/workers/project_cache_worker.rb
+++ b/app/workers/project_cache_worker.rb
@@ -5,6 +5,9 @@ class ProjectCacheWorker
def perform(project_id)
project = Project.find(project_id)
+
+ return unless project.repository.exists?
+
project.update_repository_size
project.update_commit_count
diff --git a/app/workers/project_destroy_worker.rb b/app/workers/project_destroy_worker.rb
index d06e4480292..b51c6a266c9 100644
--- a/app/workers/project_destroy_worker.rb
+++ b/app/workers/project_destroy_worker.rb
@@ -5,7 +5,7 @@ class ProjectDestroyWorker
def perform(project_id, user_id, params)
begin
- project = Project.find(project_id)
+ project = Project.unscoped.find(project_id)
rescue ActiveRecord::RecordNotFound
return
end
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 500b745f55e..fb1c3476f65 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -106,7 +106,7 @@ production: &base
enabled: false
# The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
- # The `%{key}` placeholder is added after the user part, after a `+` character, before the `@`.
+ # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`).
address: "gitlab-incoming+%{key}@gmail.com"
# Email account username
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 626268d7648..2b989015279 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -174,7 +174,6 @@ end
Settings.gitlab['time_zone'] ||= nil
Settings.gitlab['signup_enabled'] ||= true if Settings.gitlab['signup_enabled'].nil?
Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil?
-Settings.gitlab['twitter_sharing_enabled'] ||= true if Settings.gitlab['twitter_sharing_enabled'].nil?
Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)' if Settings.gitlab['issue_closing_pattern'].nil?
diff --git a/config/mail_room.yml b/config/mail_room.yml
index aed55f74eab..60257329f3e 100644
--- a/config/mail_room.yml
+++ b/config/mail_room.yml
@@ -17,7 +17,7 @@ if File.exists?(config_file)
config['start_tls'] = false if config['start_tls'].nil?
config['mailbox'] = "inbox" if config['mailbox'].nil?
- if config['enabled'] && config['address'] && config['address'].include?('%{key}')
+ if config['enabled'] && config['address']
redis_url = Gitlab::RedisConfig.new(rails_env).url
%>
-
diff --git a/db/migrate/20130218141258_convert_closed_to_state_in_issue.rb b/db/migrate/20130218141258_convert_closed_to_state_in_issue.rb
index 9fa96203ffd..99289166e81 100644
--- a/db/migrate/20130218141258_convert_closed_to_state_in_issue.rb
+++ b/db/migrate/20130218141258_convert_closed_to_state_in_issue.rb
@@ -1,14 +1,18 @@
class ConvertClosedToStateInIssue < ActiveRecord::Migration
+ include Gitlab::Database
+
def up
- Issue.transaction do
- Issue.where(closed: true).update_all(state: :closed)
- Issue.where(closed: false).update_all(state: :opened)
- end
+ execute "UPDATE #{table_name} SET state = 'closed' WHERE closed = #{true_value}"
+ execute "UPDATE #{table_name} SET state = 'opened' WHERE closed = #{false_value}"
end
def down
- Issue.transaction do
- Issue.where(state: :closed).update_all(closed: true)
- end
+ execute "UPDATE #{table_name} SET closed = #{true_value} WHERE state = 'closed'"
+ end
+
+ private
+
+ def table_name
+ Issue.table_name
end
end
diff --git a/db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb b/db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb
index ebb7ae585e6..bd1e016d679 100644
--- a/db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb
+++ b/db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb
@@ -1,16 +1,20 @@
class ConvertClosedToStateInMergeRequest < ActiveRecord::Migration
+ include Gitlab::Database
+
def up
- MergeRequest.transaction do
- MergeRequest.where(closed: true, merged: true).update_all(state: :merged)
- MergeRequest.where(closed: true, merged: false).update_all(state: :closed)
- MergeRequest.where(closed: false).update_all(state: :opened)
- end
+ execute "UPDATE #{table_name} SET state = 'merged' WHERE closed = #{true_value} AND merged = #{true_value}"
+ execute "UPDATE #{table_name} SET state = 'closed' WHERE closed = #{true_value} AND merged = #{false_value}"
+ execute "UPDATE #{table_name} SET state = 'opened' WHERE closed = #{false_value}"
end
def down
- MergeRequest.transaction do
- MergeRequest.where(state: :closed).update_all(closed: true)
- MergeRequest.where(state: :merged).update_all(closed: true, merged: true)
- end
+ execute "UPDATE #{table_name} SET closed = #{true_value} WHERE state = 'closed'"
+ execute "UPDATE #{table_name} SET closed = #{true_value}, merged = #{true_value} WHERE state = 'merged'"
+ end
+
+ private
+
+ def table_name
+ MergeRequest.table_name
end
end
diff --git a/db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb b/db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb
index 1978ea89153..d1174bc3d98 100644
--- a/db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb
+++ b/db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb
@@ -1,14 +1,18 @@
class ConvertClosedToStateInMilestone < ActiveRecord::Migration
+ include Gitlab::Database
+
def up
- Milestone.transaction do
- Milestone.where(closed: true).update_all(state: :closed)
- Milestone.where(closed: false).update_all(state: :active)
- end
+ execute "UPDATE #{table_name} SET state = 'closed' WHERE closed = #{true_value}"
+ execute "UPDATE #{table_name} SET state = 'active' WHERE closed = #{false_value}"
end
def down
- Milestone.transaction do
- Milestone.where(state: :closed).update_all(closed: true)
- end
+ execute "UPDATE #{table_name} SET closed = #{true_value} WHERE state = 'cloesd'"
+ end
+
+ private
+
+ def table_name
+ Milestone.table_name
end
end
diff --git a/db/migrate/20130220125544_convert_merge_status_in_merge_request.rb b/db/migrate/20130220125544_convert_merge_status_in_merge_request.rb
index b310b35e373..1c758c56ffe 100644
--- a/db/migrate/20130220125544_convert_merge_status_in_merge_request.rb
+++ b/db/migrate/20130220125544_convert_merge_status_in_merge_request.rb
@@ -1,17 +1,19 @@
class ConvertMergeStatusInMergeRequest < ActiveRecord::Migration
def up
- MergeRequest.transaction do
- MergeRequest.where(merge_status: 1).update_all("new_merge_status = 'unchecked'")
- MergeRequest.where(merge_status: 2).update_all("new_merge_status = 'can_be_merged'")
- MergeRequest.where(merge_status: 3).update_all("new_merge_status = 'cannot_be_merged'")
- end
+ execute "UPDATE #{table_name} SET new_merge_status = 'unchecked' WHERE merge_status = 1"
+ execute "UPDATE #{table_name} SET new_merge_status = 'can_be_merged' WHERE merge_status = 2"
+ execute "UPDATE #{table_name} SET new_merge_status = 'cannot_be_merged' WHERE merge_status = 3"
end
def down
- MergeRequest.transaction do
- MergeRequest.where(new_merge_status: :unchecked).update_all("merge_status = 1")
- MergeRequest.where(new_merge_status: :can_be_merged).update_all("merge_status = 2")
- MergeRequest.where(new_merge_status: :cannot_be_merged).update_all("merge_status = 3")
- end
+ execute "UPDATE #{table_name} SET merge_status = 1 WHERE new_merge_status = 'unchecked'"
+ execute "UPDATE #{table_name} SET merge_status = 2 WHERE new_merge_status = 'can_be_merged'"
+ execute "UPDATE #{table_name} SET merge_status = 3 WHERE new_merge_status = 'cannot_be_merged'"
+ end
+
+ private
+
+ def table_name
+ MergeRequest.table_name
end
end
diff --git a/db/migrate/20130419190306_allow_merges_for_forks.rb b/db/migrate/20130419190306_allow_merges_for_forks.rb
index 56ce58a846d..56ea97e8561 100644
--- a/db/migrate/20130419190306_allow_merges_for_forks.rb
+++ b/db/migrate/20130419190306_allow_merges_for_forks.rb
@@ -1,7 +1,7 @@
class AllowMergesForForks < ActiveRecord::Migration
def self.up
add_column :merge_requests, :target_project_id, :integer, :null => true
- MergeRequest.update_all("target_project_id = project_id")
+ execute "UPDATE #{table_name} SET target_project_id = project_id"
change_column :merge_requests, :target_project_id, :integer, :null => false
rename_column :merge_requests, :project_id, :source_project_id
end
@@ -10,4 +10,10 @@ class AllowMergesForForks < ActiveRecord::Migration
remove_column :merge_requests, :target_project_id
rename_column :merge_requests, :source_project_id,:project_id
end
+
+ private
+
+ def table_name
+ MergeRequest.table_name
+ end
end
diff --git a/db/migrate/20160324020319_remove_todos_for_deleted_issues.rb b/db/migrate/20160324020319_remove_todos_for_deleted_issues.rb
new file mode 100644
index 00000000000..1fff9759d1e
--- /dev/null
+++ b/db/migrate/20160324020319_remove_todos_for_deleted_issues.rb
@@ -0,0 +1,17 @@
+class RemoveTodosForDeletedIssues < ActiveRecord::Migration
+ def up
+ execute <<-SQL
+ DELETE FROM todos
+ WHERE todos.target_type = 'Issue'
+ AND NOT EXISTS (
+ SELECT *
+ FROM issues
+ WHERE issues.id = todos.target_id
+ AND issues.deleted_at IS NULL
+ )
+ SQL
+ end
+
+ def down
+ end
+end
diff --git a/db/migrate/20160329144452_add_index_on_pending_delete_projects.rb b/db/migrate/20160329144452_add_index_on_pending_delete_projects.rb
new file mode 100644
index 00000000000..275554e736e
--- /dev/null
+++ b/db/migrate/20160329144452_add_index_on_pending_delete_projects.rb
@@ -0,0 +1,6 @@
+class AddIndexOnPendingDeleteProjects < ActiveRecord::Migration
+ def change
+ add_index :projects, :pending_delete
+ end
+end
+
diff --git a/db/migrate/20160331133914_remove_todos_for_deleted_merge_requests.rb b/db/migrate/20160331133914_remove_todos_for_deleted_merge_requests.rb
new file mode 100644
index 00000000000..54cea964ff2
--- /dev/null
+++ b/db/migrate/20160331133914_remove_todos_for_deleted_merge_requests.rb
@@ -0,0 +1,17 @@
+class RemoveTodosForDeletedMergeRequests < ActiveRecord::Migration
+ def up
+ execute <<-SQL
+ DELETE FROM todos
+ WHERE todos.target_type = 'MergeRequest'
+ AND NOT EXISTS (
+ SELECT *
+ FROM merge_requests
+ WHERE merge_requests.id = todos.target_id
+ AND merge_requests.deleted_at IS NULL
+ )
+ SQL
+ end
+
+ def down
+ end
+end
diff --git a/db/migrate/20160331223143_remove_twitter_sharing_enabled_from_application_settings.rb b/db/migrate/20160331223143_remove_twitter_sharing_enabled_from_application_settings.rb
new file mode 100644
index 00000000000..0d736e323b6
--- /dev/null
+++ b/db/migrate/20160331223143_remove_twitter_sharing_enabled_from_application_settings.rb
@@ -0,0 +1,5 @@
+class RemoveTwitterSharingEnabledFromApplicationSettings < ActiveRecord::Migration
+ def change
+ remove_column :application_settings, :twitter_sharing_enabled, :boolean
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 75509c35b05..df4c65a0625 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20160320204112) do
+ActiveRecord::Schema.define(version: 20160331133914) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -417,9 +417,9 @@ ActiveRecord::Schema.define(version: 20160320204112) do
t.string "state"
t.integer "iid"
t.integer "updated_by_id"
+ t.integer "moved_to_id"
t.boolean "confidential", default: false
t.datetime "deleted_at"
- t.integer "moved_to_id"
end
add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree
@@ -748,6 +748,7 @@ ActiveRecord::Schema.define(version: 20160320204112) do
add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree
add_index "projects", ["path"], name: "index_projects_on_path", using: :btree
add_index "projects", ["path"], name: "index_projects_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"}
+ add_index "projects", ["pending_delete"], name: "index_projects_on_pending_delete", using: :btree
add_index "projects", ["runners_token"], name: "index_projects_on_runners_token", using: :btree
add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree
add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree
diff --git a/doc/README.md b/doc/README.md
index e6fa4fc049b..724c7cca0f1 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -19,10 +19,12 @@
## Administrator documentation
+- [Authentication/Authorization](administration/auth/README.md) Configure
+ external authentication with LDAP, SAML, CAS and additional Omniauth providers.
- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when webhooks aren't enough.
- [Install](install/README.md) Requirements, directory structures and installation from source.
- [Restart GitLab](administration/restart_gitlab.md) Learn how to restart GitLab and its components
-- [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter.
+- [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, Twitter.
- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
- [Log system](logs/logs.md) Log system.
diff --git a/doc/administration/auth/README.md b/doc/administration/auth/README.md
new file mode 100644
index 00000000000..07e548aaabe
--- /dev/null
+++ b/doc/administration/auth/README.md
@@ -0,0 +1,11 @@
+# Authentication and Authorization
+
+GitLab integrates with the following external authentication and authorization
+providers.
+
+- [LDAP](ldap.md) Includes Active Directory, Apple Open Directory, Open LDAP,
+ and 389 Server
+- [OmniAuth](../../integration/omniauth.md) Sign in via Twitter, GitHub, GitLab.com, Google,
+ Bitbucket, Facebook, Shibboleth, Crowd and Azure
+- [SAML](../../integration/saml.md) Configure GitLab as a SAML 2.0 Service Provider
+- [CAS](../../integration/cas.md) Configure GitLab to sign in using CAS
diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md
new file mode 100644
index 00000000000..237700bbcd9
--- /dev/null
+++ b/doc/administration/auth/ldap.md
@@ -0,0 +1,277 @@
+# LDAP
+
+GitLab integrates with LDAP to support user authentication.
+This integration works with most LDAP-compliant directory
+servers, including Microsoft Active Directory, Apple Open Directory, Open LDAP,
+and 389 Server. GitLab EE includes enhanced integration, including group
+membership syncing.
+
+## Security
+
+GitLab assumes that LDAP users are not able to change their LDAP 'mail', 'email'
+or 'userPrincipalName' attribute. An LDAP user who is allowed to change their
+email on the LDAP server can potentially
+[take over any account](#enabling-ldap-sign-in-for-existing-gitlab-users)
+on your GitLab server.
+
+We recommend against using LDAP integration if your LDAP users are
+allowed to change their 'mail', 'email' or 'userPrincipalName' attribute on
+the LDAP server.
+
+### User deletion
+
+If a user is deleted from the LDAP server, they will be blocked in GitLab, as
+well. Users will be immediately blocked from logging in. However, there is an
+LDAP check cache time (sync time) of one hour (see note). This means users that
+are already logged in or are using Git over SSH will still be able to access
+GitLab for up to one hour. Manually block the user in the GitLab Admin area to
+immediately block all access.
+
+>**Note**: GitLab EE supports a configurable sync time, with a default
+of one hour.
+
+## Configuration
+
+To enable LDAP integration you need to add your LDAP server settings in
+`/etc/gitlab/gitlab.rb` or `/home/git/gitlab/config/gitlab.yml`.
+
+>**Note**: In GitLab EE, you can configure multiple LDAP servers to connect to
+one GitLab server.
+
+Prior to version 7.4, GitLab used a different syntax for configuring
+LDAP integration. The old LDAP integration syntax still works but may be
+removed in a future version. If your `gitlab.rb` or `gitlab.yml` file contains
+LDAP settings in both the old syntax and the new syntax, only the __old__
+syntax will be used by GitLab.
+
+The configuration inside `gitlab_rails['ldap_servers']` below is sensitive to
+incorrect indentation. Be sure to retain the indentation given in the example.
+Copy/paste can sometimes cause problems.
+
+**Omnibus configuration**
+
+```ruby
+gitlab_rails['ldap_enabled'] = true
+gitlab_rails['ldap_servers'] = YAML.load <<-EOS # remember to close this block with 'EOS' below
+main: # 'main' is the GitLab 'provider ID' of this LDAP server
+ ## label
+ #
+ # A human-friendly name for your LDAP server. It is OK to change the label later,
+ # for instance if you find out it is too large to fit on the web page.
+ #
+ # Example: 'Paris' or 'Acme, Ltd.'
+ label: 'LDAP'
+
+ host: '_your_ldap_server'
+ port: 389
+ uid: 'sAMAccountName'
+ method: 'plain' # "tls" or "ssl" or "plain"
+ bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
+ password: '_the_password_of_the_bind_user'
+
+ # Set a timeout, in seconds, for LDAP queries. This helps avoid blocking
+ # a request if the LDAP server becomes unresponsive.
+ # A value of 0 means there is no timeout.
+ timeout: 10
+
+ # This setting specifies if LDAP server is Active Directory LDAP server.
+ # For non AD servers it skips the AD specific queries.
+ # If your LDAP server is not AD, set this to false.
+ active_directory: true
+
+ # If allow_username_or_email_login is enabled, GitLab will ignore everything
+ # after the first '@' in the LDAP username submitted by the user on login.
+ #
+ # Example:
+ # - the user enters 'jane.doe@example.com' and 'p@ssw0rd' as LDAP credentials;
+ # - GitLab queries the LDAP server with 'jane.doe' and 'p@ssw0rd'.
+ #
+ # If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to
+ # disable this setting, because the userPrincipalName contains an '@'.
+ allow_username_or_email_login: false
+
+ # To maintain tight control over the number of active users on your GitLab installation,
+ # enable this setting to keep new users blocked until they have been cleared by the admin
+ # (default: false).
+ block_auto_created_users: false
+
+ # Base where we can search for users
+ #
+ # Ex. ou=People,dc=gitlab,dc=example
+ #
+ base: ''
+
+ # Filter LDAP users
+ #
+ # Format: RFC 4515 https://tools.ietf.org/search/rfc4515
+ # Ex. (employeeType=developer)
+ #
+ # Note: GitLab does not support omniauth-ldap's custom filter syntax.
+ #
+ user_filter: ''
+
+ # LDAP attributes that GitLab will use to create an account for the LDAP user.
+ # The specified attribute can either be the attribute name as a string (e.g. 'mail'),
+ # or an array of attribute names to try in order (e.g. ['mail', 'email']).
+ # Note that the user's LDAP login will always be the attribute specified as `uid` above.
+ attributes:
+ # The username will be used in paths for the user's own projects
+ # (like `gitlab.example.com/username/project`) and when mentioning
+ # them in issues, merge request and comments (like `@username`).
+ # If the attribute specified for `username` contains an email address,
+ # the GitLab username will be the part of the email address before the '@'.
+ username: ['uid', 'userid', 'sAMAccountName']
+ email: ['mail', 'email', 'userPrincipalName']
+
+ # If no full name could be found at the attribute specified for `name`,
+ # the full name is determined using the attributes specified for
+ # `first_name` and `last_name`.
+ name: 'cn'
+ first_name: 'givenName'
+ last_name: 'sn'
+
+ ## EE only
+
+ # Base where we can search for groups
+ #
+ # Ex. ou=groups,dc=gitlab,dc=example
+ #
+ group_base: ''
+
+ # The CN of a group containing GitLab administrators
+ #
+ # Ex. administrators
+ #
+ # Note: Not `cn=administrators` or the full DN
+ #
+ admin_group: ''
+
+ # The LDAP attribute containing a user's public SSH key
+ #
+ # Ex. ssh_public_key
+ #
+ sync_ssh_keys: false
+
+# GitLab EE only: add more LDAP servers
+# Choose an ID made of a-z and 0-9 . This ID will be stored in the database
+# so that GitLab can remember which LDAP server a user belongs to.
+# uswest2:
+# label:
+# host:
+# ....
+EOS
+```
+
+**Source configuration**
+
+Use the same format as `gitlab_rails['ldap_servers']` for the contents under
+`servers:` in the example below:
+
+```
+production:
+ # snip...
+ ldap:
+ enabled: false
+ servers:
+ main: # 'main' is the GitLab 'provider ID' of this LDAP server
+ ## label
+ #
+ # A human-friendly name for your LDAP server. It is OK to change the label later,
+ # for instance if you find out it is too large to fit on the web page.
+ #
+ # Example: 'Paris' or 'Acme, Ltd.'
+ label: 'LDAP'
+ # snip...
+```
+
+## Using an LDAP filter to limit access to your GitLab server
+
+If you want to limit all GitLab access to a subset of the LDAP users on your
+LDAP server, the first step should be to narrow the configured `base`. However,
+it is sometimes necessary to filter users further. In this case, you can set up
+an LDAP user filter. The filter must comply with
+[RFC 4515](https://tools.ietf.org/search/rfc4515).
+
+**Omnibus configuration**
+
+```ruby
+gitlab_rails['ldap_servers'] = YAML.load <<-EOS
+main:
+ # snip...
+ user_filter: '(employeeType=developer)'
+EOS
+```
+
+**Source configuration**
+
+```yaml
+production:
+ ldap:
+ servers:
+ main:
+ # snip...
+ user_filter: '(employeeType=developer)'
+```
+
+Tip: If you want to limit access to the nested members of an Active Directory
+group you can use the following syntax:
+
+```
+(memberOf:1.2.840.113556.1.4.1941:=CN=My Group,DC=Example,DC=com)
+```
+
+Please note that GitLab does not support the custom filter syntax used by
+omniauth-ldap.
+
+## Enabling LDAP sign-in for existing GitLab users
+
+When a user signs in to GitLab with LDAP for the first time, and their LDAP
+email address is the primary email address of an existing GitLab user, then
+the LDAP DN will be associated with the existing user. If the LDAP email
+attribute is not found in GitLab's database, a new user is created.
+
+In other words, if an existing GitLab user wants to enable LDAP sign-in for
+themselves, they should check that their GitLab email address matches their
+LDAP email address, and then sign into GitLab via their LDAP credentials.
+
+## Limitations
+
+### TLS Client Authentication
+
+Not implemented by `Net::LDAP`.
+You should disable anonymous LDAP authentication and enable simple or SASL
+authentication. The TLS client authentication setting in your LDAP server cannot
+be mandatory and clients cannot be authenticated with the TLS protocol.
+
+### TLS Server Authentication
+
+Not supported by GitLab's configuration options.
+When setting `method: ssl`, the underlying authentication method used by
+`omniauth-ldap` is `simple_tls`. This method establishes TLS encryption with
+the LDAP server before any LDAP-protocol data is exchanged but no validation of
+the LDAP server's SSL certificate is performed.
+
+## Troubleshooting
+
+### Invalid credentials when logging in
+
+- Make sure the user you are binding with has enough permissions to read the user's
+tree and traverse it.
+- Check that the `user_filter` is not blocking otherwise valid users.
+- Run the following check command to make sure that the LDAP settings are
+ correct and GitLab can see your users:
+
+ ```bash
+ # For Omnibus installations
+ sudo gitlab-rake gitlab:ldap:check
+
+ # For installations from source
+ sudo -u git -H bundle exec rake gitlab:ldap:check RAILS_ENV=production
+ ```
+
+### Connection Refused
+
+If you are getting 'Connection Refused' errors when trying to connect to the
+LDAP server please double-check the LDAP `port` and `method` settings used by
+GitLab. Common combinations are `method: 'plain'` and `port: 389`, OR
+`method: 'ssl'` and `port: 636`.
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 18d64c41986..cc6355d34ef 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -237,6 +237,7 @@ POST /projects/:id/issues
| `assignee_id` | integer | no | The ID of a user to assign issue |
| `milestone_id` | integer | no | The ID of a milestone to assign issue |
| `labels` | string | no | Comma-separated label names for an issue |
+| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` |
```bash
curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues?title=Issues%20with%20auth&labels=bug
diff --git a/doc/api/labels.md b/doc/api/labels.md
index 6496ffe9fd1..544e898b6aa 100644
--- a/doc/api/labels.md
+++ b/doc/api/labels.md
@@ -8,9 +8,9 @@ Get all labels for a given project.
GET /projects/:id/labels
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of the project |
+| Attribute | Type | Required | Description |
+| --------- | ------- | -------- | --------------------- |
+| `id` | integer | yes | The ID of the project |
```bash
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/1/labels
@@ -22,35 +22,43 @@ Example response:
[
{
"name" : "bug",
- "color" : "#d9534f"
+ "color" : "#d9534f",
+ "description": "Bug reported by user"
},
{
"color" : "#d9534f",
- "name" : "confirmed"
+ "name" : "confirmed",
+ "description": "Confirmed issue"
},
{
"name" : "critical",
- "color" : "#d9534f"
+ "color" : "#d9534f",
+ "description": "Criticalissue. Need fix ASAP"
},
{
"color" : "#428bca",
- "name" : "discussion"
+ "name" : "discussion",
+ "description": "Issue that needs further discussion"
},
{
"name" : "documentation",
- "color" : "#f0ad4e"
+ "color" : "#f0ad4e",
+ "description": "Issue about documentation"
},
{
"color" : "#5cb85c",
- "name" : "enhancement"
+ "name" : "enhancement",
+ "description": "Enhancement proposal"
},
{
"color" : "#428bca",
- "name" : "suggestion"
+ "name" : "suggestion",
+ "description": "Suggestion"
},
{
"color" : "#f0ad4e",
- "name" : "support"
+ "name" : "support",
+ "description": "Support issue"
}
]
```
@@ -66,11 +74,12 @@ and 409 if the label already exists.
POST /projects/:id/labels
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of the project |
-| `name` | string | yes | The name of the label |
-| `color` | string | yes | The color of the label in 6-digit hex notation with leading `#` sign |
+| Attribute | Type | Required | Description |
+| ------------- | ------- | -------- | ---------------------------- |
+| `id` | integer | yes | The ID of the project |
+| `name` | string | yes | The name of the label |
+| `color` | string | yes | The color of the label in 6-digit hex notation with leading `#` sign |
+| `description` | string | no | The description of the label |
```bash
curl --data "name=feature&color=#5843AD" -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels"
@@ -81,7 +90,8 @@ Example response:
```json
{
"name" : "feature",
- "color" : "#5843AD"
+ "color" : "#5843AD",
+ "description":null
}
```
@@ -97,10 +107,10 @@ In case of an error, an additional error message is returned.
DELETE /projects/:id/labels
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of the project |
-| `name` | string | yes | The name of the label |
+| Attribute | Type | Required | Description |
+| --------- | ------- | -------- | --------------------- |
+| `id` | integer | yes | The ID of the project |
+| `name` | string | yes | The name of the label |
```bash
curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels?name=bug"
@@ -112,6 +122,7 @@ Example response:
{
"title" : "feature",
"color" : "#5843AD",
+ "description": "New feature proposal",
"updated_at" : "2015-11-03T21:22:30.737Z",
"template" : false,
"project_id" : 1,
@@ -133,15 +144,16 @@ In case of an error, an additional error message is returned.
PUT /projects/:id/labels
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of the project |
-| `name` | string | yes | The name of the existing label |
-| `new_name` | string | yes if `color` if not provided | The new name of the label |
-| `color` | string | yes if `new_name` is not provided | The new color of the label in 6-digit hex notation with leading `#` sign |
+| Attribute | Type | Required | Description |
+| --------------- | ------- | --------------------------------- | ------------------------------- |
+| `id` | integer | yes | The ID of the project |
+| `name` | string | yes | The name of the existing label |
+| `new_name` | string | yes if `color` if not provided | The new name of the label |
+| `color` | string | yes if `new_name` is not provided | The new color of the label in 6-digit hex notation with leading `#` sign |
+| `description` | string | no | The new description of the label |
```bash
-curl -X PUT --data "name=documentation&new_name=docs&color=#8E44AD" -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels"
+curl -X PUT --data "name=documentation&new_name=docs&color=#8E44AD&description=Documentation" -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels"
```
Example response:
@@ -149,6 +161,7 @@ Example response:
```json
{
"color" : "#8E44AD",
- "name" : "docs"
+ "name" : "docs",
+ "description": "Documentation"
}
```
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 3703f4b327a..3a909a2bc87 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -491,6 +491,172 @@ Parameters:
- `id` (required) - The ID of the project to be forked
+### Archive a project
+
+Archives the project if the user is either admin or the project owner of this project. This action is
+idempotent, thus archiving an already archived project will not change the project.
+
+Status code 201 with the project as body is given when successful, in case the user doesn't
+have the proper access rights, code 403 is returned. Status 404 is returned if the project
+doesn't exist, or is hidden to the user.
+
+```
+POST /projects/:id/archive
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
+
+```bash
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/archive"
+```
+
+Example response:
+
+```json
+{
+ "id": 3,
+ "description": null,
+ "default_branch": "master",
+ "public": false,
+ "visibility_level": 0,
+ "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git",
+ "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git",
+ "web_url": "http://example.com/diaspora/diaspora-project-site",
+ "tag_list": [
+ "example",
+ "disapora project"
+ ],
+ "owner": {
+ "id": 3,
+ "name": "Diaspora",
+ "created_at": "2013-09-30T13: 46: 02Z"
+ },
+ "name": "Diaspora Project Site",
+ "name_with_namespace": "Diaspora / Diaspora Project Site",
+ "path": "diaspora-project-site",
+ "path_with_namespace": "diaspora/diaspora-project-site",
+ "issues_enabled": true,
+ "open_issues_count": 1,
+ "merge_requests_enabled": true,
+ "builds_enabled": true,
+ "wiki_enabled": true,
+ "snippets_enabled": false,
+ "created_at": "2013-09-30T13: 46: 02Z",
+ "last_activity_at": "2013-09-30T13: 46: 02Z",
+ "creator_id": 3,
+ "namespace": {
+ "created_at": "2013-09-30T13: 46: 02Z",
+ "description": "",
+ "id": 3,
+ "name": "Diaspora",
+ "owner_id": 1,
+ "path": "diaspora",
+ "updated_at": "2013-09-30T13: 46: 02Z"
+ },
+ "permissions": {
+ "project_access": {
+ "access_level": 10,
+ "notification_level": 3
+ },
+ "group_access": {
+ "access_level": 50,
+ "notification_level": 3
+ }
+ },
+ "archived": true,
+ "avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png",
+ "shared_runners_enabled": true,
+ "forks_count": 0,
+ "star_count": 0,
+ "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b"
+}
+```
+
+### Unarchive a project
+
+Unarchives the project if the user is either admin or the project owner of this project. This action is
+idempotent, thus unarchiving an non-archived project will not change the project.
+
+Status code 201 with the project as body is given when successful, in case the user doesn't
+have the proper access rights, code 403 is returned. Status 404 is returned if the project
+doesn't exist, or is hidden to the user.
+
+```
+POST /projects/:id/archive
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
+
+```bash
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/unarchive"
+```
+
+Example response:
+
+```json
+{
+ "id": 3,
+ "description": null,
+ "default_branch": "master",
+ "public": false,
+ "visibility_level": 0,
+ "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git",
+ "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git",
+ "web_url": "http://example.com/diaspora/diaspora-project-site",
+ "tag_list": [
+ "example",
+ "disapora project"
+ ],
+ "owner": {
+ "id": 3,
+ "name": "Diaspora",
+ "created_at": "2013-09-30T13: 46: 02Z"
+ },
+ "name": "Diaspora Project Site",
+ "name_with_namespace": "Diaspora / Diaspora Project Site",
+ "path": "diaspora-project-site",
+ "path_with_namespace": "diaspora/diaspora-project-site",
+ "issues_enabled": true,
+ "open_issues_count": 1,
+ "merge_requests_enabled": true,
+ "builds_enabled": true,
+ "wiki_enabled": true,
+ "snippets_enabled": false,
+ "created_at": "2013-09-30T13: 46: 02Z",
+ "last_activity_at": "2013-09-30T13: 46: 02Z",
+ "creator_id": 3,
+ "namespace": {
+ "created_at": "2013-09-30T13: 46: 02Z",
+ "description": "",
+ "id": 3,
+ "name": "Diaspora",
+ "owner_id": 1,
+ "path": "diaspora",
+ "updated_at": "2013-09-30T13: 46: 02Z"
+ },
+ "permissions": {
+ "project_access": {
+ "access_level": 10,
+ "notification_level": 3
+ },
+ "group_access": {
+ "access_level": 50,
+ "notification_level": 3
+ }
+ },
+ "archived": false,
+ "avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png",
+ "shared_runners_enabled": true,
+ "forks_count": 0,
+ "star_count": 0,
+ "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b"
+}
+```
+
### Remove project
Removes a project including all associated resources (issues, merge requests etc.)
diff --git a/doc/api/settings.md b/doc/api/settings.md
index 001de76c7af..1e745115dc8 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -26,7 +26,6 @@ Example response:
"default_branch_protection" : 2,
"restricted_visibility_levels" : [],
"signin_enabled" : true,
- "twitter_sharing_enabled" : true,
"after_sign_out_path" : null,
"max_attachment_size" : 10,
"user_oauth_applications" : true,
@@ -57,7 +56,6 @@ PUT /application/settings
| `sign_in_text` | string | no | Text on login page |
| `home_page_url` | string | no | Redirect to this URL when not logged in |
| `default_branch_protection` | integer | no | Determine if developers can push to master. Can take `0` _(not protected, both developers and masters can push new commits, force push or delete the branch)_, `1` _(partially protected, developers can push new commits, but cannot force push or delete the branch, masters can do anything)_ or `2` _(fully protected, developers cannot push new commits, force push or delete the branch, masters can do anything)_ as a parameter. Default is `1`. |
-| `twitter_sharing_enabled` | boolean | no | Allow users to share project creation on Twitter |
| `restricted_visibility_levels` | array of integers | no | Selected levels cannot be used by non-admin users for projects or snippets. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is null which means there is no restriction. |
| `max_attachment_size` | integer | no | Limit attachment size in MB |
| `session_expire_delay` | integer | no | Session duration in minutes. GitLab restart is required to apply changes |
@@ -85,7 +83,6 @@ Example response:
"updated_at": "2015-06-30T13:22:42.210Z",
"home_page_url": "",
"default_branch_protection": 2,
- "twitter_sharing_enabled": true,
"restricted_visibility_levels": [],
"max_attachment_size": 10,
"session_expire_delay": 10080,
diff --git a/doc/incoming_email/README.md b/doc/incoming_email/README.md
index 4cfb8402943..5a9a1582877 100644
--- a/doc/incoming_email/README.md
+++ b/doc/incoming_email/README.md
@@ -1,36 +1,99 @@
# Reply by email
-GitLab can be set up to allow users to comment on issues and merge requests by replying to notification emails.
+GitLab can be set up to allow users to comment on issues and merge requests by
+replying to notification emails.
-## Get a mailbox
+## Requirement
-Reply by email requires an IMAP-enabled email account, with a provider or server that supports [email sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing). Sub-addressing is a feature where any email to `user+some_arbitrary_tag@example.com` will end up in the mailbox for `user@example.com`, and is supported by providers such as Gmail, Google Apps, Yahoo! Mail, Outlook.com and iCloud, as well as the Postfix mail server which you can run on-premises.
+Reply by email requires an IMAP-enabled email account. GitLab allows you to use
+three strategies for this feature:
+- using email sub-addressing
+- using a dedicated email address
+- using a catch-all mailbox
-If you want to use Gmail / Google Apps with Reply by email, make sure you have [IMAP access enabled](https://support.google.com/mail/troubleshooter/1668960?hl=en#ts=1665018) and [allow less secure apps to access the account](https://support.google.com/accounts/answer/6010255).
+### Email sub-addressing
-To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these instructions](./postfix.md).
+**If your provider or server supports email sub-addressing, we recommend using it.**
+
+[Sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing) is
+a feature where any email to `user+some_arbitrary_tag@example.com` will end up
+in the mailbox for `user@example.com`, and is supported by providers such as
+Gmail, Google Apps, Yahoo! Mail, Outlook.com and iCloud, as well as the Postfix
+mail server which you can run on-premises.
+
+### Dedicated email address
+
+This solution is really simple to set up: you just have to create an email
+address dedicated to receive your users' replies to GitLab notifications.
+
+### Catch-all mailbox
+
+A [catch-all mailbox](https://en.wikipedia.org/wiki/Catch-all) for a domain will
+"catch all" the emails addressed to the domain that do not exist in the mail
+server.
+
+## How it works?
+
+### 1. GitLab sends a notification email
+
+When GitLab sends a notification and Reply by email is enabled, the `Reply-To`
+header is set to the address defined in your GitLab configuration, with the
+`%{key}` placeholder (if present) replaced by a specific "reply key". In
+addition, this "reply key" is also added to the `References` header.
+
+### 2. You reply to the notification email
+
+When you reply to the notification email, your email client will:
+
+- send the email to the `Reply-To` address it got from the notification email
+- set the `In-Reply-To` header to the value of the `Message-ID` header from the
+ notification email
+- set the `References` header to the value of the `Message-ID` plus the value of
+ the notification email's `References` header.
+
+### 3. GitLab receives your reply to the notification email
+
+When GitLab receives your reply, it will look for the "reply key" in the
+following headers, in this order:
+
+1. the `To` header
+1. the `References` header
+
+If it finds a reply key, it will be able to leave your reply as a comment on
+the entity the notification was about (issue, merge request, commit...).
+
+For more details about the `Message-ID`, `In-Reply-To`, and `References headers`,
+please consult [RFC 5322](https://tools.ietf.org/html/rfc5322#section-3.6.4).
## Set it up
+If you want to use Gmail / Google Apps with Reply by email, make sure you have
+[IMAP access enabled](https://support.google.com/mail/troubleshooter/1668960?hl=en#ts=1665018)
+and [allowed less secure apps to access the account](https://support.google.com/accounts/answer/6010255).
+
+To set up a basic Postfix mail server with IMAP access on Ubuntu, follow
+[these instructions](./postfix.md).
+
### Omnibus package installations
-1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the feature and fill in the details for your specific IMAP server and email account:
+1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the
+ feature and fill in the details for your specific IMAP server and email account:
```ruby
# Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.com
gitlab_rails['incoming_email_enabled'] = true
-
- # The email address including a placeholder for the key that references the item being replied to.
- # The `%{key}` placeholder is added after the user part, before the `@`.
+
+ # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
+ # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`).
gitlab_rails['incoming_email_address'] = "incoming+%{key}@gitlab.example.com"
-
+
# Email account username
# With third party providers, this is usually the full email address.
# With self-hosted email servers, this is usually the user part of the email address.
gitlab_rails['incoming_email_email'] = "incoming"
# Email account password
gitlab_rails['incoming_email_password'] = "[REDACTED]"
-
+
# IMAP server host
gitlab_rails['incoming_email_host'] = "gitlab.example.com"
# IMAP server port
@@ -47,18 +110,18 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these
```ruby
# Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com
gitlab_rails['incoming_email_enabled'] = true
-
+
# The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
- # The `%{key}` placeholder is added after the user part, after a `+` character, before the `@`.
+ # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`).
gitlab_rails['incoming_email_address'] = "gitlab-incoming+%{key}@gmail.com"
-
+
# Email account username
# With third party providers, this is usually the full email address.
# With self-hosted email servers, this is usually the user part of the email address.
gitlab_rails['incoming_email_email'] = "gitlab-incoming@gmail.com"
# Email account password
gitlab_rails['incoming_email_password'] = "[REDACTED]"
-
+
# IMAP server host
gitlab_rails['incoming_email_host'] = "imap.gmail.com"
# IMAP server port
@@ -72,8 +135,6 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these
gitlab_rails['incoming_email_mailbox_name'] = "inbox"
```
- As mentioned, the part after `+` in the address is ignored, and any email sent here will end up in the mailbox for `incoming@gitlab.example.com`/`gitlab-incoming@gmail.com`.
-
1. Reconfigure GitLab and restart mailroom for the changes to take effect:
```sh
@@ -97,7 +158,8 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these
cd /home/git/gitlab
```
-1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature and fill in the details for your specific IMAP server and email account:
+1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature
+ and fill in the details for your specific IMAP server and email account:
```sh
sudo editor config/gitlab.yml
@@ -109,7 +171,7 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these
enabled: true
# The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
- # The `%{key}` placeholder is added after the user part, after a `+` character, before the `@`.
+ # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`).
address: "incoming+%{key}@gitlab.example.com"
# Email account username
@@ -138,7 +200,7 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these
enabled: true
# The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
- # The `%{key}` placeholder is added after the user part, after a `+` character, before the `@`.
+ # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`).
address: "gitlab-incoming+%{key}@gmail.com"
# Email account username
@@ -161,8 +223,6 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these
mailbox: "inbox"
```
- As mentioned, the part after `+` in the address is ignored, and any email sent here will end up in the mailbox for `incoming@gitlab.example.com`/`gitlab-incoming@gmail.com`.
-
1. Enable `mail_room` in the init script at `/etc/default/gitlab`:
```sh
@@ -195,8 +255,8 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these
incoming_email:
enabled: true
- # The email address including a placeholder for the key that references the item being replied to.
- # The `%{key}` placeholder is added after the user part, before the `@`.
+ # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
+ # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`).
address: "gitlab-incoming+%{key}@gmail.com"
# Email account username
diff --git a/doc/install/installation.md b/doc/install/installation.md
index bffbc776500..e0a16df09c1 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -227,9 +227,9 @@ sudo usermod -aG redis git
### Clone the Source
# Clone GitLab repository
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-6-stable gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-7-stable gitlab
-**Note:** You can change `8-6-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `8-7-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
diff --git a/doc/integration/github.md b/doc/integration/github.md
index 886784a27c9..1890edd7a4c 100644
--- a/doc/integration/github.md
+++ b/doc/integration/github.md
@@ -17,7 +17,7 @@ GitHub will generate an application ID and secret key for you to use.
- Application name: This can be anything. Consider something like "\<Organization\>'s GitLab" or "\<Your Name\>'s GitLab" or something else descriptive.
- Homepage URL: The URL to your GitLab installation. 'https://gitlab.company.com'
- Application description: Fill this in if you wish.
- - Authorization callback URL: 'https://gitlab.company.com/'
+ - Default authorization callback URL is '${YOUR_DOMAIN}/import/github/callback'
1. Select "Register application".
1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot).
diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md
index cf1f98492ea..fb20308c49c 100644
--- a/doc/integration/ldap.md
+++ b/doc/integration/ldap.md
@@ -1,228 +1,3 @@
# GitLab LDAP integration
-GitLab can be configured to allow your users to sign with their LDAP credentials to integrate with e.g. Active Directory.
-
-The first time a user signs in with LDAP credentials, GitLab will create a new GitLab user associated with the LDAP Distinguished Name (DN) of the LDAP user.
-
-GitLab user attributes such as nickname and email will be copied from the LDAP user entry.
-
-## Security
-
-GitLab assumes that LDAP users are not able to change their LDAP 'mail', 'email' or 'userPrincipalName' attribute.
-An LDAP user who is allowed to change their email on the LDAP server can [take over any account](#enabling-ldap-sign-in-for-existing-gitlab-users) on your GitLab server.
-
-We recommend against using GitLab LDAP integration if your LDAP users are allowed to change their 'mail', 'email' or 'userPrincipalName' attribute on the LDAP server.
-
-If a user is deleted from the LDAP server, they will be blocked in GitLab as well.
-Users will be immediately blocked from logging in. However, there is an LDAP check
-cache time of one hour. The means users that are already logged in or are using Git
-over SSH will still be able to access GitLab for up to one hour. Manually block
-the user in the GitLab Admin area to immediately block all access.
-
-## Configuring GitLab for LDAP integration
-
-To enable GitLab LDAP integration you need to add your LDAP server settings in `/etc/gitlab/gitlab.rb` or `/home/git/gitlab/config/gitlab.yml`.
-In GitLab Enterprise Edition you can have multiple LDAP servers connected to one GitLab server.
-
-Please note that before version 7.4, GitLab used a different syntax for configuring LDAP integration.
-The old LDAP integration syntax still works in GitLab 7.4.
-If your `gitlab.rb` or `gitlab.yml` file contains LDAP settings in both the old syntax and the new syntax, only the __old__ syntax will be used by GitLab.
-
-```ruby
-# For omnibus packages
-gitlab_rails['ldap_enabled'] = true
-gitlab_rails['ldap_servers'] = YAML.load <<-EOS # remember to close this block with 'EOS' below
-main: # 'main' is the GitLab 'provider ID' of this LDAP server
- ## label
- #
- # A human-friendly name for your LDAP server. It is OK to change the label later,
- # for instance if you find out it is too large to fit on the web page.
- #
- # Example: 'Paris' or 'Acme, Ltd.'
- label: 'LDAP'
-
- host: '_your_ldap_server'
- port: 389
- uid: 'sAMAccountName'
- method: 'plain' # "tls" or "ssl" or "plain"
- bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
- password: '_the_password_of_the_bind_user'
-
- # Set a timeout, in seconds, for LDAP queries. This helps avoid blocking
- # a request if the LDAP server becomes unresponsive.
- # A value of 0 means there is no timeout.
- timeout: 10
-
- # This setting specifies if LDAP server is Active Directory LDAP server.
- # For non AD servers it skips the AD specific queries.
- # If your LDAP server is not AD, set this to false.
- active_directory: true
-
- # If allow_username_or_email_login is enabled, GitLab will ignore everything
- # after the first '@' in the LDAP username submitted by the user on login.
- #
- # Example:
- # - the user enters 'jane.doe@example.com' and 'p@ssw0rd' as LDAP credentials;
- # - GitLab queries the LDAP server with 'jane.doe' and 'p@ssw0rd'.
- #
- # If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to
- # disable this setting, because the userPrincipalName contains an '@'.
- allow_username_or_email_login: false
-
- # To maintain tight control over the number of active users on your GitLab installation,
- # enable this setting to keep new users blocked until they have been cleared by the admin
- # (default: false).
- block_auto_created_users: false
-
- # Base where we can search for users
- #
- # Ex. ou=People,dc=gitlab,dc=example
- #
- base: ''
-
- # Filter LDAP users
- #
- # Format: RFC 4515 https://tools.ietf.org/search/rfc4515
- # Ex. (employeeType=developer)
- #
- # Note: GitLab does not support omniauth-ldap's custom filter syntax.
- #
- user_filter: ''
-
- # LDAP attributes that GitLab will use to create an account for the LDAP user.
- # The specified attribute can either be the attribute name as a string (e.g. 'mail'),
- # or an array of attribute names to try in order (e.g. ['mail', 'email']).
- # Note that the user's LDAP login will always be the attribute specified as `uid` above.
- attributes:
- # The username will be used in paths for the user's own projects
- # (like `gitlab.example.com/username/project`) and when mentioning
- # them in issues, merge request and comments (like `@username`).
- # If the attribute specified for `username` contains an email address,
- # the GitLab username will be the part of the email address before the '@'.
- username: ['uid', 'userid', 'sAMAccountName']
- email: ['mail', 'email', 'userPrincipalName']
-
- # If no full name could be found at the attribute specified for `name`,
- # the full name is determined using the attributes specified for
- # `first_name` and `last_name`.
- name: 'cn'
- first_name: 'givenName'
- last_name: 'sn'
-
-# GitLab EE only: add more LDAP servers
-# Choose an ID made of a-z and 0-9 . This ID will be stored in the database
-# so that GitLab can remember which LDAP server a user belongs to.
-# uswest2:
-# label:
-# host:
-# ....
-EOS
-```
-
-If you are getting 'Connection Refused' errors when trying to connect to the LDAP server please double-check the LDAP `port` and `method` settings used by GitLab.
-Common combinations are `method: 'plain'` and `port: 389`, OR `method: 'ssl'` and `port: 636`.
-
-If you are using a GitLab installation from source you can find the LDAP settings in `/home/git/gitlab/config/gitlab.yml`:
-
-```
-production:
- # snip...
- ldap:
- enabled: false
- servers:
- main: # 'main' is the GitLab 'provider ID' of this LDAP server
- ## label
- #
- # A human-friendly name for your LDAP server. It is OK to change the label later,
- # for instance if you find out it is too large to fit on the web page.
- #
- # Example: 'Paris' or 'Acme, Ltd.'
- label: 'LDAP'
- # snip...
-```
-
-## Enabling LDAP sign-in for existing GitLab users
-
-When a user signs in to GitLab with LDAP for the first time, and their LDAP email address is the primary email address of an existing GitLab user, then the LDAP DN will be associated with the existing user.
-
-If the LDAP email attribute is not found in GitLab's database, a new user is created.
-
-In other words, if an existing GitLab user wants to enable LDAP sign-in for themselves, they should check that their GitLab email address matches their LDAP email address, and then sign into GitLab via their LDAP credentials.
-
-GitLab recognizes the following LDAP attributes as email addresses: `mail`, `email` and `userPrincipalName`.
-
-If multiple LDAP email attributes are present, e.g. `mail: foo@bar.com` and `email: foo@example.com`, then the first attribute found wins -- in this case `foo@bar.com`.
-
-## Using an LDAP filter to limit access to your GitLab server
-
-If you want to limit all GitLab access to a subset of the LDAP users on your LDAP server you can set up an LDAP user filter.
-The filter must comply with [RFC 4515](https://tools.ietf.org/search/rfc4515).
-
-```ruby
-# For omnibus packages; new LDAP server syntax
-gitlab_rails['ldap_servers'] = YAML.load <<-EOS
-main:
- # snip...
- user_filter: '(employeeType=developer)'
-EOS
-```
-
-```yaml
-# For installations from source; new LDAP server syntax
-production:
- ldap:
- servers:
- main:
- # snip...
- user_filter: '(employeeType=developer)'
-```
-
-Tip: if you want to limit access to the nested members of an Active Directory group you can use the following syntax:
-
-```
-(memberOf:1.2.840.113556.1.4.1941:=CN=My Group,DC=Example,DC=com)
-```
-
-Please note that GitLab does not support the custom filter syntax used by omniauth-ldap.
-
-## Limitations
-
-GitLab's LDAP client is based on [omniauth-ldap](https://gitlab.com/gitlab-org/omniauth-ldap)
-which encapsulates Ruby's `Net::LDAP` class. It provides a pure-Ruby implementation
-of the LDAP client protocol. As a result, GitLab is limited by `omniauth-ldap` and may impact your LDAP
-server settings.
-
-### TLS Client Authentication
-Not implemented by `Net::LDAP`.
-So you should disable anonymous LDAP authentication and enable simple or SASL
-authentication. TLS client authentication setting in your LDAP server cannot be
-mandatory and clients cannot be authenticated with the TLS protocol.
-
-### TLS Server Authentication
-Not supported by GitLab's configuration options.
-When setting `method: ssl`, the underlying authentication method used by
-`omniauth-ldap` is `simple_tls`. This method establishes TLS encryption with
-the LDAP server before any LDAP-protocol data is exchanged but no validation of
-the LDAP server's SSL certificate is performed.
-
-## Troubleshooting
-
-### Invalid credentials when logging in
-
-Make sure the user you are binding with has enough permissions to read the user's
-tree and traverse it.
-
-Also make sure that the `user_filter` is not blocking otherwise valid users.
-
-To make sure that the LDAP settings are correct and GitLab can see your users,
-execute the following command:
-
-
-```bash
-# For Omnibus installations
-sudo gitlab-rake gitlab:ldap:check
-
-# For installations from source
-sudo -u git -H bundle exec rake gitlab:ldap:check RAILS_ENV=production
-```
-
+This document was moved under [`administration/auth/ldap`](administration/auth/ldap.md).
diff --git a/doc/update/8.5-to-8.6.md b/doc/update/8.5-to-8.6.md
index 712e9fdf93a..b9abcbd2c12 100644
--- a/doc/update/8.5-to-8.6.md
+++ b/doc/update/8.5-to-8.6.md
@@ -62,7 +62,26 @@ sudo -u git -H git checkout v0.7.1
sudo -u git -H make
```
-### 6. Install libs, migrations, etc.
+### 6. Updates for PostgreSQL Users
+
+Starting with 8.6 users using GitLab in combination with PostgreSQL are required
+to have the `pg_trgm` extension enabled for all GitLab databases. If you're
+using GitLab's Omnibus packages there's nothing you'll need to do manually as
+this extension is enabled automatically. Users who install GitLab without using
+Omnibus (e.g. by building from source) have to enable this extension manually.
+To enable this extension run the following SQL command as a PostgreSQL super
+user for _every_ GitLab database:
+
+```sql
+CREATE EXTENSION IF NOT EXISTS pg_trgm;
+```
+
+Certain operating systems might require the installation of extra packages for
+this extension to be available. For example, users using Ubuntu will have to
+install the `postgresql-contrib` package in order for this extension to be
+available.
+
+### 7. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
@@ -84,7 +103,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
```
-### 7. Update configuration files
+### 8. Update configuration files
#### New configuration options for `gitlab.yml`
@@ -120,25 +139,6 @@ Ensure you're still up-to-date with the latest init script changes:
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
-### 8. Updates for PostgreSQL Users
-
-Starting with 8.6 users using GitLab in combination with PostgreSQL are required
-to have the `pg_trgm` extension enabled for all GitLab databases. If you're
-using GitLab's Omnibus packages there's nothing you'll need to do manually as
-this extension is enabled automatically. Users who install GitLab without using
-Omnibus (e.g. by building from source) have to enable this extension manually.
-To enable this extension run the following SQL command as a PostgreSQL super
-user for _every_ GitLab database:
-
-```sql
-CREATE EXTENSION IF NOT EXISTS pg_trgm;
-```
-
-Certain operating systems might require the installation of extra packages for
-this extension to be available. For example, users using Ubuntu will have to
-install the `postgresql-contrib` package in order for this extension to be
-available.
-
### 9. Start application
sudo service gitlab start
diff --git a/doc/update/8.6-to-8.7.md b/doc/update/8.6-to-8.7.md
new file mode 100644
index 00000000000..76eee147c72
--- /dev/null
+++ b/doc/update/8.6-to-8.7.md
@@ -0,0 +1,146 @@
+# From 8.6 to 8.7
+
+Make sure you view this update guide from the tag (version) of GitLab you would
+like to install. In most cases this should be the highest numbered production
+tag (without rc in it). You can select the tag in the version dropdown at the
+top left corner of GitLab (below the menu bar).
+
+If the highest number stable branch is unclear please check the
+[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
+guide links by version.
+
+### 1. Stop server
+
+ sudo service gitlab stop
+
+### 2. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 3. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 8-7-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 8-7-stable-ee
+```
+
+### 4. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout v2.7.0
+```
+
+### 5. Update gitlab-workhorse
+
+Install and compile gitlab-workhorse. This requires
+[Go 1.5](https://golang.org/dl) which should already be on your system from
+GitLab 8.1.
+
+```bash
+cd /home/git/gitlab-workhorse
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout v0.7.1
+sudo -u git -H make
+```
+
+### 6. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without postgres')
+sudo -u git -H bundle install --without postgres development test --deployment
+
+# PostgreSQL installations (note: the line below states '--without mysql')
+sudo -u git -H bundle install --without mysql development test --deployment
+
+# Optional: clean up old gems
+sudo -u git -H bundle clean
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+```
+
+### 7. Update configuration files
+
+#### Nginx configuration
+
+Ensure you're still up-to-date with the latest NGINX configuration changes:
+
+```sh
+# For HTTPS configurations
+git diff origin/8-6-stable:lib/support/nginx/gitlab-ssl origin/8-7-stable:lib/support/nginx/gitlab-ssl
+
+# For HTTP configurations
+git diff origin/8-6-stable:lib/support/nginx/gitlab origin/8-7-stable:lib/support/nginx/gitlab
+```
+
+If you are using Apache instead of NGINX please see the updated [Apache templates].
+Also note that because Apache does not support upstreams behind Unix sockets you
+will need to let gitlab-workhorse listen on a TCP port. You can do this
+via [/etc/default/gitlab].
+
+[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
+[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-7-stable/lib/support/init.d/gitlab.default.example#L37
+
+#### Init script
+
+Ensure you're still up-to-date with the latest init script changes:
+
+ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+
+### 8. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 9. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations, the upgrade is complete!
+
+## Things went south? Revert to previous version (8.6)
+
+### 1. Revert the code to the previous version
+
+Follow the [upgrade guide from 8.5 to 8.6](8.5-to-8.6.md), except for the
+database migration (the backup is already migrated to the previous version).
+
+### 2. Restore from the backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+
+If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/features/dashboard/dashboard.feature b/features/dashboard/dashboard.feature
index c3b3577c449..db73309804c 100644
--- a/features/dashboard/dashboard.feature
+++ b/features/dashboard/dashboard.feature
@@ -6,6 +6,7 @@ Feature: Dashboard
And project "Shop" has push event
And project "Shop" has CI enabled
And project "Shop" has CI build
+ And project "Shop" has labels: "bug", "feature", "enhancement"
And I visit dashboard page
Scenario: I should see projects list
@@ -51,6 +52,13 @@ Feature: Dashboard
Then The list should be sorted by "Oldest updated"
@javascript
+ Scenario: Filtering Issues by label
+ Given project "Shop" has issue "Bugfix1" with label "feature"
+ When I visit dashboard issues page
+ And I filter the list by label "feature"
+ Then I should see "Bugfix1" in issues list
+
+ @javascript
Scenario: Visiting Project's issues after sorting
Given I visit dashboard issues page
And I sort the list by "Oldest updated"
diff --git a/features/dashboard/todos.feature b/features/dashboard/todos.feature
index 1e7b1b50d64..8677b450813 100644
--- a/features/dashboard/todos.feature
+++ b/features/dashboard/todos.feature
@@ -36,3 +36,8 @@ Feature: Dashboard Todos
Scenario: I filter by action
Given I filter by "Mentioned"
Then I should not see todos related to "Assignments" in the list
+
+ @javascript
+ Scenario: I click on a todo row
+ Given I click on the todo
+ Then I should be directed to the corresponding page
diff --git a/features/groups.feature b/features/groups.feature
index 419a5d3963d..49e939807b5 100644
--- a/features/groups.feature
+++ b/features/groups.feature
@@ -7,10 +7,6 @@ Feature: Groups
When I visit group "NonExistentGroup" page
Then page status code should be 404
- Scenario: I should have back to group button
- When I visit group "Owned" page
- Then I should see back to dashboard button
-
@javascript
Scenario: I should see group "Owned" dashboard list
When I visit group "Owned" page
diff --git a/features/project/project.feature b/features/project/project.feature
index f1f3ed26065..aa22401c88e 100644
--- a/features/project/project.feature
+++ b/features/project/project.feature
@@ -18,15 +18,6 @@ Feature: Project
Then I should see the default project avatar
And I should not see the "Remove avatar" button
- Scenario: I should have back to group button
- And project "Shop" belongs to group
- And I visit project "Shop" page
- Then I should see back to group button
-
- Scenario: I should have back to group button
- And I visit project "Shop" page
- Then I should see back to dashboard button
-
Scenario: I should have readme on page
And I visit project "Shop" page
Then I should see project "Shop" README
diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb
index 5062e348844..b5980b35102 100644
--- a/features/steps/dashboard/dashboard.rb
+++ b/features/steps/dashboard/dashboard.rb
@@ -87,4 +87,23 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
step 'I should see 1 project at group list' do
expect(find('span.last_activity/span')).to have_content('1')
end
+
+ step 'I filter the list by label "feature"' do
+ page.within ".labels-filter" do
+ find('.dropdown').click
+ click_link "feature"
+ end
+ end
+
+ step 'I should see "Bugfix1" in issues list' do
+ page.within "ul.content-list" do
+ expect(page).to have_content "Bugfix1"
+ end
+ end
+
+ step 'project "Shop" has issue "Bugfix1" with label "feature"' do
+ project = Project.find_by(name: "Shop")
+ issue = create(:issue, title: "Bugfix1", project: project, assignee: current_user)
+ issue.labels << project.labels.find_by(title: 'feature')
+ end
end
diff --git a/features/steps/dashboard/issues.rb b/features/steps/dashboard/issues.rb
index 93aa77589be..e21af72a777 100644
--- a/features/steps/dashboard/issues.rb
+++ b/features/steps/dashboard/issues.rb
@@ -42,11 +42,10 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
end
step 'I click "All" link' do
- find('.js-author-search').click
- find('.dropdown-content a', match: :first).click
-
- find('.js-assignee-search').click
- find('.dropdown-content a', match: :first).click
+ find(".js-author-search").click
+ find(".dropdown-menu-author li a", match: :first).click
+ find(".js-assignee-search").click
+ find(".dropdown-menu-assignee li a", match: :first).click
end
def should_see(issue)
diff --git a/features/steps/dashboard/todos.rb b/features/steps/dashboard/todos.rb
index 963e4f21365..30b21b93ac7 100644
--- a/features/steps/dashboard/todos.rb
+++ b/features/steps/dashboard/todos.rb
@@ -88,6 +88,14 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
should_not_see_todo "John Doe assigned you issue ##{issue.iid}"
end
+ step 'I click on the todo' do
+ find('.todo:nth-child(1)').click
+ end
+
+ step 'I should be directed to the corresponding page' do
+ page.should have_css('.identifier', text: 'Merge Request !1')
+ end
+
def should_see_todo(position, title, body, pending = true)
page.within(".todo:nth-child(#{position})") do
expect(page).to have_content title
diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb
index a167d259837..b6ce5bc9cec 100644
--- a/features/steps/group/milestones.rb
+++ b/features/steps/group/milestones.rb
@@ -5,7 +5,9 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
include SharedUser
step 'I click on group milestones' do
- click_link 'Milestones'
+ page.within '.nav-secondary' do
+ click_link("Milestones")
+ end
end
step 'I should see group milestones index page has no milestones' do
diff --git a/features/steps/groups.rb b/features/steps/groups.rb
index e5b7db4c5e3..483370f41c6 100644
--- a/features/steps/groups.rb
+++ b/features/steps/groups.rb
@@ -4,10 +4,6 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
include SharedGroup
include SharedUser
- step 'I should see back to dashboard button' do
- expect(page).to have_content 'Go to dashboard'
- end
-
step 'I should see group "Owned"' do
expect(page).to have_content '@owned'
end
diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb
index 19d81453d8c..4584fc4d754 100644
--- a/features/steps/project/active_tab.rb
+++ b/features/steps/project/active_tab.rb
@@ -82,7 +82,9 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
# Sub Tabs: Issues
step 'I click the "Milestones" tab' do
- click_link('Milestones')
+ page.within '.nav-secondary' do
+ click_link('Milestones')
+ end
end
step 'I click the "Labels" tab' do
diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb
index 527f7853da9..d9b16afa9b8 100644
--- a/features/steps/project/fork.rb
+++ b/features/steps/project/fork.rb
@@ -36,7 +36,7 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
end
step 'I goto the Merge Requests page' do
- page.within '.page-sidebar-expanded' do
+ page.within '.nav-secondary' do
click_link "Merge Requests"
end
end
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index 91fe19dd477..a4f02b590ea 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -326,7 +326,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I should see a discussion has started on diff' do
page.within(".notes .discussion") do
- page.should have_content "#{current_user.name} started a discussion"
+ page.should have_content "#{current_user.name} #{current_user.to_reference} started a discussion"
page.should have_content sample_commit.line_code_path
page.should have_content "Line is wrong"
end
@@ -334,7 +334,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I should see a discussion by user "John Doe" has started on diff' do
page.within(".notes .discussion") do
- page.should have_content "#{user_exists("John Doe").name} started a discussion"
+ page.should have_content "#{user_exists("John Doe").name} #{user_exists("John Doe").to_reference} started a discussion"
page.should have_content sample_commit.line_code_path
page.should have_content "Line is wrong"
end
@@ -350,7 +350,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I should see a discussion has started on commit diff' do
page.within(".notes .discussion") do
- page.should have_content "#{current_user.name} started a discussion on commit"
+ page.should have_content "#{current_user.name} #{current_user.to_reference} started a discussion on commit"
page.should have_content sample_commit.line_code_path
page.should have_content "Line is wrong"
end
@@ -358,7 +358,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I should see a discussion has started on commit' do
page.within(".notes .discussion") do
- page.should have_content "#{current_user.name} started a discussion on commit"
+ page.should have_content "#{current_user.name} #{current_user.to_reference} started a discussion on commit"
page.should have_content "One comment to rule them all"
end
end
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index ef185861e00..8f1d4a223a9 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -114,7 +114,9 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end
step 'I should not see "Snippets" button' do
- expect(page).not_to have_link 'Snippets'
+ page.within '.nav-secondary' do
+ expect(page).not_to have_link 'Snippets'
+ end
end
step 'project "Shop" belongs to group' do
@@ -123,14 +125,6 @@ class Spinach::Features::Project < Spinach::FeatureSteps
@project.save!
end
- step 'I should see back to dashboard button' do
- expect(page).to have_content 'Go to dashboard'
- end
-
- step 'I should see back to group button' do
- expect(page).to have_content 'Go to group'
- end
-
step 'I click notifications drop down button' do
click_link 'notifications-button'
end
diff --git a/features/steps/shared/project_tab.rb b/features/steps/shared/project_tab.rb
index 4fc2ece79ff..fa7d24ce611 100644
--- a/features/steps/shared/project_tab.rb
+++ b/features/steps/shared/project_tab.rb
@@ -41,7 +41,7 @@ module SharedProjectTab
end
step 'the active main tab should be Settings' do
- page.within '.nav-sidebar' do
+ page.within '.nav-secondary' do
expect(page).to have_content('Go to project')
end
end
diff --git a/fixtures/emojis/digests.json b/fixtures/emojis/digests.json
new file mode 100644
index 00000000000..18d6e93e0f4
--- /dev/null
+++ b/fixtures/emojis/digests.json
@@ -0,0 +1,8597 @@
+[
+ {
+ "name": "100",
+ "unicode": "1F4AF",
+ "digest": "6d57c7cc93335f853e1a5670233f121bc94730dbd82b2b3c5c5a509e092ef0fd"
+ },
+ {
+ "name": "1234",
+ "unicode": "1F522",
+ "digest": "727763fd9f18fd5df59e9f78e678ea4ec753e674d70f15d4e77c7802067d660b"
+ },
+ {
+ "name": "8ball",
+ "unicode": "1F3B1",
+ "digest": "1aecf21951452ba24e921ec71b3d313b7ddc2e185b0339c9e0eebc85be4f031d"
+ },
+ {
+ "name": "a",
+ "unicode": "1F170",
+ "digest": "2272113a5bcb7faf8db7c1bd35df576d32f2f7cbd881463934ad3382eb87c723"
+ },
+ {
+ "name": "ab",
+ "unicode": "1F18E",
+ "digest": "6f8a237751fdc84db4121f408272d9a23258515449610e4c6c54f50f6e995627"
+ },
+ {
+ "name": "abc",
+ "unicode": "1F524",
+ "digest": "652a2381a7b587d8a52d5178e2d7d6c8600b33d36160fa69677943da374105bc"
+ },
+ {
+ "name": "abcd",
+ "unicode": "1F521",
+ "digest": "35ade4fd3d75294ebb72c24490aa32745604edc6cabe095b90634cd3ce78c07b"
+ },
+ {
+ "name": "accept",
+ "unicode": "1F251",
+ "digest": "8212ed158cc447c92813273fc915e84d3d5c4c48d1b38e498c088bad27ab8145"
+ },
+ {
+ "name": "aerial_tramway",
+ "unicode": "1F6A1",
+ "digest": "8039d7f67e6e5b211066cab6cf2142afc3aca5c830a357369362c9b484029563"
+ },
+ {
+ "name": "airplane",
+ "unicode": "2708",
+ "digest": "18f4dfac323555d8cdabb79148874c0185ce98e1a08e69414d236b23e502a854"
+ },
+ {
+ "name": "airplane_arriving",
+ "unicode": "1F6EC",
+ "digest": "9a1c81d97512e5d0e3acec40290d00f616ec182140909859e366a734b9f840bb"
+ },
+ {
+ "name": "airplane_departure",
+ "unicode": "1F6EB",
+ "digest": "e3c5ff4038db998c1897cb237d0b865da0bc60331c758f204e45a979d5fab445"
+ },
+ {
+ "name": "airplane_northeast",
+ "unicode": "1F6EA",
+ "digest": "fdddc2cd3618ec6661612581b8b93553cb086b0bb197e96aedf1bee8055e7bb4"
+ },
+ {
+ "name": "airplane_small",
+ "unicode": "1F6E9",
+ "digest": "f98b44422d6bf505b50330805ecf68013d035341f0b6487c3c05ad913eb5abd3"
+ },
+ {
+ "name": "airplane_small_up",
+ "unicode": "1F6E8",
+ "digest": "029752b29a757c087dec60f45ea242e974fc181129e20390d5d4a2f90442091a"
+ },
+ {
+ "name": "airplane_up",
+ "unicode": "1F6E7",
+ "digest": "ec45d4dbfce1f75dc59339417b1dcf5f1e1359cd9d04ff233babf359a3330e77"
+ },
+ {
+ "name": "alarm_clock",
+ "unicode": "23F0",
+ "digest": "84ddd7b3b857c165410b7b44863e5354ca0f3591c3bfe56231f12c9f7531a96f"
+ },
+ {
+ "name": "alembic",
+ "unicode": "2697",
+ "digest": "45698914a21683f06931d807af171bcb6984e5ebce66012bba71b467565bd69d"
+ },
+ {
+ "name": "alien",
+ "unicode": "1F47D",
+ "digest": "94dbe4e90614c654145aba93610c43e3ab86df8ca07391bd4e56383f9329c008"
+ },
+ {
+ "name": "ambulance",
+ "unicode": "1F691",
+ "digest": "82ef36bcd13c88a4b2397c918b8048adc6bf045ed2532ff568e0dfd1b1b29c3c"
+ },
+ {
+ "name": "amphora",
+ "unicode": "1F3FA",
+ "digest": "d3758d88aa1fc3be01894102f57479d3a49790510d38ad3d06a2774962010608"
+ },
+ {
+ "name": "anchor",
+ "unicode": "2693",
+ "digest": "27c6034f769d9f020362fc5b227b9279651cc940861e727d1f6ccd59af98f851"
+ },
+ {
+ "name": "angel",
+ "unicode": "1F47C",
+ "digest": "c1b8ad2adc7686e7fbbe4ec357071e7228a5e0762e001bb589e2f97ff258d5c7"
+ },
+ {
+ "name": "angel_tone1",
+ "unicode": "1F47C-1F3FB",
+ "digest": "90b701c43311b1096c4a012d9905a186f1a16829ea2707921a8418c28617d751"
+ },
+ {
+ "name": "angel_tone2",
+ "unicode": "1F47C-1F3FC",
+ "digest": "d6bcaf1b76e25d486d4ab9b159cf727782d508543d1ae27c8d2c12d2f13d6eb0"
+ },
+ {
+ "name": "angel_tone3",
+ "unicode": "1F47C-1F3FD",
+ "digest": "3069285e6218c8083cb0085aa10017bcdea033e321d97ba339a84892074b903a"
+ },
+ {
+ "name": "angel_tone4",
+ "unicode": "1F47C-1F3FE",
+ "digest": "dbb87019752d9caa94ce086858c1e3225b62e221ad599f5106548fda2456fc2b"
+ },
+ {
+ "name": "angel_tone5",
+ "unicode": "1F47C-1F3FF",
+ "digest": "f77703df97720c27a128b5f3c0948b9e04a6b6b81ea5306468154f9bf56225db"
+ },
+ {
+ "name": "anger",
+ "unicode": "1F4A2",
+ "digest": "2253b7ff0894f247bc6f04d841a748c56d6c94684880c13df42387691ff20e75"
+ },
+ {
+ "name": "anger_left",
+ "unicode": "1F5EE",
+ "digest": "f2711991e8b386b2d5b12f296ce20a9b4b00ef91d6d67af2cf4e06abf2faa1dc"
+ },
+ {
+ "name": "anger_right",
+ "unicode": "1F5EF",
+ "digest": "24b572d64c519251a3ae8844e8d66fd6955752aff99aebe7dc20179505a466c4"
+ },
+ {
+ "name": "angry",
+ "unicode": "1F620",
+ "digest": "c4188ba70df99d8ccef5706d711176725d3dd50d62f065a177d68d85c7828107"
+ },
+ {
+ "name": "anguished",
+ "unicode": "1F627",
+ "digest": "9c2347308133ae50dc04da62042fff847f4c477b2956b8aa976f0413899e38bc"
+ },
+ {
+ "name": "ant",
+ "unicode": "1F41C",
+ "digest": "d2af2ed1cfe15d649aa329d965764a1e8726941d833841781a5b66d7dd0b0921"
+ },
+ {
+ "name": "apple",
+ "unicode": "1F34E",
+ "digest": "a9babee24f454934a5e1fb8d781cbce354dfd88e8a8e01f02e8b30071fd40460"
+ },
+ {
+ "name": "aquarius",
+ "unicode": "2652",
+ "digest": "1a168c252678847d1f9ef450887489e3bdc207ecae4b6fb05e92295ff861ae2c"
+ },
+ {
+ "name": "aries",
+ "unicode": "2648",
+ "digest": "bde262a8795e12f8b0ebb3f0f8c3a56104062fcee8d5d678cf4bb445a7daf698"
+ },
+ {
+ "name": "arrow_backward",
+ "unicode": "25C0",
+ "digest": "ddae36d1febf5c246e51d599e2898a8aa30cd47f88b5bcb469e3ca9d22538b97"
+ },
+ {
+ "name": "arrow_double_down",
+ "unicode": "23EC",
+ "digest": "906f42b5f788128ed90d2d162cf03e6e595a50ad05e0aa5f64e925637379d0cd"
+ },
+ {
+ "name": "arrow_double_up",
+ "unicode": "23EB",
+ "digest": "2129a57402980de6fc6f59ad8354525c2dbcd66d1b78f4de091181ddc81e0693"
+ },
+ {
+ "name": "arrow_down",
+ "unicode": "2B07",
+ "digest": "370e4f41565d5dab245c20e45c502505a56d26c2392283781b841eb3e905edb2"
+ },
+ {
+ "name": "arrow_down_small",
+ "unicode": "1F53D",
+ "digest": "98a2b183f2daec425160bbfce1d2b940b8baa0d5032fdacfa9453e39bed5651b"
+ },
+ {
+ "name": "arrow_forward",
+ "unicode": "25B6",
+ "digest": "348627b8e0f55cf1e9ab19c9de1d170371b2c4cb4dda9a2aa8e0c558db08b18a"
+ },
+ {
+ "name": "arrow_heading_down",
+ "unicode": "2935",
+ "digest": "96c64953fc3134711247bef320f252c48993ebc90494925b7fee42ffce2a2ec2"
+ },
+ {
+ "name": "arrow_heading_up",
+ "unicode": "2934",
+ "digest": "94f94e74176cc050703b3584f3f700debf86e4e61b893a441825a21fa3f8ce74"
+ },
+ {
+ "name": "arrow_left",
+ "unicode": "2B05",
+ "digest": "4553be62a63d7550deac4f7dbeffce6006f769ae6cddfb8c795671672011ba0b"
+ },
+ {
+ "name": "arrow_lower_left",
+ "unicode": "2199",
+ "digest": "10f83c252110d705cdcfebc35a70c341ad288730d0c0729479e3a96e263d5120"
+ },
+ {
+ "name": "arrow_lower_right",
+ "unicode": "2198",
+ "digest": "ee33abd4c96c19e9b80a2fc1500ba8ecaa6668c49310cc816a496e8c61af3850"
+ },
+ {
+ "name": "arrow_right",
+ "unicode": "27A1",
+ "digest": "2611e9138a2651916f414015d0287f5f0af266514d96a42915d32b04fb652a90"
+ },
+ {
+ "name": "arrow_right_hook",
+ "unicode": "21AA",
+ "digest": "628b06384a2963a4fe81e9fbf4e22511f697878d9b9db7d2fc98f8aadbe8f4f9"
+ },
+ {
+ "name": "arrow_up",
+ "unicode": "2B06",
+ "digest": "c09e5f41c01028b45707c525d30d3d6731ec57b7447f0d7ba4ad6c1404449e5c"
+ },
+ {
+ "name": "arrow_up_down",
+ "unicode": "2195",
+ "digest": "e7fd92d24a01702f76c7fcc0de998bc81fbfb93711d076984f6da91d1dccd84c"
+ },
+ {
+ "name": "arrow_up_small",
+ "unicode": "1F53C",
+ "digest": "bc48dad74bc1d0c5579cbf5e3d005314b0d21bc5b5ebbba2b05136e33f49296d"
+ },
+ {
+ "name": "arrow_upper_left",
+ "unicode": "2196",
+ "digest": "792a9709f03843024e53d201cb4769c59b656c3bf0dff2306e8e605493a66b93"
+ },
+ {
+ "name": "arrow_upper_right",
+ "unicode": "2197",
+ "digest": "ee934b0c9cff270efd30a6cafc15253d405efd2c93b4785ac2ed4ea6420266a6"
+ },
+ {
+ "name": "arrows_clockwise",
+ "unicode": "1F503",
+ "digest": "914f4120513730d7a19c9f8c4e59223a90568de0b25a225b712b31fa9697ef4f"
+ },
+ {
+ "name": "arrows_counterclockwise",
+ "unicode": "1F504",
+ "digest": "86d87597e4e3db6dbba9907ee82412db0cbab1ea875bd0be6505dd886dc19b90"
+ },
+ {
+ "name": "art",
+ "unicode": "1F3A8",
+ "digest": "dfc6b0da780199df86507d65b0499ba1706c266ae7badcb0e7fb5b85af7c9578"
+ },
+ {
+ "name": "articulated_lorry",
+ "unicode": "1F69B",
+ "digest": "4c4de240ebd175f7b53453eda4e51f2e57d0db2a98d317f804116e14e47cff1d"
+ },
+ {
+ "name": "ascending_notes",
+ "unicode": "1F39C",
+ "digest": "33432042771d456338dda5d98e49322d3600f2cc9049963480c7c38d9de1ef0a"
+ },
+ {
+ "name": "asterisk",
+ "unicode": "002A-20E3",
+ "digest": "0b7f27f545b616677c83d40ff957337477b2881459b4d3c839ae55e23797419f"
+ },
+ {
+ "name": "astonished",
+ "unicode": "1F632",
+ "digest": "58632b97e274ade5183752db2b3c5c4fe29effcd5a9720a8d01fa809b97023dc"
+ },
+ {
+ "name": "athletic_shoe",
+ "unicode": "1F45F",
+ "digest": "1fc55d85a4d6751f9e60467801b051d2fb3341bdcc33b8d3695d5143359edb43"
+ },
+ {
+ "name": "atm",
+ "unicode": "1F3E7",
+ "digest": "bf827ef6c349f5b6912d821457975a4720d1750529d907e94ece429b7a388d7e"
+ },
+ {
+ "name": "atom",
+ "unicode": "269B",
+ "digest": "cbce1725602efbb77a935cfae5407e4d75489ee988910296c7f6140665afc669"
+ },
+ {
+ "name": "b",
+ "unicode": "1F171",
+ "digest": "9116256b3189977e37f6da7ddedf82bb29b0358829a4e8718fd59e51d9b86b3c"
+ },
+ {
+ "name": "baby",
+ "unicode": "1F476",
+ "digest": "66596bea11015154e0b1752b85f349f4286c6643ee6f51ee5e60e0d625c4ae9a"
+ },
+ {
+ "name": "baby_bottle",
+ "unicode": "1F37C",
+ "digest": "ed42994b4a539b8bfeccde0f3c7e9c7f54d6696ff48ce7e48171bbab51002348"
+ },
+ {
+ "name": "baby_chick",
+ "unicode": "1F424",
+ "digest": "ea2cfa0e5c2cbff5fffdb52cc04dfe7872834bd7cfeaa45e0541b8faffcbd0e9"
+ },
+ {
+ "name": "baby_symbol",
+ "unicode": "1F6BC",
+ "digest": "65df04dff8739b86f7663ae9c0648927341f360a986655e109721b0e16013b75"
+ },
+ {
+ "name": "baby_tone1",
+ "unicode": "1F476-1F3FB",
+ "digest": "bc747527a2d723cf99ef3fc2539c19d29634c92ff417736982d3bf87d65d06eb"
+ },
+ {
+ "name": "baby_tone2",
+ "unicode": "1F476-1F3FC",
+ "digest": "b82bba7a666b7d070751726e54acc7fb8f96e2dfc09e9610d61cfd20947aef9c"
+ },
+ {
+ "name": "baby_tone3",
+ "unicode": "1F476-1F3FD",
+ "digest": "7f45dfd4ea2ae8515d419ffa13e7ee5c625b024b4e521ace5344c414bb929da0"
+ },
+ {
+ "name": "baby_tone4",
+ "unicode": "1F476-1F3FE",
+ "digest": "80b1854626616f15426649cc6415e4911a55c8f761422fe48a08af9e8ac6a7cb"
+ },
+ {
+ "name": "baby_tone5",
+ "unicode": "1F476-1F3FF",
+ "digest": "9f890804d19a61bee76a29644c818045dd96cf69d67cfbca2d11f4ad376b27da"
+ },
+ {
+ "name": "back",
+ "unicode": "1F519",
+ "digest": "1dc73947b8f56e033777ca3f747407923bd16b07e53a6c78b09950ca474b7e7a"
+ },
+ {
+ "name": "badminton",
+ "unicode": "1F3F8",
+ "digest": "3f95180c1175d0248ebf4b8650cf86566c39e0486d828078244080194c14d4fe"
+ },
+ {
+ "name": "baggage_claim",
+ "unicode": "1F6C4",
+ "digest": "7c1a69511aa2a93984d601da4d1cef1cb4cefbbf127b1486278da8c01345bbf3"
+ },
+ {
+ "name": "balloon",
+ "unicode": "1F388",
+ "digest": "a10c2b0865179cdbdef339494ec9b2a109451a356e53738d6a9dd43232500956"
+ },
+ {
+ "name": "ballot_box",
+ "unicode": "1F5F3",
+ "digest": "0455ea75612efe78354315b4c345953d2d559bb471d5b01c1adc1d6b74ed693a"
+ },
+ {
+ "name": "ballot_box_check",
+ "unicode": "1F5F9",
+ "digest": "fc3ba16c009d963a4a0ea20a348ac98eee3c4c18c481df19a5ada0d1de7fcc15"
+ },
+ {
+ "name": "ballot_box_with_check",
+ "unicode": "2611",
+ "digest": "5f5cec7fe462557d31e8d2b836534c1e76d546cc0061236fa2af3667972b84aa"
+ },
+ {
+ "name": "ballot_box_x",
+ "unicode": "1F5F5",
+ "digest": "861dcfc2361298262587b5d0e163fed96a55c44636361f5b4a9ab1d6502b8928"
+ },
+ {
+ "name": "ballot_x",
+ "unicode": "1F5F4",
+ "digest": "0b73b89847eb82bcad5664644c8af237e0aef6c3d8c94b7a5df94e05d0ebf4e1"
+ },
+ {
+ "name": "bamboo",
+ "unicode": "1F38D",
+ "digest": "feb0cf2f1012a1c0649b8c66f7e96e2d8bcdefe879c5a52dab3e25c51009e3b2"
+ },
+ {
+ "name": "banana",
+ "unicode": "1F34C",
+ "digest": "aa9a1e6db00efa94a7f414c570eff7fc29011be64031a24d03b7f37b617cfd2d"
+ },
+ {
+ "name": "bangbang",
+ "unicode": "203C",
+ "digest": "bdd350766ccd1c0138f6294f7ebfa3e9867b02bda40a743f7062e52c68358765"
+ },
+ {
+ "name": "bank",
+ "unicode": "1F3E6",
+ "digest": "c9648c93049cf8e7884242e58ae3145383d2e5034c9090e0d34c53f5bbce397f"
+ },
+ {
+ "name": "bar_chart",
+ "unicode": "1F4CA",
+ "digest": "942277f72a5b754b13454dab62c85b1ff3447544f38ec76a285f3be32f6f5d12"
+ },
+ {
+ "name": "barber",
+ "unicode": "1F488",
+ "digest": "e1526eea685aafc56fb83d07f8ff63c9967600e447b0e5f831a17d6153f2062d"
+ },
+ {
+ "name": "baseball",
+ "unicode": "26BE",
+ "digest": "3d028b16a898f3a15874bc9d3891f9fbf59ea1c226c5c774eddb58a712c489ae"
+ },
+ {
+ "name": "basketball",
+ "unicode": "1F3C0",
+ "digest": "b2f5a3904d505db066337a24fc840ef75b49ef4c5f152227d8e632ff82285b12"
+ },
+ {
+ "name": "basketball_player",
+ "unicode": "26F9",
+ "digest": "e94beb69f631667479a80095bf313ceb3aa109d6ebb80f182722360a6d2a214e"
+ },
+ {
+ "name": "basketball_player_tone1",
+ "unicode": "26F9-1F3FB",
+ "digest": "6fc77cf2f26ee18e9a3faea500d4277839f77633f31ee618a68c301f1ad32d90"
+ },
+ {
+ "name": "basketball_player_tone2",
+ "unicode": "26F9-1F3FC",
+ "digest": "6ee9060c24d92708e12a854fb0bdf5c717c90b8c0350d8aa40c278b41bfa12fc"
+ },
+ {
+ "name": "basketball_player_tone3",
+ "unicode": "26F9-1F3FD",
+ "digest": "752e90dbfa7c7a9ae3f37de924e22f3c3d5a7e54dd41c8e8eb99cabb0dad73cf"
+ },
+ {
+ "name": "basketball_player_tone4",
+ "unicode": "26F9-1F3FE",
+ "digest": "38bedc3074e6243454d568d9b665f5764f1a3d983875651ce7a1cdb53da9f6c8"
+ },
+ {
+ "name": "basketball_player_tone5",
+ "unicode": "26F9-1F3FF",
+ "digest": "25ee1e84670d3db96d3ad098c859abd6b3448f55f668ce0c195ee2337a215de7"
+ },
+ {
+ "name": "bath",
+ "unicode": "1F6C0",
+ "digest": "ae6301a6354630cd9dc06a5137f23f826d019c8298b2b012b6ff31b773a910b6"
+ },
+ {
+ "name": "bath_tone1",
+ "unicode": "1F6C0-1F3FB",
+ "digest": "fce7ae2e7ef3f7f44f36c2ad49348b4cf7fce0b0c17e1a90a1e85734cee95b2a"
+ },
+ {
+ "name": "bath_tone2",
+ "unicode": "1F6C0-1F3FC",
+ "digest": "4d1c9444f16467488fe939fdad279d6855d28be564e5dcc1990451c4b9ae8c95"
+ },
+ {
+ "name": "bath_tone3",
+ "unicode": "1F6C0-1F3FD",
+ "digest": "9a59a4360effb48af4cbb1a953655ef61e69375407038b4d0bd8068fbaf3cc16"
+ },
+ {
+ "name": "bath_tone4",
+ "unicode": "1F6C0-1F3FE",
+ "digest": "01aafa8a53a08018b9fbf28ec6b3b918d6bd0dee7a891196f32f81f60d114f0e"
+ },
+ {
+ "name": "bath_tone5",
+ "unicode": "1F6C0-1F3FF",
+ "digest": "2733e81ccaee21231c2e47e3310b431e9bd784bf34f0db609f8eadcee359500d"
+ },
+ {
+ "name": "bathtub",
+ "unicode": "1F6C1",
+ "digest": "9515e3bb9ab41350305e64fc6877aae82d51e1ba8ce8b2b4b8ffaeda960820cd"
+ },
+ {
+ "name": "battery",
+ "unicode": "1F50B",
+ "digest": "7d4d475c1d5b1be55c319953e3363ff864fe4fcd921a8aa649b9a547c0894deb"
+ },
+ {
+ "name": "beach",
+ "unicode": "1F3D6",
+ "digest": "52855d75cfa4476ccc23c58b4afcb76ee48abb22a9a6081210c8accefdf33099"
+ },
+ {
+ "name": "beach_umbrella",
+ "unicode": "26F1",
+ "digest": "cefe8e195d21d3e0769d3bfe15170db9e57c86db9d31cacb19fcdc8d2191b661"
+ },
+ {
+ "name": "bear",
+ "unicode": "1F43B",
+ "digest": "b5ac126875c20c82b9e3140b143233944a2e4132d781d0b575e83673988523cb"
+ },
+ {
+ "name": "bed",
+ "unicode": "1F6CF",
+ "digest": "1919245d7a76799aad0533eb72db2cbaa1f32ee8231a0c1989d3f233f2d42370"
+ },
+ {
+ "name": "bee",
+ "unicode": "1F41D",
+ "digest": "69ada63403c8dabae39c63ba143143aeb59b66faae6aa82d8342337925a9e6b5"
+ },
+ {
+ "name": "beer",
+ "unicode": "1F37A",
+ "digest": "b71dd6efdb4ce7d9d71fdbf82a2ccf83841fb0cceb119ee7da1e575d3bfa853c"
+ },
+ {
+ "name": "beers",
+ "unicode": "1F37B",
+ "digest": "994108cebfe0c614c05967af4e3864d8adbbfcf7cccef1cbd42a47b7dfabf80c"
+ },
+ {
+ "name": "beetle",
+ "unicode": "1F41E",
+ "digest": "ec351ce238a81711eef00e5be1de2e198423cf524b60e531d435902b44420edc"
+ },
+ {
+ "name": "beginner",
+ "unicode": "1F530",
+ "digest": "13288d9fc221dc02f4181b998104e13c3c5c98d3c4e650186bef59a46d39f6f0"
+ },
+ {
+ "name": "bell",
+ "unicode": "1F514",
+ "digest": "784b9a82814ce14a264e54b3a8f8e706f3c7b763646d9f8174c4aa84ad41ef09"
+ },
+ {
+ "name": "bellhop",
+ "unicode": "1F6CE",
+ "digest": "c15455f1b52ac26404b5c13a0e1070212ed1830026422873f4f6335e26e31259"
+ },
+ {
+ "name": "bento",
+ "unicode": "1F371",
+ "digest": "d59314b17a8646d4a78fefb7b79f289f33d4aaea893fed4cad0b890df63395e7"
+ },
+ {
+ "name": "bicyclist",
+ "unicode": "1F6B4",
+ "digest": "e7359d615d40325bb08a145cfebde2ecef448deeb21695a34b55d3ccb971447f"
+ },
+ {
+ "name": "bicyclist_tone1",
+ "unicode": "1F6B4-1F3FB",
+ "digest": "e45808faa32f4ffb881d3569c0b8e2c69d4a64665f4d1fae24d7a1e5f1d3ea4b"
+ },
+ {
+ "name": "bicyclist_tone2",
+ "unicode": "1F6B4-1F3FC",
+ "digest": "92a3494270d1da6a117e92402c7898d4a7fffbe3d6143fb9ae445c4827c0c8a4"
+ },
+ {
+ "name": "bicyclist_tone3",
+ "unicode": "1F6B4-1F3FD",
+ "digest": "6fdf1db2bbd08d06b643b08f0f29daeaa20e0b8c8abec21132191f435cc05e42"
+ },
+ {
+ "name": "bicyclist_tone4",
+ "unicode": "1F6B4-1F3FE",
+ "digest": "d9c27848e1bcc8197c858e1ef12a537f4ed6c77fb211b6731388dc88c2bb7a61"
+ },
+ {
+ "name": "bicyclist_tone5",
+ "unicode": "1F6B4-1F3FF",
+ "digest": "4892af1a8a0229a813d7b8e3d88481c2365e3e1a5ce2e0e27ce432c5336da810"
+ },
+ {
+ "name": "bike",
+ "unicode": "1F6B2",
+ "digest": "e726f97b5432f46ed51328c0930d1d63b3a2d7b67c5c2303a5ca997083cfcac1"
+ },
+ {
+ "name": "bikini",
+ "unicode": "1F459",
+ "digest": "7612fcb72c005ae7172260825f588d6995f2bc919cb3d283dd4591f6872a1855"
+ },
+ {
+ "name": "biohazard",
+ "unicode": "2623",
+ "digest": "81f8309318051255ed4dc18855a3cd3f8657a6f3b2d368caa531a57ce0e34235"
+ },
+ {
+ "name": "bird",
+ "unicode": "1F426",
+ "digest": "3f219e5aa18e2f1febfd368ec133786cd2eab357db79984cb8ba07fed0eec7cd"
+ },
+ {
+ "name": "birthday",
+ "unicode": "1F382",
+ "digest": "9eb1adb0170ab851042cb3da8b64f02f4e4b63e7a07db405b55b50f5bbd3cacf"
+ },
+ {
+ "name": "black_circle",
+ "unicode": "26AB",
+ "digest": "c2ba672994ad0f99d7fdc449f3fee45a2dca68a58f9fe95825b38465a30ef44e"
+ },
+ {
+ "name": "black_joker",
+ "unicode": "1F0CF",
+ "digest": "1eb85b8e2b93dec221a97a1c309dee3683408f6166e1a1a1bd83cf2f64f007dd"
+ },
+ {
+ "name": "black_large_square",
+ "unicode": "2B1B",
+ "digest": "0ff2112227c38ed8c30b0bddf2300e87d2a244cd7fe81886a1cb1a287a7e8bb6"
+ },
+ {
+ "name": "black_medium_small_square",
+ "unicode": "25FE",
+ "digest": "f1010aa694084ad4655a9d4ce5a1711eaab21029e31bf8798253f0ad644e8abb"
+ },
+ {
+ "name": "black_medium_square",
+ "unicode": "25FC",
+ "digest": "06bf48ffbc84e71bbb90aa0f6c3f9f53533c6fd063ff168cefdb0a050dcf8302"
+ },
+ {
+ "name": "black_nib",
+ "unicode": "2712",
+ "digest": "c1361df4a5ae9f2ed121d26928021e96c6865331861e1960700d39cb1bd49355"
+ },
+ {
+ "name": "black_small_square",
+ "unicode": "25AA",
+ "digest": "d430ec419869fa1b5ba980ddeecb4c5ad5050a2b3421e45048cc184a6fc46899"
+ },
+ {
+ "name": "black_square_button",
+ "unicode": "1F532",
+ "digest": "85b6587b6b2c3544ddb7bc07207b0740e437744ba134835836153899ae396135"
+ },
+ {
+ "name": "blossom",
+ "unicode": "1F33C",
+ "digest": "029bbe385e07e2017dd918d685e107678c9c0e919a3bd1521b7a0d7c9172da05"
+ },
+ {
+ "name": "blowfish",
+ "unicode": "1F421",
+ "digest": "b5ee9f6ffabb74e3024067f016d17a631ee98536cb9c7269d55fa867f95a54fb"
+ },
+ {
+ "name": "blue_book",
+ "unicode": "1F4D8",
+ "digest": "6fbf227fb9facc1957bb9dfb31749cbfe66c3afe8081347f2471fd64ef2e6b3a"
+ },
+ {
+ "name": "blue_car",
+ "unicode": "1F699",
+ "digest": "e61ef2299d11fc01e9d6c496d188a7211633946706f6e771c412368346ca16f4"
+ },
+ {
+ "name": "blue_heart",
+ "unicode": "1F499",
+ "digest": "1af8d04173e0a984360786f6031220000dd548b8c912a68fd51f2ba490a9e16a"
+ },
+ {
+ "name": "blush",
+ "unicode": "1F60A",
+ "digest": "d615cda0f7c185ed8a92008204043ef769f3b7fb5424d595aeaaf3827bcdbd73"
+ },
+ {
+ "name": "boar",
+ "unicode": "1F417",
+ "digest": "c23a06db0337597e361ae581eacd4faf9926c6b7db0510d3599eb2e2a73315cb"
+ },
+ {
+ "name": "bomb",
+ "unicode": "1F4A3",
+ "digest": "0099e7435eba35f4f3ad273993293693a8b5cd110567c95ed83e5b4e2d0978ff"
+ },
+ {
+ "name": "book",
+ "unicode": "1F4D6",
+ "digest": "152408f2ff9949b7cbe57f623e4f875aa8dd0b02317e03cc914e1ea3712b3fc7"
+ },
+ {
+ "name": "book2",
+ "unicode": "1F56E",
+ "digest": "26d6b66a1957e7750b3e22eb2e46d0cc85932977bbb81d3d8482ec1ec58ee12b"
+ },
+ {
+ "name": "bookmark",
+ "unicode": "1F516",
+ "digest": "a2e0c6f5466c1b2fc148b20f6afcf4a878f4df55b0181f61fffa3ff727dcb251"
+ },
+ {
+ "name": "bookmark_tabs",
+ "unicode": "1F4D1",
+ "digest": "16135d62ff440722bd1ce8f84219be6a5eb3120a1597bfda4aeed4a2d9e7d7b2"
+ },
+ {
+ "name": "books",
+ "unicode": "1F4DA",
+ "digest": "ba019e4174639440caec424b30dfa016fe71a6f7436fe63025a2e3609ebfc012"
+ },
+ {
+ "name": "boom",
+ "unicode": "1F4A5",
+ "digest": "ec26246935c99749950612d69c06435ccdc126f14426a48a7599c5b6b91d9d58"
+ },
+ {
+ "name": "boot",
+ "unicode": "1F462",
+ "digest": "7ed639d52e285b0f46064dd4e1f4a8fb5814e1b2dc47c6f93cb349a6ac7ea97a"
+ },
+ {
+ "name": "bouquet",
+ "unicode": "1F490",
+ "digest": "b699f13af218560344f3571436f87b6f8c5c9f0fa0308836937667241b3fc7aa"
+ },
+ {
+ "name": "bouquet2",
+ "unicode": "1F395",
+ "digest": "1643ec51ff26fc1ac0c67859e202386398650bf2a996c82b68e1b73fa52abf7d"
+ },
+ {
+ "name": "bow",
+ "unicode": "1F647",
+ "digest": "5e260c38cfc80cd2f20ef78d982126dbf90934f7afa12c96d0b7b413beb6d4e0"
+ },
+ {
+ "name": "bow_and_arrow",
+ "unicode": "1F3F9",
+ "digest": "1c23469256331ea4ff03c036f89f0e63ad3228c51faecba50129da99b7eaddf3"
+ },
+ {
+ "name": "bow_tone1",
+ "unicode": "1F647-1F3FB",
+ "digest": "d3ec7ef70b355ba310d6fae7130a4e4cd11526b6e219474b5678a2b3ba1077f0"
+ },
+ {
+ "name": "bow_tone2",
+ "unicode": "1F647-1F3FC",
+ "digest": "c2905c0feba15fbc533cc6b36038eeda30f729182aa544f1d9164f5ccfed64d5"
+ },
+ {
+ "name": "bow_tone3",
+ "unicode": "1F647-1F3FD",
+ "digest": "298fc646d96c307eaa137c80b403d8355539ed8af13d3954a4ccacef67d341fa"
+ },
+ {
+ "name": "bow_tone4",
+ "unicode": "1F647-1F3FE",
+ "digest": "27db8401aa62a2544b24ff839b332958b5e8c3ab3fd7a289d3c62c654705da60"
+ },
+ {
+ "name": "bow_tone5",
+ "unicode": "1F647-1F3FF",
+ "digest": "168cdf834edb54723cf1c32311d4117c288132c5f76d6c415726c7484158c52a"
+ },
+ {
+ "name": "bowling",
+ "unicode": "1F3B3",
+ "digest": "0e888bcd1a5cc1ea7b07cea255ccb04dcdc87b0337b74cdc96a708aad7975768"
+ },
+ {
+ "name": "boy",
+ "unicode": "1F466",
+ "digest": "f349ab3e1015b4ccda5faab6a355f9c38e36e7c1cd667084563a14a2b11036ea"
+ },
+ {
+ "name": "boy_tone1",
+ "unicode": "1F466-1F3FB",
+ "digest": "4d04a5e45c9f9749de580321a212e14304b4ffcd229fa971fb59d97e6124262f"
+ },
+ {
+ "name": "boy_tone2",
+ "unicode": "1F466-1F3FC",
+ "digest": "0c9d6b6b1b3da68b9ef1f0f01efa4d170a48cfc66de4f577f8669c160b81cc97"
+ },
+ {
+ "name": "boy_tone3",
+ "unicode": "1F466-1F3FD",
+ "digest": "7dbecace78edb2aceffce6cb4d49ca132b93d80c26a8f1526a18832a2f23454a"
+ },
+ {
+ "name": "boy_tone4",
+ "unicode": "1F466-1F3FE",
+ "digest": "49f9c633afa8ff81068c78717e0012f8936fb3dcdb8b57342410f57f0635ae7c"
+ },
+ {
+ "name": "boy_tone5",
+ "unicode": "1F466-1F3FF",
+ "digest": "17e2ec379c7b542e6c2c5deef992af5f1fbaa3e288d1f71c8c984fb91a698cd4"
+ },
+ {
+ "name": "boys_symbol",
+ "unicode": "1F6C9",
+ "digest": "47fadbcb876ca436264ce2f3ebd1472bd68f55cc2b4833bf054335be9dc7a0f2"
+ },
+ {
+ "name": "bread",
+ "unicode": "1F35E",
+ "digest": "43697495538bfed11ed75213af8b1bdc14ef359d9b472cd7f9130fcb0a198680"
+ },
+ {
+ "name": "bride_with_veil",
+ "unicode": "1F470",
+ "digest": "37e75fbb2b0d06c900d51269b99107c60b61453dbf218b54df3011a455cd6dc3"
+ },
+ {
+ "name": "bride_with_veil_tone1",
+ "unicode": "1F470-1F3FB",
+ "digest": "44072e54e0618d2675a5bfd6572108590e51e8e733381e091e8754ee96c2cf20"
+ },
+ {
+ "name": "bride_with_veil_tone2",
+ "unicode": "1F470-1F3FC",
+ "digest": "f0acd961e108db9d9dd5d1b06e708b2eb6a7ef7235d6c8678b9319077faf4fa8"
+ },
+ {
+ "name": "bride_with_veil_tone3",
+ "unicode": "1F470-1F3FD",
+ "digest": "3f7adddb41ead3cd07098799ab2a5b8e8842344307d9045264403fb685f20555"
+ },
+ {
+ "name": "bride_with_veil_tone4",
+ "unicode": "1F470-1F3FE",
+ "digest": "5f7199fd99319651f3a7b3553cc5387c59b65cac1eb020441e19b5c12c807dc7"
+ },
+ {
+ "name": "bride_with_veil_tone5",
+ "unicode": "1F470-1F3FF",
+ "digest": "4b1f6c33dd72a3a11c764bb00e7be7441b39c7af78aae52141276a279d63ab78"
+ },
+ {
+ "name": "bridge_at_night",
+ "unicode": "1F309",
+ "digest": "f81cc36de8edbdf3fe4d55932d5c6c8ad429487ec1f7af044611b6dc950ee09c"
+ },
+ {
+ "name": "briefcase",
+ "unicode": "1F4BC",
+ "digest": "a3c3e802191f3e131683dac1fcd81e294dea72af8e65c94972990924c79c5619"
+ },
+ {
+ "name": "broken_heart",
+ "unicode": "1F494",
+ "digest": "4dee349274c2ea44d1c0395cbd39356b88897b0c45040aa40d8cb2607ee67420"
+ },
+ {
+ "name": "bug",
+ "unicode": "1F41B",
+ "digest": "bac4660ee8dcbef0023691804ee3fad3ea3d4bac20d847a5913cee6e7dca826c"
+ },
+ {
+ "name": "bulb",
+ "unicode": "1F4A1",
+ "digest": "af5394230f95781c7eb8054b1a13732a6e6170318599c79e9ca2a816a5b821a2"
+ },
+ {
+ "name": "bullettrain_front",
+ "unicode": "1F685",
+ "digest": "59afcd289500bd4148b1b91f560a5ce8ac9e1b52eddb8fec857ff5d171f017fb"
+ },
+ {
+ "name": "bullettrain_side",
+ "unicode": "1F684",
+ "digest": "79ff8f579081a2f1c3b05311a18ca432adb026a7860875cea4a5460e49b2a474"
+ },
+ {
+ "name": "bullhorn",
+ "unicode": "1F56B",
+ "digest": "a4ca5cbfe299e8ccd148d17055d2d395cf8515e416bf771044c9a670509a8254"
+ },
+ {
+ "name": "bullhorn_waves",
+ "unicode": "1F56C",
+ "digest": "92493636cf086205d1e12cc19e613b84152ef10b8cd0215619a0fc813bfc9a7c"
+ },
+ {
+ "name": "burrito",
+ "unicode": "1F32F",
+ "digest": "4babb1af1136ab2334d26495b0be779d0bcc9516fd956fc07ffde427d11122f0"
+ },
+ {
+ "name": "bus",
+ "unicode": "1F68C",
+ "digest": "476e7a5e92f64038e5012205395efead51f1c10b3edb25380f38da97e2412edd"
+ },
+ {
+ "name": "busstop",
+ "unicode": "1F68F",
+ "digest": "3bcf82872ab6abb0278238c71bd004a40c46696bdda05f54c153d45d6fe88f15"
+ },
+ {
+ "name": "bust_in_silhouette",
+ "unicode": "1F464",
+ "digest": "2230844993ab011fe2756a1aa3873ff7d5f7d888bddec408ba0b32e4f6003570"
+ },
+ {
+ "name": "busts_in_silhouette",
+ "unicode": "1F465",
+ "digest": "d1c3cb6d437616834425a53621c0bc0a6b368d745dd9da2300a3db4543d57660"
+ },
+ {
+ "name": "cactus",
+ "unicode": "1F335",
+ "digest": "e87588e6548d201db903dc0523b3ccc83c6b559981d743eae1504ce668cd8be4"
+ },
+ {
+ "name": "cake",
+ "unicode": "1F370",
+ "digest": "3947783d128018f5e396602d0492cb5c31e8e8df98af01eda7cade71aea8d989"
+ },
+ {
+ "name": "calculator",
+ "unicode": "1F5A9",
+ "digest": "01b47b5c69c12b65fa4f4c0d580f2a98280d6116f4ad2cf8be378759008bcc3c"
+ },
+ {
+ "name": "calendar",
+ "unicode": "1F4C6",
+ "digest": "00bb700dd88efbc43bc64263491cdf77965130b1dc23f31e682905c3dfe4040c"
+ },
+ {
+ "name": "calendar_spiral",
+ "unicode": "1F5D3",
+ "digest": "1dd5da98bb435c0c3f632bc0a5c9fdde694de7aee752bf4bb85def086e788a2a"
+ },
+ {
+ "name": "calling",
+ "unicode": "1F4F2",
+ "digest": "2375828085f2efd17b8a5ebb3cfec1e420190913328a7a0dd9ff0f67c7249ffb"
+ },
+ {
+ "name": "camel",
+ "unicode": "1F42B",
+ "digest": "9ff789ab50b51cd9e7fdc7fbe8d6f913fda95dfd425949f97974548652a53ce1"
+ },
+ {
+ "name": "camera",
+ "unicode": "1F4F7",
+ "digest": "d95192b9ba0f566d8874099125def031e15297d1306989ea9b6a49f7b9b56661"
+ },
+ {
+ "name": "camera_with_flash",
+ "unicode": "1F4F8",
+ "digest": "4db6fb3fdb9a004537dff97f4197c7ed87c9c978ba9ac562ed8bb7c1fa260d38"
+ },
+ {
+ "name": "camping",
+ "unicode": "1F3D5",
+ "digest": "f0855dc78bf6f3d06b3c2fc19180c8ff23d9e22871658fcc26a8fde08d328a0a"
+ },
+ {
+ "name": "cancellation_x",
+ "unicode": "1F5D9",
+ "digest": "cea2f7a48543207615ee06755ded62c2a95a7eaf7d7b68a3fc25e74d94e2c92c"
+ },
+ {
+ "name": "cancer",
+ "unicode": "264B",
+ "digest": "b990f85e9f62017d99526244eaef5c5e56f8808698011e85d44de1d2ed87f1a2"
+ },
+ {
+ "name": "candle",
+ "unicode": "1F56F",
+ "digest": "5eefd555951e65298583009a307acc6fb6d02c88325ef3adf231717e75e5a333"
+ },
+ {
+ "name": "candy",
+ "unicode": "1F36C",
+ "digest": "f14203c408173fbb94b4ee69d6de67226a17dc51b0cbd776f62623ee03fd2eb3"
+ },
+ {
+ "name": "capital_abcd",
+ "unicode": "1F520",
+ "digest": "2a7cc876218b8c244b9802448ee25ce5004671a4f00ea950a636d8c3b766dbef"
+ },
+ {
+ "name": "capricorn",
+ "unicode": "2651",
+ "digest": "03a5fd064c10f47c7fd0ae318c573bb559c269b1b2d61b45aa5b8ce9b5fbd9df"
+ },
+ {
+ "name": "card_box",
+ "unicode": "1F5C3",
+ "digest": "7d760ae1d44e6f4b2aac00895ca86b5743f8b5ca157ec2bd21ce2665e50ad23a"
+ },
+ {
+ "name": "card_index",
+ "unicode": "1F4C7",
+ "digest": "150950903eccb468981c58b87ed7c1ba44e17f52627d695f660ce96b3d9d6e8e"
+ },
+ {
+ "name": "carousel_horse",
+ "unicode": "1F3A0",
+ "digest": "d6862085550fa139a147dceb1b2b9f950a08dcd01cecd8b8697f9c7992ca054e"
+ },
+ {
+ "name": "cartridge",
+ "unicode": "1F5AD",
+ "digest": "0b1625eea118060b51a70905c1eb3313ed632e989f70943eca16aa29fe8a34f2"
+ },
+ {
+ "name": "cat",
+ "unicode": "1F431",
+ "digest": "002208c0c9165971853ee05cd05513175a913376a462a345a939d73401c6acb7"
+ },
+ {
+ "name": "cat2",
+ "unicode": "1F408",
+ "digest": "fbdb726cc035f83784dcfe2d9adb85f8aeec429064aed5c5ca0b8be406068aa5"
+ },
+ {
+ "name": "cd",
+ "unicode": "1F4BF",
+ "digest": "bd4d4eef2cc0b1e4ee1f5280f922743e76f27d35836987801b2b48969eac17d8"
+ },
+ {
+ "name": "celtic_cross",
+ "unicode": "1F548",
+ "digest": "187aac988d7e02085a15f31c4cc0ff25127be5b088e354e65c7b1152bffb40ff"
+ },
+ {
+ "name": "chains",
+ "unicode": "26D3",
+ "digest": "a6a915d9c361e1564e13cf2d33ad5df3d684aa349b8dc5909e6343d67401beb9"
+ },
+ {
+ "name": "champagne",
+ "unicode": "1F37E",
+ "digest": "77395d3afe5cc10bfdc381120bae2ae4aefdaa96c529536413873a696c5fa713"
+ },
+ {
+ "name": "chart",
+ "unicode": "1F4B9",
+ "digest": "9fd5f8cd99988bbe0fabc89a0b23e28d1468641d2f9468e82b7148a1948d8236"
+ },
+ {
+ "name": "chart_with_downwards_trend",
+ "unicode": "1F4C9",
+ "digest": "6fe456d76c0a996c12049057b5d60129098a9deddfa2d133cff5c4400e4595a0"
+ },
+ {
+ "name": "chart_with_upwards_trend",
+ "unicode": "1F4C8",
+ "digest": "e83cc4cf4228bd77e030a19755b11cf75cf671f40973c23e240afa54d9de478e"
+ },
+ {
+ "name": "checkered_flag",
+ "unicode": "1F3C1",
+ "digest": "77501c2c66af31f72f5c05f21e87598cd59740b5cfc02926c66dc755bab3c3cf"
+ },
+ {
+ "name": "cheese",
+ "unicode": "1F9C0",
+ "digest": "5897036ba97b557868bb314fcee83b9d8a609c8447b270a0b3d34a29ce7496d1"
+ },
+ {
+ "name": "cherries",
+ "unicode": "1F352",
+ "digest": "5a0ba73039e4b56e3d16a1c70ad992f41af7a16f6d5ba4b5337bdf338276f0ff"
+ },
+ {
+ "name": "cherry_blossom",
+ "unicode": "1F338",
+ "digest": "b40533225291f539ffe97e4ab1d70d07e179b2f9345b2814355164d0407cf3bf"
+ },
+ {
+ "name": "chestnut",
+ "unicode": "1F330",
+ "digest": "6a2a37899d28326daf36965b343b2646492c2c0cee8871321cc17315d6252a9a"
+ },
+ {
+ "name": "chicken",
+ "unicode": "1F414",
+ "digest": "13d770684a11ea10c0ae7570a98c5dfafd4bfb78ac3f72f46729aef9060b85c0"
+ },
+ {
+ "name": "children_crossing",
+ "unicode": "1F6B8",
+ "digest": "654d2502c1edc57c5ab4237df76db3121f6b8735eb13d30bffd305605a083445"
+ },
+ {
+ "name": "chipmunk",
+ "unicode": "1F43F",
+ "digest": "1ae3c838450afcbbe8a96992481dde252e343ab83546d0789ebed81a78ca9188"
+ },
+ {
+ "name": "chocolate_bar",
+ "unicode": "1F36B",
+ "digest": "2486b7265048eb2294d6be0a0a8a4d6067df95721ace9d131d8f715a27ba8cf0"
+ },
+ {
+ "name": "christmas_tree",
+ "unicode": "1F384",
+ "digest": "454c08870eaa84283c19731ed3b10c4868d2e2f0cc44f2feba0de9ba4cc9c4e1"
+ },
+ {
+ "name": "church",
+ "unicode": "26EA",
+ "digest": "b62e838ffb0dfefeced1707359437b6815e0721783b549212282e08617402f6f"
+ },
+ {
+ "name": "cinema",
+ "unicode": "1F3A6",
+ "digest": "6df56f6a0008d0352740d1e045ffdb702e80c2a6d88b6db1a8bcd27eb3c12dcc"
+ },
+ {
+ "name": "circus_tent",
+ "unicode": "1F3AA",
+ "digest": "f8b7a7f4cf4f9efd20423acc30abb3a28e2a5183b3e39f5cc88e7e0ed7757d64"
+ },
+ {
+ "name": "city_dusk",
+ "unicode": "1F306",
+ "digest": "8779066dc9386d05c951b1df1753983c2937a5f3b84d5fc09ed0b172d4ef914e"
+ },
+ {
+ "name": "city_sunset",
+ "unicode": "1F307",
+ "digest": "c2530d12204eb518c5a3c8d7deba11170b1412fdf406aea05a69d4c026210d1b"
+ },
+ {
+ "name": "cityscape",
+ "unicode": "1F3D9",
+ "digest": "15251a708d50fc721bd67d8abb2a517c0bade196df3b736e21d79191d749241f"
+ },
+ {
+ "name": "cl",
+ "unicode": "1F191",
+ "digest": "104591d8e7b980cf38dcf8326d36c845384b7a4e6d94c49f36e9946484712a95"
+ },
+ {
+ "name": "clap",
+ "unicode": "1F44F",
+ "digest": "ed6ef8bb78ca1fa295b87222c440c6d5ba4f154f2752bf0d428941260d66aaac"
+ },
+ {
+ "name": "clap_tone1",
+ "unicode": "1F44F-1F3FB",
+ "digest": "57a1fd1fa2578c30b8a47abb84e81af5f5bbc6c301a5daf0c53d4d07b017e777"
+ },
+ {
+ "name": "clap_tone2",
+ "unicode": "1F44F-1F3FC",
+ "digest": "2ad4dcd513e55486f21151bf3792e1febf116574d238545b07b4290901430fdd"
+ },
+ {
+ "name": "clap_tone3",
+ "unicode": "1F44F-1F3FD",
+ "digest": "2d8c705d4fcc162fb65cd51e2c6683f1129ebc72fba13343533f64ede1c62687"
+ },
+ {
+ "name": "clap_tone4",
+ "unicode": "1F44F-1F3FE",
+ "digest": "40ffd41b2b4f59d0040e9d20497e57c4e47f18aeae43fcae02be5c2f50069102"
+ },
+ {
+ "name": "clap_tone5",
+ "unicode": "1F44F-1F3FF",
+ "digest": "be55df1ac7600ba086c2ef6ea223ebc62271fa47876c53ade1a1c0151fdc994c"
+ },
+ {
+ "name": "clapper",
+ "unicode": "1F3AC",
+ "digest": "a8748398f56fd2c1e6e87fe0c77edec444df7c7dd462d43dbcea6d8de97c81c5"
+ },
+ {
+ "name": "classical_building",
+ "unicode": "1F3DB",
+ "digest": "6a607b0666141b51d6e944b04f3f6188a5c026396e6105f1d2a5e6b6350cd66b"
+ },
+ {
+ "name": "clipboard",
+ "unicode": "1F4CB",
+ "digest": "4ca1a0b864a962b111d6bdb65373b779f3fff571ffd32d029666f9b708e1ab73"
+ },
+ {
+ "name": "clock",
+ "unicode": "1F570",
+ "digest": "c48314ccde8bf01acc2b1bc9a6b5aa7d796fc0c8769f80398bc74545fcef31ed"
+ },
+ {
+ "name": "clock1",
+ "unicode": "1F550",
+ "digest": "c0550fa0c385920cbdb775bdaaa5e812097a484c4a32e35ebbafe3a364a4a438"
+ },
+ {
+ "name": "clock10",
+ "unicode": "1F559",
+ "digest": "25651ac5520505f326457364428de3679cc22ca57278d4c54cc4b60420fa7b74"
+ },
+ {
+ "name": "clock1030",
+ "unicode": "1F565",
+ "digest": "dbf682bac968fc5a3959af2b96eaaa5ee78306f6341c43c1345b94bc561a3d04"
+ },
+ {
+ "name": "clock11",
+ "unicode": "1F55A",
+ "digest": "333732dd6c3184f257964bcf5a20a6111f9adb04560b5d12dc613636e846df5b"
+ },
+ {
+ "name": "clock1130",
+ "unicode": "1F566",
+ "digest": "005999cb37998adea1645d7df63b2705a42db3b4f1a734891d79af3e833764ff"
+ },
+ {
+ "name": "clock12",
+ "unicode": "1F55B",
+ "digest": "6690e591bec1751e1c5472e0bf52f66779b2113e5b8c6c578e65dbb83d091b16"
+ },
+ {
+ "name": "clock1230",
+ "unicode": "1F567",
+ "digest": "549f3921bcff7f330c5a41e6756d8c15601f1f8278b35b369148771c60be2a6f"
+ },
+ {
+ "name": "clock130",
+ "unicode": "1F55C",
+ "digest": "9332ef07a9dde8ccaa1e58a3e97edee0601a1152fc6d351b782816c838d2a408"
+ },
+ {
+ "name": "clock2",
+ "unicode": "1F551",
+ "digest": "9d1ec8fbdae627880e1c067c10d6a40f1e4494a246c77224b3cd7b287554c4b4"
+ },
+ {
+ "name": "clock230",
+ "unicode": "1F55D",
+ "digest": "3578a39c28695d4e617a648a1eb44e0bb5a8a11dcbe04fa2eb2aea0a60589067"
+ },
+ {
+ "name": "clock3",
+ "unicode": "1F552",
+ "digest": "c2e2a27301b6ac27dc359be590448eb1e65fe87211f1af30a473d8bde4f3db47"
+ },
+ {
+ "name": "clock330",
+ "unicode": "1F55E",
+ "digest": "7a77cf8cf9a98f4767a2dca1d3795be45938eee185db81120d85cedebe128899"
+ },
+ {
+ "name": "clock4",
+ "unicode": "1F553",
+ "digest": "0945c4199400d546350cfff25bc9e9160789d1cf9890b3318bdc462ac6cc9782"
+ },
+ {
+ "name": "clock430",
+ "unicode": "1F55F",
+ "digest": "9fdb6f1fa076c4c6a395dbf6db27499ee447b3558f3aa64d913686c360e428a8"
+ },
+ {
+ "name": "clock5",
+ "unicode": "1F554",
+ "digest": "855b3500eb6d20bb6e51d3a6c9d1a5131c06404c6c149841c7cca52201036428"
+ },
+ {
+ "name": "clock530",
+ "unicode": "1F560",
+ "digest": "a6ebd9f884d45a1f43650351a1f1da9724bc044d7da2f6d99ffb3d1fa0c31c5d"
+ },
+ {
+ "name": "clock6",
+ "unicode": "1F555",
+ "digest": "e38f9fc4f87f12ee602dcf2285d59dbc343fc0fc37662992cfe9866c20f58e87"
+ },
+ {
+ "name": "clock630",
+ "unicode": "1F561",
+ "digest": "735954a650791fc38c845c43998023e652d36e55534850e43952878b8804b2f1"
+ },
+ {
+ "name": "clock7",
+ "unicode": "1F556",
+ "digest": "2c4244ec4019e9624e6ea5a751bb735ab87bead33b1ea160265c81bba3c2f736"
+ },
+ {
+ "name": "clock730",
+ "unicode": "1F562",
+ "digest": "0bcf20e30be1bb23394696770301867e307f8e5014e0ed7d75ed96efe34d625d"
+ },
+ {
+ "name": "clock8",
+ "unicode": "1F557",
+ "digest": "af454047a1765ef1c8355969302a826d4c47f5c61a6ec47fdec3510a8003b0d8"
+ },
+ {
+ "name": "clock830",
+ "unicode": "1F563",
+ "digest": "e48b81dac055dc6d5f7832cf34368329c573d03b35bfe076fed1c6e6d48a82e7"
+ },
+ {
+ "name": "clock9",
+ "unicode": "1F558",
+ "digest": "f2a3d1bc029dc0e6406cdaa96542e77503e4cfb79d99c69cb454b8cf635a73fc"
+ },
+ {
+ "name": "clock930",
+ "unicode": "1F564",
+ "digest": "bb1b2b83052e8e6fb97c48c13bce0d950907e044eb2dabf21d7fed321f75110b"
+ },
+ {
+ "name": "clockwise_arrows",
+ "unicode": "1F5D8",
+ "digest": "67027b7e1a4d800a3ce7d731c21c098d1109d217159a27665eebb7e080fc2622"
+ },
+ {
+ "name": "closed_book",
+ "unicode": "1F4D5",
+ "digest": "afd6dae5fa0f59330fc2adb922e92b3410a33a80a2667651718c7dac588010bc"
+ },
+ {
+ "name": "closed_lock_with_key",
+ "unicode": "1F510",
+ "digest": "d0ed5c00f939111ce86f9c741b733b22e04ebbd871aa33da3eb0f46a6f38b707"
+ },
+ {
+ "name": "closed_umbrella",
+ "unicode": "1F302",
+ "digest": "3ef08b299f9170007a5433fe82d0953bf0f75b6685d0ce58972f9af032dc471a"
+ },
+ {
+ "name": "cloud",
+ "unicode": "2601",
+ "digest": "d1e7932551e85c6e86bfb3b41f0c936a6d0953bf9f9119b8cca3eaed22ac0c01"
+ },
+ {
+ "name": "cloud_lightning",
+ "unicode": "1F329",
+ "digest": "fc9c85cc95f9c456635692c974f72b6d40e14943824b8129a21c47265c3416f4"
+ },
+ {
+ "name": "cloud_rain",
+ "unicode": "1F327",
+ "digest": "f4406e62ed98f6141ab70736f6d5c540023e805396db0346ee6b7082c3f5e8e2"
+ },
+ {
+ "name": "cloud_snow",
+ "unicode": "1F328",
+ "digest": "948990cd13dd927917208c026089519fcf8e258a8a284684ace67c9a2f9a8149"
+ },
+ {
+ "name": "cloud_tornado",
+ "unicode": "1F32A",
+ "digest": "44753516d0bd05d47cfa6eb922aba570ba6a87f805f325772b2cff071460ead1"
+ },
+ {
+ "name": "clubs",
+ "unicode": "2663",
+ "digest": "5fd19fadd3b0887a6a59819ffbbe33a061055c043200700c31be30e14a5d36d5"
+ },
+ {
+ "name": "cocktail",
+ "unicode": "1F378",
+ "digest": "cf096ebe15b4053702d490cd96f04d565b4993529bcd6d8d50cb821200d1cd92"
+ },
+ {
+ "name": "coffee",
+ "unicode": "2615",
+ "digest": "6ea6128e353d9f74aee99caaaaa30c53f996fb242bf3bffb0fa92e6b4d373e57"
+ },
+ {
+ "name": "coffin",
+ "unicode": "26B0",
+ "digest": "b59772d7aa262c4d7433f9cdf76d50011f4c63421b730c8ab4a08675f730c39f"
+ },
+ {
+ "name": "cold_sweat",
+ "unicode": "1F630",
+ "digest": "f0d0057bf01db8d930f6e4632c5bf8d0b1bc709bcfb6463a1f1973b5f1d70a83"
+ },
+ {
+ "name": "comet",
+ "unicode": "2604",
+ "digest": "00252ec55d1846d95c8d4c704b35251232d9810029fc215a7da08262dd1f3541"
+ },
+ {
+ "name": "compression",
+ "unicode": "1F5DC",
+ "digest": "432fbe66e5e3c38ebfeb4eb03465667a1e1be868b4afe510ec95eadda6481bde"
+ },
+ {
+ "name": "computer",
+ "unicode": "1F4BB",
+ "digest": "99777be010488867c7872b2e235be7c35b1a6f28d92baa921b61ced5491c0257"
+ },
+ {
+ "name": "computer_old",
+ "unicode": "1F5B3",
+ "digest": "b27c30d74f205a8a3bd00a55ca17da7cf6ae3b65ae33e949755a4c6bd69a9fd3"
+ },
+ {
+ "name": "confetti_ball",
+ "unicode": "1F38A",
+ "digest": "e77d0c0970d3d12e123e548639fc0fa3ce41668667e4be55baefc09dfaa22cb0"
+ },
+ {
+ "name": "confounded",
+ "unicode": "1F616",
+ "digest": "0f51db64149151d3d7ae5dce08c9af3d064123524fa36fe1f51a78cbd966b6ea"
+ },
+ {
+ "name": "confused",
+ "unicode": "1F615",
+ "digest": "ed23587432c1be98356156784ca4fe0b374b7b3b371660d45cfb0a1efd44e322"
+ },
+ {
+ "name": "congratulations",
+ "unicode": "3297",
+ "digest": "2a46d640bf24fd4dc7649baf4b28c4adb30eda8d24d70eda07036c85b48195e0"
+ },
+ {
+ "name": "construction",
+ "unicode": "1F6A7",
+ "digest": "73fac9fb5eb91954b0f998f9d05fb953241eed988c134fa42477393159fa34fa"
+ },
+ {
+ "name": "construction_site",
+ "unicode": "1F3D7",
+ "digest": "0ff52e6adf1927d356b27be5fef6bad2ad842be05e3a0bd16a17efe78e5676d9"
+ },
+ {
+ "name": "construction_worker",
+ "unicode": "1F477",
+ "digest": "2be436fa7ad0a31e328fc6f776044bd1eec35c99541ced891792e3bef738d0a0"
+ },
+ {
+ "name": "construction_worker_tone1",
+ "unicode": "1F477-1F3FB",
+ "digest": "172cebc84f91237a85292c5ab0a105cc3abbb96e7423c4ae81feffd00bdb3b26"
+ },
+ {
+ "name": "construction_worker_tone2",
+ "unicode": "1F477-1F3FC",
+ "digest": "3e9b96ddfd639eefda99ad3a0ad26a28a0f2c8be72988c2bdbd648e6104638b6"
+ },
+ {
+ "name": "construction_worker_tone3",
+ "unicode": "1F477-1F3FD",
+ "digest": "11f83c565168dce5ac2387b873769d85ec4087171d6e92fc766c209ea06cd4f3"
+ },
+ {
+ "name": "construction_worker_tone4",
+ "unicode": "1F477-1F3FE",
+ "digest": "09e320e78e3a2940f0c5a0ef9a235ab72c51e053fd8ff433843fdb62571c8e70"
+ },
+ {
+ "name": "construction_worker_tone5",
+ "unicode": "1F477-1F3FF",
+ "digest": "7ac2a1a0038e7aefea889380be604a98255823587e90799165f7db39dd03a0cc"
+ },
+ {
+ "name": "control_knobs",
+ "unicode": "1F39B",
+ "digest": "9f10e578b410ff6aa7cc7fe806a0f1181893765303c0ca3867b652f1392a8a22"
+ },
+ {
+ "name": "contruction_site",
+ "unicode": "1F3D7",
+ "digest": "0ff52e6adf1927d356b27be5fef6bad2ad842be05e3a0bd16a17efe78e5676d9"
+ },
+ {
+ "name": "convenience_store",
+ "unicode": "1F3EA",
+ "digest": "1ff4351e4a4503f58ed5d35074a2112c681337e35ffe55332187481685573606"
+ },
+ {
+ "name": "cookie",
+ "unicode": "1F36A",
+ "digest": "5c78ce2e721b0a3767d6ce0b59c1e88fdf94a7edc94e98c4d6b7aadb5b2aeea7"
+ },
+ {
+ "name": "cool",
+ "unicode": "1F192",
+ "digest": "54a96697a5070388ce8364a5ee2e0d78a53acc8b4f6755b1359fd67252cc41e8"
+ },
+ {
+ "name": "cop",
+ "unicode": "1F46E",
+ "digest": "16bee252c2a133bcf57f6d7b8372a61364744a2f662acb90e2005732555135fa"
+ },
+ {
+ "name": "cop_tone1",
+ "unicode": "1F46E-1F3FB",
+ "digest": "2fc52f3ed735e327d12dadb15f9feb7b7f720fc6857b551548a2a84809053817"
+ },
+ {
+ "name": "cop_tone2",
+ "unicode": "1F46E-1F3FC",
+ "digest": "6208f3174ced4f07ba3820ba838b247d7438d69d86eb04927333e7436e56af7e"
+ },
+ {
+ "name": "cop_tone3",
+ "unicode": "1F46E-1F3FD",
+ "digest": "2427d30bdfe127be4d8c3870472cae191eece142c784a5c2809df938f43e7c53"
+ },
+ {
+ "name": "cop_tone4",
+ "unicode": "1F46E-1F3FE",
+ "digest": "6e73f8abdf816f3cb2728b971a5a8d006a236c1d71b2ee1788ab60329f406323"
+ },
+ {
+ "name": "cop_tone5",
+ "unicode": "1F46E-1F3FF",
+ "digest": "4b146465cc95ade7e9ca722e31a1b06311214dae8f7f4d95c6329d56c45b451f"
+ },
+ {
+ "name": "copyright",
+ "unicode": "00A9",
+ "digest": "8143583821085dfc8ac21079fe220288ba3a3b6ca3014dc5dc98b18da77589c1"
+ },
+ {
+ "name": "corn",
+ "unicode": "1F33D",
+ "digest": "0160502226b5f9af81763545f288dbbb20632039d7509f347c751cfdb49dc5b5"
+ },
+ {
+ "name": "couch",
+ "unicode": "1F6CB",
+ "digest": "a93fffed194b404200495abda8772bb35539cfc8499eb0a9bf09c508afad6676"
+ },
+ {
+ "name": "couple",
+ "unicode": "1F46B",
+ "digest": "97fe611a613216a1788f9bd88a9deb4714ee123a66b5fd3d0ac916fbb4da7304"
+ },
+ {
+ "name": "couple_mm",
+ "unicode": "1F468-2764-1F468",
+ "digest": "3ae6fbf3ba168256ea85c756ac1e7b83fdb8b780d33f06128ed80706ff627eea"
+ },
+ {
+ "name": "couple_with_heart",
+ "unicode": "1F491",
+ "digest": "d9701173a5e8dff052ab6a15a42494dbb61dc7146d3734c82916abc9c05f76db"
+ },
+ {
+ "name": "couple_ww",
+ "unicode": "1F469-2764-1F469",
+ "digest": "d2a2ec29c1a1234ea0aa1d9fc6707cf8be8bb36ea8b92523ffa1c3071bcf0b06"
+ },
+ {
+ "name": "couplekiss",
+ "unicode": "1F48F",
+ "digest": "e722730de82397da7c8f88d79319b391e8f01fbe4a9133850cc92ad34e77bd82"
+ },
+ {
+ "name": "cow",
+ "unicode": "1F42E",
+ "digest": "dcc1efef2f02588806a156ed43da959c587d4c576ff6badec77f820ed3ba507f"
+ },
+ {
+ "name": "cow2",
+ "unicode": "1F404",
+ "digest": "dcf59f92fd0a37b2ca720bcda606defa4357b58d8f4ad15c1288ad8d814b2bc7"
+ },
+ {
+ "name": "crab",
+ "unicode": "1F980",
+ "digest": "59d34a4e92326ebeab188d9e33b25c20f4d54d187c274713fa3256b03b9e662a"
+ },
+ {
+ "name": "crayon",
+ "unicode": "1F58D",
+ "digest": "0f3351c2e68a8d47d27b45a9901be6160de0f9a291bd8680df84d0fc679bcb31"
+ },
+ {
+ "name": "credit_card",
+ "unicode": "1F4B3",
+ "digest": "708c0e7008e06e5d1b3b4e68a7e0ada9f4ae22ab6c28285d81a340f913fd9a84"
+ },
+ {
+ "name": "crescent_moon",
+ "unicode": "1F319",
+ "digest": "0959f838a410e8bfeebf00aa9658df56e515dbd2361142021071e17244662bfc"
+ },
+ {
+ "name": "cricket",
+ "unicode": "1F3CF",
+ "digest": "00eb11254e887c71db5e8945ad211e9e0280f1e02f4b77a4799b64bba2bbe9b3"
+ },
+ {
+ "name": "crocodile",
+ "unicode": "1F40A",
+ "digest": "99abcb42264d40d2450aaca8c3759a019bfd600a311cf3027243f1ca200d4639"
+ },
+ {
+ "name": "cross",
+ "unicode": "271D",
+ "digest": "a6e3c345cf6aa2ce690b66454066b53ef5b1dab2ed635e21f1586b1dffc5df42"
+ },
+ {
+ "name": "cross_heavy",
+ "unicode": "1F547",
+ "digest": "2e37c26b9bad0beb019c7f3e7a3892352d0ad9ca1b90c4333d42e8d56680be70"
+ },
+ {
+ "name": "cross_white",
+ "unicode": "1F546",
+ "digest": "3452e667010d7e49a51d7e1f4ba8ed4f303e33ed43255a051e9a18832a1efba6"
+ },
+ {
+ "name": "crossbones",
+ "unicode": "1F571",
+ "digest": "f5e7ce293c1a3282711073e68f033a3876e8428d1218cb2f8294630f9124e584"
+ },
+ {
+ "name": "crossed_flags",
+ "unicode": "1F38C",
+ "digest": "d4da057db289bec83f0106a94c89bd0cd9b52c7c7f8bc69bc8cbce480d53e12b"
+ },
+ {
+ "name": "crossed_swords",
+ "unicode": "2694",
+ "digest": "f159978583fa77c73ba6de85d35c4195cbd55963e537bd2bfd8f98ab8ff3559a"
+ },
+ {
+ "name": "crown",
+ "unicode": "1F451",
+ "digest": "e6fe2a28b7d80749ca121cabbe89321dcecdd760a122e73fb1562ea9bb40e90d"
+ },
+ {
+ "name": "cruise_ship",
+ "unicode": "1F6F3",
+ "digest": "90519c46ddfb63e71bc76661953da9041e5f0b97e9f8a7a8696518b4d529f3dd"
+ },
+ {
+ "name": "cry",
+ "unicode": "1F622",
+ "digest": "2d6a096796222c29b050f74db6b5aff9b9f61390c5eb56e45d1801918751002f"
+ },
+ {
+ "name": "crying_cat_face",
+ "unicode": "1F63F",
+ "digest": "df057d4e3e5c5c87caedf87ea3a6f936811b93f228f46bb7018d2bb5afaa6d35"
+ },
+ {
+ "name": "crystal_ball",
+ "unicode": "1F52E",
+ "digest": "7de438f88134c32c4db67d705e5fecf2a6187a87f56ebbb5bcc5ba09626e2935"
+ },
+ {
+ "name": "cupid",
+ "unicode": "1F498",
+ "digest": "7cb3f7d1ddf9678982197ef0e65735fb465ae8e3652d611f37d3bcccf4d7e2c1"
+ },
+ {
+ "name": "curly_loop",
+ "unicode": "27B0",
+ "digest": "881a43ae406cb74b2ef136bf970db9928bcdc3bbbb7393e90d2c597fe1dd9a96"
+ },
+ {
+ "name": "currency_exchange",
+ "unicode": "1F4B1",
+ "digest": "c4d76e9e61fac8d3c0cb9e07f1fbf1a7fcac6f4d4c78776ff7f04fc9391ce689"
+ },
+ {
+ "name": "curry",
+ "unicode": "1F35B",
+ "digest": "ebe41ee864c873e3a371888c0087b11dbcb124335812895002ed81fe2b6ba571"
+ },
+ {
+ "name": "custard",
+ "unicode": "1F36E",
+ "digest": "afc192f405c30e2d529ec0f4b31a7faf474bcd01fded5294dc38880b8bb22155"
+ },
+ {
+ "name": "customs",
+ "unicode": "1F6C3",
+ "digest": "5abb98151a79cebc1032c0ea149617093e42f41e50574a790a91074cabaa4c3a"
+ },
+ {
+ "name": "cyclone",
+ "unicode": "1F300",
+ "digest": "ae77e15bf2f312f03dbc5c7813d304005bbb549953482db9beb91810c585dc0e"
+ },
+ {
+ "name": "dagger",
+ "unicode": "1F5E1",
+ "digest": "377060a7ce930566a4732b361be98e8a193a546846dfbba2a00abeeef41d1976"
+ },
+ {
+ "name": "dancer",
+ "unicode": "1F483",
+ "digest": "e050db55afbb968e02219a58c7e82b824848d299a4df64f0d08d4e1872816203"
+ },
+ {
+ "name": "dancer_tone1",
+ "unicode": "1F483-1F3FB",
+ "digest": "350f6b2e4589fdd436173163035621b8da0bd49c7b9ec9f39593aae5e0ed0641"
+ },
+ {
+ "name": "dancer_tone2",
+ "unicode": "1F483-1F3FC",
+ "digest": "a9efc84ec80582f286147ca34162a27fd5989f4030084acdbc309d4368660f5b"
+ },
+ {
+ "name": "dancer_tone3",
+ "unicode": "1F483-1F3FD",
+ "digest": "ef187f44278fdb8605c80f5cf199e0b3de8a49085dada2e215bb91e1d7d3be5d"
+ },
+ {
+ "name": "dancer_tone4",
+ "unicode": "1F483-1F3FE",
+ "digest": "5195bc352dc9d24cc5505a167c756038e55c05048c61799ea1bfdf2debe44ac2"
+ },
+ {
+ "name": "dancer_tone5",
+ "unicode": "1F483-1F3FF",
+ "digest": "55cb7eee9fa11a16a3932800a19e334546f7396df6aadde22e58fe3185926b16"
+ },
+ {
+ "name": "dancers",
+ "unicode": "1F46F",
+ "digest": "39e7dfd9dafeee20f2968960b1179ee4bf3f2b63a3035fc1944024d0ae8b5de1"
+ },
+ {
+ "name": "dango",
+ "unicode": "1F361",
+ "digest": "2a1b50abe5dc72335344878d9b701028ccad651964d9e3affeedbf3c2bfd652a"
+ },
+ {
+ "name": "dark_sunglasses",
+ "unicode": "1F576",
+ "digest": "6bb1e911a93d5eb0581d3ce8f8929125d3d8fc04e086f3263cfd25af1348ce6c"
+ },
+ {
+ "name": "dart",
+ "unicode": "1F3AF",
+ "digest": "6f28741543a4c1eead21856128ffea1fcf772954fe6af40844dfde47f092ed32"
+ },
+ {
+ "name": "dash",
+ "unicode": "1F4A8",
+ "digest": "25aef37611f1c2f2e96518bf8aeba80580dca9634c8505d390c147388adf6746"
+ },
+ {
+ "name": "date",
+ "unicode": "1F4C5",
+ "digest": "de591b8fad608be761b839beefe9e4c2316320bcf0c44c543a1bc4b89923d938"
+ },
+ {
+ "name": "deciduous_tree",
+ "unicode": "1F333",
+ "digest": "ff31a52096ac1eae770f7f71b6d802198add2c8b4d9d7c9327071b6d6ab86c7b"
+ },
+ {
+ "name": "department_store",
+ "unicode": "1F3EC",
+ "digest": "c1e200d5fdd792121acabdb17bbcfe8e28a63757cfd895c72d4909f14de95ac2"
+ },
+ {
+ "name": "descending_notes",
+ "unicode": "1F39D",
+ "digest": "f09c6a2e094b13bf91cc07b7b776e43348ccef9f91247ca36cc02e7d91098af0"
+ },
+ {
+ "name": "desert",
+ "unicode": "1F3DC",
+ "digest": "e45815250bfc5411de516f87efa218874bcda4b0420b4c17182efc22ba0ce80d"
+ },
+ {
+ "name": "desktop",
+ "unicode": "1F5A5",
+ "digest": "ba46323e695918e7253f1013cb991efb09790581c74c07c38bc5e10a20b8e8de"
+ },
+ {
+ "name": "desktop_window",
+ "unicode": "1F5D4",
+ "digest": "d5b6c4a847e2a96f97f50fd353a22cb121915cb1d7bbc0f02df38769819b6b7e"
+ },
+ {
+ "name": "diamond_shape_with_a_dot_inside",
+ "unicode": "1F4A0",
+ "digest": "4e0e6364b8682dec9a9e20676161c9c9c0faf0a5fdd5402ca2668b18f2bb850a"
+ },
+ {
+ "name": "diamonds",
+ "unicode": "2666",
+ "digest": "42b13b2ed8e5fc63fbe81263c06cc203ba18a45ed5cc2a4fdbf617d219a0d3b4"
+ },
+ {
+ "name": "disappointed",
+ "unicode": "1F61E",
+ "digest": "7f1a619fef84960a9f312d17a58aa58105a4f20a4072efb10227892ab22475d8"
+ },
+ {
+ "name": "disappointed_relieved",
+ "unicode": "1F625",
+ "digest": "a389f5e0a4b619dbc406217967fb1f8f3d0e49b3f790e554ae0ececadbf98967"
+ },
+ {
+ "name": "dividers",
+ "unicode": "1F5C2",
+ "digest": "bf4c303452a4c0b4986925041dbec5b7e478060d560630b7c5bc2f997fcad668"
+ },
+ {
+ "name": "dizzy",
+ "unicode": "1F4AB",
+ "digest": "d6fba9b906f0eabd46686e416273a2ca6634249374385f2abf7ed284f0eef995"
+ },
+ {
+ "name": "dizzy_face",
+ "unicode": "1F635",
+ "digest": "b55e20c1551a2912bb5ec64a66c788c9d6f21594cc1da66032188f3814b03f40"
+ },
+ {
+ "name": "do_not_litter",
+ "unicode": "1F6AF",
+ "digest": "126f8c4085e0a8de8241f211f96c3f42c3e3400ea7d8fdf79a14443c3eceb972"
+ },
+ {
+ "name": "document",
+ "unicode": "1F5CE",
+ "digest": "2cbca96cc69306a10f1a9b6505723e027239439d899f6b395dc43f3c37d2d777"
+ },
+ {
+ "name": "document_text",
+ "unicode": "1F5B9",
+ "digest": "29407b12409c9673f3d89ef1f86ee50cbc7ed53b1870e33b4a29bbc609017f72"
+ },
+ {
+ "name": "dog",
+ "unicode": "1F436",
+ "digest": "c7b729de8a0967b1f38c3fa5ded94e77e329588caeaaf43abfd1090f420e62bf"
+ },
+ {
+ "name": "dog2",
+ "unicode": "1F415",
+ "digest": "e1897ca60bb3d2662cbe7933352e2b9c50739adf5901d3328797bf399575b97a"
+ },
+ {
+ "name": "dollar",
+ "unicode": "1F4B5",
+ "digest": "7db1e57f799439df1295d42b5249393f1e8cacc8df54caf30499c967a7282742"
+ },
+ {
+ "name": "dolls",
+ "unicode": "1F38E",
+ "digest": "398e7ff5780328700aadded7ce8c50757b1096af5cec66cc4d813a6714686b6d"
+ },
+ {
+ "name": "dolphin",
+ "unicode": "1F42C",
+ "digest": "27385af08848d93acdd13f72751074c2cbccb5ab3c6047e334598af74ed4862d"
+ },
+ {
+ "name": "door",
+ "unicode": "1F6AA",
+ "digest": "3365d7834086328ecbf1da0037f1cf1d0eb49534e173f7962a9e8f4b2ab87e26"
+ },
+ {
+ "name": "doughnut",
+ "unicode": "1F369",
+ "digest": "b4b99fdfe8d07b49cbdd78f8c57e4424819a4ffc8a3ba4867da44cbb3b3a5cca"
+ },
+ {
+ "name": "dove",
+ "unicode": "1F54A",
+ "digest": "4e2e9c47e5632efe6ccf945d61dbc2f1155a2e52905e17f307b502a2c951bdb8"
+ },
+ {
+ "name": "dragon",
+ "unicode": "1F409",
+ "digest": "d7d016568b54d67017681a075fb799d4a2a790ecfa2946d02dbcee629eb4975d"
+ },
+ {
+ "name": "dragon_face",
+ "unicode": "1F432",
+ "digest": "4d0025f1df63b62448477a8f08a50704e15caafb10fea476b529113f41797ab9"
+ },
+ {
+ "name": "dress",
+ "unicode": "1F457",
+ "digest": "02d56ed227280eaf5ad92830ee304afb81f74bb5a13c855397bcd04dd7fa51fb"
+ },
+ {
+ "name": "dromedary_camel",
+ "unicode": "1F42A",
+ "digest": "5afe8a0b73f9f4560264020b1e02a566149dbc38c15a00d2fb5cd90b32d09a75"
+ },
+ {
+ "name": "droplet",
+ "unicode": "1F4A7",
+ "digest": "a92c419792cbd3ba90ed21547362134cfac3e17a5304ee4e3872c9f7b561f834"
+ },
+ {
+ "name": "dvd",
+ "unicode": "1F4C0",
+ "digest": "1ba23e2f01ced5e192e4c1d2f766d9bce400470e81c81410139fd3c0739422df"
+ },
+ {
+ "name": "e-mail",
+ "unicode": "1F4E7",
+ "digest": "12135310cfedc091d120426f5b132df82b538c5fcad458bf6b21588f353c3adb"
+ },
+ {
+ "name": "ear",
+ "unicode": "1F442",
+ "digest": "70ba1103a34e68590d91a3b6f8acdbad3b1c65e46e31e26ee1cb855c1e21095e"
+ },
+ {
+ "name": "ear_of_rice",
+ "unicode": "1F33E",
+ "digest": "ddd5f3cc83dbdafd9115861eecd0128e52165bb1dd0049df06ffc564b650d384"
+ },
+ {
+ "name": "ear_tone1",
+ "unicode": "1F442-1F3FB",
+ "digest": "72977be94f5d287a09d175f98fba8b7955ae13aa12ce8e029c0ca875c02ee820"
+ },
+ {
+ "name": "ear_tone2",
+ "unicode": "1F442-1F3FC",
+ "digest": "5ff2e46cb3be7f13b8b94092246b58dab4c2a9ee2a5a46e0b84cf35a6928141f"
+ },
+ {
+ "name": "ear_tone3",
+ "unicode": "1F442-1F3FD",
+ "digest": "19b523f5ada2acaea94b922059c458a3303f4da1dd4c197cf25d31a0e6ecc4b2"
+ },
+ {
+ "name": "ear_tone4",
+ "unicode": "1F442-1F3FE",
+ "digest": "6a5cca9f49c539ef7d0883a2f39652f33ee2d3b25dca0234e4ba027ebbb2b466"
+ },
+ {
+ "name": "ear_tone5",
+ "unicode": "1F442-1F3FF",
+ "digest": "a0a56e8abd36e9be6e2448bcee6f56ecb8bf62d728b19ab6e8f9c6338e226b67"
+ },
+ {
+ "name": "earth_africa",
+ "unicode": "1F30D",
+ "digest": "d4921b543d7cf0c7344fa50c5e4d5a76c208d900be852adc1ee82ed4e8861a39"
+ },
+ {
+ "name": "earth_americas",
+ "unicode": "1F30E",
+ "digest": "61691e6aa9b8d90fc7f75fbc6cc7add5c36022d38f3e05c9d7c54dc44cf865bb"
+ },
+ {
+ "name": "earth_asia",
+ "unicode": "1F30F",
+ "digest": "262904cb552c7f5cf828a11071b3d430a74824b7464e8759ef93ee23b1705767"
+ },
+ {
+ "name": "egg",
+ "unicode": "1F373",
+ "digest": "a7dd617cad489c481ffd14937d9ed491cdd5756903e00473f42600c2fbefb600"
+ },
+ {
+ "name": "eggplant",
+ "unicode": "1F346",
+ "digest": "e5402e8ae5b7f9699ed86b97c242f7939d5731c5a364a2d5b9d04ea5d293cda1"
+ },
+ {
+ "name": "eight",
+ "unicode": "0038-20E3",
+ "digest": "34e293d3228e4643a0132d592f96db91b651fe6ced056ac3c8a3fd49c5ed3416"
+ },
+ {
+ "name": "eight_pointed_black_star",
+ "unicode": "2734",
+ "digest": "c3c2da75731a9a0f4f0a8d1f9cffef75c35e19b7f5d4081da33ac12b46be5fc2"
+ },
+ {
+ "name": "eight_spoked_asterisk",
+ "unicode": "2733",
+ "digest": "cc69618c1074d2b00e6f2c49df5e2c5ff6f4c0fae305505eb8c9daa65a0ea340"
+ },
+ {
+ "name": "electric_plug",
+ "unicode": "1F50C",
+ "digest": "732e1d1675233a0b4643cb73d0c352f8a5a56a11ee90d26627ad1e43c2e4a8e5"
+ },
+ {
+ "name": "elephant",
+ "unicode": "1F418",
+ "digest": "08df3910c4d5d8f49a72c47dd938195e495bde8fd8b3e7b17098a2c1afc41634"
+ },
+ {
+ "name": "end",
+ "unicode": "1F51A",
+ "digest": "05844ab9dcb43deff86f04617af6ea09215595de1415dcfaae018bced57938fe"
+ },
+ {
+ "name": "envelope",
+ "unicode": "2709",
+ "digest": "aad272511d0db910437ba25cf1fb9c806d47aad92a232edb87055916daf4676a"
+ },
+ {
+ "name": "envelope_back",
+ "unicode": "1F582",
+ "digest": "bc60b6d375feee00758a94a05b42eeb165f4084b20eb3e6012b72faa221f7e75"
+ },
+ {
+ "name": "envelope_flying",
+ "unicode": "1F585",
+ "digest": "9d6b6ca4c08006062a6f11948de3e15b13cf5c458967e39a9358665a8e13e214"
+ },
+ {
+ "name": "envelope_stamped",
+ "unicode": "1F583",
+ "digest": "f6102aea7283ddc136bfeb09589573420b9279105045fc6b965c1633c1297468"
+ },
+ {
+ "name": "envelope_stamped_pen",
+ "unicode": "1F586",
+ "digest": "80ea471318d1e04f8e525ff236b3cd4a4c864e66c6246b6aad77d92f56895f33"
+ },
+ {
+ "name": "envelope_with_arrow",
+ "unicode": "1F4E9",
+ "digest": "c1ba19b5e7cf64c547ac46eee139e6af70700d49ab511a96e6828c30feb116bc"
+ },
+ {
+ "name": "euro",
+ "unicode": "1F4B6",
+ "digest": "f571952583ffecfa5777065e4d1b680c423d25bc80e567a48fb5d7a1c1b5e735"
+ },
+ {
+ "name": "european_castle",
+ "unicode": "1F3F0",
+ "digest": "db82e383975d079a7bb006e7868035088d75c33bd4031cf8466b71089b65426f"
+ },
+ {
+ "name": "european_post_office",
+ "unicode": "1F3E4",
+ "digest": "d9b38e0f0ca3ad8895b40c767bdbb2b142ccaf03a86c2f275f57a31ed478801a"
+ },
+ {
+ "name": "evergreen_tree",
+ "unicode": "1F332",
+ "digest": "60d8b2d86b20255341f7ecad6d0f178ba9db5fa6b3de92f1b439cdb19f2fc0b1"
+ },
+ {
+ "name": "exclamation",
+ "unicode": "2757",
+ "digest": "cd900ecf82de2b26f0d7783dac4b3232ae94d2cddad5bfacea2eaf65b7ac0a09"
+ },
+ {
+ "name": "expressionless",
+ "unicode": "1F611",
+ "digest": "2ec9466b2d629907ce4c3e24e57f7ee556d2258ff011d972e14d0ae969a40c51"
+ },
+ {
+ "name": "eye",
+ "unicode": "1F441",
+ "digest": "790841e8fce647173eec3c5019440ad9c7e916c535f92acb3132bd92df148cad"
+ },
+ {
+ "name": "eye_in_speech_bubble",
+ "unicode": "1F441-1F5E8",
+ "digest": "bcde5a89a7653bff302685d9d632dd2723796a7ac73125fb7b9493d1ca848e0a"
+ },
+ {
+ "name": "eyeglasses",
+ "unicode": "1F453",
+ "digest": "fd140bef19c420bafe59368d35dd58a58a53e7145b104bae94be10f90679213b"
+ },
+ {
+ "name": "eyes",
+ "unicode": "1F440",
+ "digest": "57ed1f87ebe2485ea32ea69abdb8c5f7ccdcc149b33e74230d801f0883c68c5d"
+ },
+ {
+ "name": "factory",
+ "unicode": "1F3ED",
+ "digest": "6e6b35ae013e5dd26852c9a95d05c39e89c1c1950a33f47e7b951c34af18f37c"
+ },
+ {
+ "name": "fallen_leaf",
+ "unicode": "1F342",
+ "digest": "28ba8628065ffa973b525dd1455691c828d49c2b8c814af387880c13f6707f7e"
+ },
+ {
+ "name": "family",
+ "unicode": "1F46A",
+ "digest": "b5307f86e54cfea581e8406f4b95c801e250a893a9d208cc9a69a6d910b90932"
+ },
+ {
+ "name": "family_mmb",
+ "unicode": "1F468-1F468-1F466",
+ "digest": "49a753c3fcd4420800dd1cda585dae6bfa81615ad4862b477246456f86dc9e82"
+ },
+ {
+ "name": "family_mmbb",
+ "unicode": "1F468-1F468-1F466-1F466",
+ "digest": "882a3a0048efd666b0ab3a07b9f08041aa3a2acdab02664d0feff30bbfa70d68"
+ },
+ {
+ "name": "family_mmg",
+ "unicode": "1F468-1F468-1F467",
+ "digest": "45dd75c19d260a658c8ac93cf878976b96d2000f0efc9c59e72dacc80afb08fa"
+ },
+ {
+ "name": "family_mmgb",
+ "unicode": "1F468-1F468-1F467-1F466",
+ "digest": "910f44a348a951d36ee1f1484d237085bec5083c3875a4d908831dfc64530eaf"
+ },
+ {
+ "name": "family_mmgg",
+ "unicode": "1F468-1F468-1F467-1F467",
+ "digest": "012e75ad0d1b16c2ce63bf80a1ebfb1fc194229cfaf1241039599b82832f6aee"
+ },
+ {
+ "name": "family_mwbb",
+ "unicode": "1F468-1F469-1F466-1F466",
+ "digest": "049a32f61c54f093d2124e25f8b2ec7eac13161e2f2ebf6dc067797698cbe831"
+ },
+ {
+ "name": "family_mwg",
+ "unicode": "1F468-1F469-1F467",
+ "digest": "ba32c637caba634bda99ccba2a1a2a4b6f33aaaed933c30c7d5a51e8de1790d0"
+ },
+ {
+ "name": "family_mwgb",
+ "unicode": "1F468-1F469-1F467-1F466",
+ "digest": "198faba987f45429329b93bbce4f111329f284558bf0eecfa1424186b5f009fe"
+ },
+ {
+ "name": "family_mwgg",
+ "unicode": "1F468-1F469-1F467-1F467",
+ "digest": "3fa2e57cba314dcff04cf8186914823e1e081aabf34fa7437b05c58015df400c"
+ },
+ {
+ "name": "family_wwb",
+ "unicode": "1F469-1F469-1F466",
+ "digest": "b9592fc110a25a478569075deaa520308ef74579cd47aa44df9836599d68143f"
+ },
+ {
+ "name": "family_wwbb",
+ "unicode": "1F469-1F469-1F466-1F466",
+ "digest": "88f398997835fcf5153f17f6baf0deeb2a9c25ce2f8422192c18ac23e90b3193"
+ },
+ {
+ "name": "family_wwg",
+ "unicode": "1F469-1F469-1F467",
+ "digest": "c8d859d3c957fe0d535efccde295fe99bab76e3d28ab5a49c8e736608461cb2e"
+ },
+ {
+ "name": "family_wwgb",
+ "unicode": "1F469-1F469-1F467-1F466",
+ "digest": "006506e4a3d0c82642a0c8481ce95e5e3b969e20fe2def0a16dd686afddbc705"
+ },
+ {
+ "name": "family_wwgg",
+ "unicode": "1F469-1F469-1F467-1F467",
+ "digest": "2553f0deab133aad09b99411d9dd68b56fede30f55ee1f354358767765e36673"
+ },
+ {
+ "name": "fast_forward",
+ "unicode": "23E9",
+ "digest": "1baaed10969b60c083da754ee056bb71df36182cc65af40640acfb76f6b39200"
+ },
+ {
+ "name": "fax",
+ "unicode": "1F4E0",
+ "digest": "b0a392192d03bd5d1ad5ee8eea933cf64725b1776819537bbed27561d78192e7"
+ },
+ {
+ "name": "fearful",
+ "unicode": "1F628",
+ "digest": "7c4cc4de3357c2a6d6e779342b09dabb3ef832a32f2778a0ba074b446f588e8f"
+ },
+ {
+ "name": "feet",
+ "unicode": "1F43E",
+ "digest": "cae13fb54ec64dbcf86ea25bebe2b79877e2d4f5d810b867f095f1d3dfc7f144"
+ },
+ {
+ "name": "ferris_wheel",
+ "unicode": "1F3A1",
+ "digest": "a710a8a0fb039d953313b75330db37e3228d856593547b1f04dc83c00168b987"
+ },
+ {
+ "name": "ferry",
+ "unicode": "26F4",
+ "digest": "21ea239b5adb68dc1ce6c5a1993b0a0b835ef6cc7a0a27cb890838d8475504f6"
+ },
+ {
+ "name": "field_hockey",
+ "unicode": "1F3D1",
+ "digest": "1e46c7f0b5b79c90a5d211ea14cd7e358b1a26a3c8294439253f2b08d0e5c92e"
+ },
+ {
+ "name": "file_cabinet",
+ "unicode": "1F5C4",
+ "digest": "c0b7bdab6c98909eb0fbf1ac89da0008bb00ddb1cb57fe64b4a5ac993eeb18c9"
+ },
+ {
+ "name": "file_folder",
+ "unicode": "1F4C1",
+ "digest": "d98f93c6d7283df0c45f08d3d31ecf5b91b6db1b735959f19e42bfada500a0d1"
+ },
+ {
+ "name": "film_frames",
+ "unicode": "1F39E",
+ "digest": "754a0a60e978f8299a0c4f8959e1f9260f01683e15ae943db430036f01a79b18"
+ },
+ {
+ "name": "finger_pointing_down",
+ "unicode": "1F597",
+ "digest": "0c542ac3141e8f2e74767acd0eb399c2d68c779cb78bf16d437ad3b1f8134ad9"
+ },
+ {
+ "name": "finger_pointing_down2",
+ "unicode": "1F59F",
+ "digest": "c5b128a232cbf518544802a2ae1459368274297163721fa05d0103cf95b2b1ee"
+ },
+ {
+ "name": "finger_pointing_left",
+ "unicode": "1F598",
+ "digest": "d178ece691e2091be08db77fda9cf05462934628557358a8cb6222587b291f7e"
+ },
+ {
+ "name": "finger_pointing_right",
+ "unicode": "1F599",
+ "digest": "a412a47544d8f401f9181f8826c5fa3d6b42a1d76f6926963c2d9cd2a01be06d"
+ },
+ {
+ "name": "finger_pointing_up",
+ "unicode": "1F59E",
+ "digest": "32c2ccab52aa318a47c816d1bcf9c076e667c9ef3e64ce37d7ba7e827238690d"
+ },
+ {
+ "name": "fire",
+ "unicode": "1F525",
+ "digest": "b44311874681135acbb5e7226febe4365c732da3a9617f10d7074a3b1ade1641"
+ },
+ {
+ "name": "fire_engine",
+ "unicode": "1F692",
+ "digest": "3ae03fa34a7088ada95458eb4ee3e97691b3489149f6bbc168086f0483ed3bb2"
+ },
+ {
+ "name": "fire_engine_oncoming",
+ "unicode": "1F6F1",
+ "digest": "e2482c450136d373f74dfafddf502e0b675eb5d2e1e1c645f163db0e4d15fbb6"
+ },
+ {
+ "name": "fireworks",
+ "unicode": "1F386",
+ "digest": "3dee83a27c406960253ca1460eb88a599c7b81506051b69605a421b17fe8282c"
+ },
+ {
+ "name": "first_quarter_moon",
+ "unicode": "1F313",
+ "digest": "8fa066362d77bd889090bbe0904ca47f34704e29781c67133c6eaa521c3e1972"
+ },
+ {
+ "name": "first_quarter_moon_with_face",
+ "unicode": "1F31B",
+ "digest": "8877edb366f8eaa00fd83200acf5a17c3b84d246a250519d565dda3aea866ec3"
+ },
+ {
+ "name": "fish",
+ "unicode": "1F41F",
+ "digest": "9ce742108794cc15e59f7719623ae938efbd8155c93ad72585a32f4e32ea9414"
+ },
+ {
+ "name": "fish_cake",
+ "unicode": "1F365",
+ "digest": "1b5b14509287e30da9b8d7abcec376b247f9095aea4bf3fc320349f061a4c321"
+ },
+ {
+ "name": "fishing_pole_and_fish",
+ "unicode": "1F3A3",
+ "digest": "35db56776db1fcec7c8479922d57d54da2577cfe44a894bfd78c51c950c450fb"
+ },
+ {
+ "name": "fist",
+ "unicode": "270A",
+ "digest": "6b80ac2e4d8b830ae06f7c1626d456460094e4ba20c20fb82dabb6b3d2ce7605"
+ },
+ {
+ "name": "fist_tone1",
+ "unicode": "270A-1F3FB",
+ "digest": "d7c79f4f988dd68f064baa5a3a568ab299f8d409db45c8463f39b80e5dd6081f"
+ },
+ {
+ "name": "fist_tone2",
+ "unicode": "270A-1F3FC",
+ "digest": "d1108194e2d962f9ccd00131876d769a8e003117a460d18b2ccbf93e0a0ea346"
+ },
+ {
+ "name": "fist_tone3",
+ "unicode": "270A-1F3FD",
+ "digest": "12f5644b632c95a5c2e41cc9af299e286e266db8b3860091ef5be5f0c4ccc026"
+ },
+ {
+ "name": "fist_tone4",
+ "unicode": "270A-1F3FE",
+ "digest": "521a3ac573381f3bc37a08ddd2d122767aaa0b6b7a38050d3671a12343351816"
+ },
+ {
+ "name": "fist_tone5",
+ "unicode": "270A-1F3FF",
+ "digest": "604e5a234da1b9160e506b3c9026faf9e04268fced7b44baa1ef5e3d4efa83a4"
+ },
+ {
+ "name": "five",
+ "unicode": "0035-20E3",
+ "digest": "0cbd6cd11eb6c2d67749112750d125f4f0a07b53bb7bfb1de0986d943ea9d632"
+ },
+ {
+ "name": "flag_ac",
+ "unicode": "1F1E6-1F1E8",
+ "digest": "d9db1edeb709824a1083c2bba79ca5f683ed0edded35918bb167d1ee7396c8da"
+ },
+ {
+ "name": "flag_ad",
+ "unicode": "1F1E6-1F1E9",
+ "digest": "04a8c1745d9b8b20e903302379f2557e8082f72e33878db4cb2cd6b33eb97952"
+ },
+ {
+ "name": "flag_ae",
+ "unicode": "1F1E6-1F1EA",
+ "digest": "868324ac2e7bea1547f5de95f39633b77b8d62f3b3433b3d1a4ee96d169a09cd"
+ },
+ {
+ "name": "flag_af",
+ "unicode": "1F1E6-1F1EB",
+ "digest": "9a94458519e9db5d6cf1557e54fdf62d7e48aaf7de25744a093ec8f284656226"
+ },
+ {
+ "name": "flag_ag",
+ "unicode": "1F1E6-1F1EC",
+ "digest": "ea59fabc2bd9024df06a59a34412f52bebfeb03eb6abd73d8fe153e3a68e28f4"
+ },
+ {
+ "name": "flag_ai",
+ "unicode": "1F1E6-1F1EE",
+ "digest": "75676ded736ad2ebb921e9fd8ebfef49819a35c3dcf005bbc3b7e8c6e75178f2"
+ },
+ {
+ "name": "flag_al",
+ "unicode": "1F1E6-1F1F1",
+ "digest": "77b835dcff399b609e2479cbf10f08344c8fc277370ba8e4540165ca15563847"
+ },
+ {
+ "name": "flag_am",
+ "unicode": "1F1E6-1F1F2",
+ "digest": "3b820c628dd5a93137f7288a43553778f60b0beea4c0a239d063893c0723e73d"
+ },
+ {
+ "name": "flag_ao",
+ "unicode": "1F1E6-1F1F4",
+ "digest": "d26439d4ecbe8b67bb1ae9753454505358ebb6b802624f19800471e53ee27187"
+ },
+ {
+ "name": "flag_aq",
+ "unicode": "1F1E6-1F1F6",
+ "digest": "6b0b4e800d88ab289ae4b6d449bfa115e92543958b477d13ad348468a74e4616"
+ },
+ {
+ "name": "flag_ar",
+ "unicode": "1F1E6-1F1F7",
+ "digest": "ca76db601dd3f5794f1caace8ab5641fe3786b86e4ae030706162f0ce07d27b3"
+ },
+ {
+ "name": "flag_as",
+ "unicode": "1F1E6-1F1F8",
+ "digest": "170e1dde0e3fd2e0f2149de5cc8845e15580cc0412e81a643d61bd387de16141"
+ },
+ {
+ "name": "flag_at",
+ "unicode": "1F1E6-1F1F9",
+ "digest": "0ab3675a16b4988e87c81e87453c160d6616c7be76247f54c471dc63aa8b42ba"
+ },
+ {
+ "name": "flag_au",
+ "unicode": "1F1E6-1F1FA",
+ "digest": "b6f17d3dfd3547c069a0b6cddd4cf44fb8ce1d1d300e24284fb292ac142537e3"
+ },
+ {
+ "name": "flag_aw",
+ "unicode": "1F1E6-1F1FC",
+ "digest": "7857bc907f04dfb7ccc4401c05034ad8afb6383a022db77973cfcafa4d6c16c8"
+ },
+ {
+ "name": "flag_ax",
+ "unicode": "1F1E6-1F1FD",
+ "digest": "ab8f1fd4af7c220a54d478cec5a9f7f3beb5fc83439c448f3ac9848af8391ac1"
+ },
+ {
+ "name": "flag_az",
+ "unicode": "1F1E6-1F1FF",
+ "digest": "187cc7b6d39800c5910a34409db1e6b1d8aac808c72a93e922a419d9b054fd0b"
+ },
+ {
+ "name": "flag_ba",
+ "unicode": "1F1E7-1F1E6",
+ "digest": "cd22c744213087384cf79ed314742026787212c9ceb6999ed166534670f7864a"
+ },
+ {
+ "name": "flag_bb",
+ "unicode": "1F1E7-1F1E7",
+ "digest": "44ff0a48ac2d2180374baa58b1b7c64f26d0d151a48811eb08ffa20758104512"
+ },
+ {
+ "name": "flag_bd",
+ "unicode": "1F1E7-1F1E9",
+ "digest": "c18793d2b963458607a0bab94c57e62c8278fce870e96fd8dda78067a8fbde18"
+ },
+ {
+ "name": "flag_be",
+ "unicode": "1F1E7-1F1EA",
+ "digest": "6e6ccfca064a43b93c8acc04a9425f95af204198022ca20b9ee6c491e99ad950"
+ },
+ {
+ "name": "flag_bf",
+ "unicode": "1F1E7-1F1EB",
+ "digest": "d69c0394a1c7cb6323f54f024b7d740c728f229ca5e1b54ac374d5024f5470a5"
+ },
+ {
+ "name": "flag_bg",
+ "unicode": "1F1E7-1F1EC",
+ "digest": "413a270caf4a9155e84bdba6c9512277f5642246f6ba8d701383a5eeb02f7e95"
+ },
+ {
+ "name": "flag_bh",
+ "unicode": "1F1E7-1F1ED",
+ "digest": "9243ed65d7f24c824c2a3207335a2d4ad25251258547c16d0b7b7cbb9df6f8de"
+ },
+ {
+ "name": "flag_bi",
+ "unicode": "1F1E7-1F1EE",
+ "digest": "63056519030524b2d2dcd47448267d817205dbd6b98075c97f011a8f1d4d1a4b"
+ },
+ {
+ "name": "flag_bj",
+ "unicode": "1F1E7-1F1EF",
+ "digest": "93b245eed85d22260d27d1a8c77f51fb3439309e09b2aeca6cd504dbea77b509"
+ },
+ {
+ "name": "flag_bl",
+ "unicode": "1F1E7-1F1F1",
+ "digest": "5e1e478deaf02bbaa26595e4cefc5f5c9bec6105ce521b7b9ab4fa5e7a452c14"
+ },
+ {
+ "name": "flag_black",
+ "unicode": "1F3F4",
+ "digest": "df131e5c28e9f51dea53fe7f33551f91d420f7d686b7a62980f0154c6b5357a6"
+ },
+ {
+ "name": "flag_bm",
+ "unicode": "1F1E7-1F1F2",
+ "digest": "9dcd9e60faebe7f93eb19157e99f2ad654a8145c61738de96e6ecd11a246764a"
+ },
+ {
+ "name": "flag_bn",
+ "unicode": "1F1E7-1F1F3",
+ "digest": "078af6ca481a77871ba005e251a46ce63951c27b1b0cd33b9c1d0d31d349bc1a"
+ },
+ {
+ "name": "flag_bo",
+ "unicode": "1F1E7-1F1F4",
+ "digest": "92516d04e922a3bcbabe2e7619194bc972c09ba97576e8155f9829c397a71d8c"
+ },
+ {
+ "name": "flag_bq",
+ "unicode": "1F1E7-1F1F6",
+ "digest": "7832df5267a2bb8dddb83aeb11162ce79aeebdb718f2ac0e54adcf3d87936171"
+ },
+ {
+ "name": "flag_br",
+ "unicode": "1F1E7-1F1F7",
+ "digest": "aabcc1c082124045ed214f7d9778d8e2ed791ebb8433defea91db458658abeec"
+ },
+ {
+ "name": "flag_bs",
+ "unicode": "1F1E7-1F1F8",
+ "digest": "f628f39003608e181696634929522884165e27ccef55270293f92eeef991635f"
+ },
+ {
+ "name": "flag_bt",
+ "unicode": "1F1E7-1F1F9",
+ "digest": "af24a8ab34815da04c3e5af49a47449e0de93b068957cbda695816d0f830ca12"
+ },
+ {
+ "name": "flag_bv",
+ "unicode": "1F1E7-1F1FB",
+ "digest": "ff0037f6eed95d4bb5f2b502902360e1ff41426e2896daf3e0730cef1f8f7e41"
+ },
+ {
+ "name": "flag_bw",
+ "unicode": "1F1E7-1F1FC",
+ "digest": "3e3241ecb97946cc3e467b083d113a57dd305595e1512d4da18cc403e8689c1d"
+ },
+ {
+ "name": "flag_by",
+ "unicode": "1F1E7-1F1FE",
+ "digest": "bdd21885c6fac475241884a44149b887297772e17617ee59dd9fe8518d52cf3d"
+ },
+ {
+ "name": "flag_bz",
+ "unicode": "1F1E7-1F1FF",
+ "digest": "21c16e1da641af004576000bf1db44b9a1e0fccfddc775e96022721c2f18eeea"
+ },
+ {
+ "name": "flag_ca",
+ "unicode": "1F1E8-1F1E6",
+ "digest": "0d00e459084d58d3ea9c60488a9e51bf45f71b77f1600f190225d5ca6ca6c796"
+ },
+ {
+ "name": "flag_cc",
+ "unicode": "1F1E8-1F1E8",
+ "digest": "86ab27164603ef0f1f83fe898eda6fbb7bc5709f2518f5577f00817860806a7b"
+ },
+ {
+ "name": "flag_cd",
+ "unicode": "1F1E8-1F1E9",
+ "digest": "fdc2796530ada4bd0bae37ace4bbe707b321b287dcd64568f8e01d3a9df56066"
+ },
+ {
+ "name": "flag_cf",
+ "unicode": "1F1E8-1F1EB",
+ "digest": "5943bec02bede0931e21e7c34a68f375499f60a34883cc1edf2f21e9834b15ce"
+ },
+ {
+ "name": "flag_cg",
+ "unicode": "1F1E8-1F1EC",
+ "digest": "54498482e2772371e148e05cfb7c5eb55f6a22cd528662abdea10bad47d157da"
+ },
+ {
+ "name": "flag_ch",
+ "unicode": "1F1E8-1F1ED",
+ "digest": "53d6d35aeeebb0b4b1ad858dc3691e649ac73d30b3be76f96d5fe9605fa99386"
+ },
+ {
+ "name": "flag_ci",
+ "unicode": "1F1E8-1F1EE",
+ "digest": "3a173a3058a5c0174dc88750852cafec264e901ce82a6c69db122c8c0ea71a3a"
+ },
+ {
+ "name": "flag_ck",
+ "unicode": "1F1E8-1F1F0",
+ "digest": "42f395ff53c618b72b8a224cd4343d1a32f5ad82ced56bf590170a5ff0d5134c"
+ },
+ {
+ "name": "flag_cl",
+ "unicode": "1F1E8-1F1F1",
+ "digest": "9d6255feb690596904d800e72d5acdb5cda941c5a741b031ea39a3c7650ac46f"
+ },
+ {
+ "name": "flag_cm",
+ "unicode": "1F1E8-1F1F2",
+ "digest": "ffc99d14e0a8b46a980331090ed9f36f31a87f1b0f8dd8c09007a31c6127c69e"
+ },
+ {
+ "name": "flag_cn",
+ "unicode": "1F1E8-1F1F3",
+ "digest": "869a98c52bdc33591f87e2aab6cb4f13e98bb19136250ff25805d0312a8b7c8a"
+ },
+ {
+ "name": "flag_co",
+ "unicode": "1F1E8-1F1F4",
+ "digest": "6aa458440eb2500ad307fea40fd8f1171a1506a6e32af144a4fd51545bb56151"
+ },
+ {
+ "name": "flag_cp",
+ "unicode": "1F1E8-1F1F5",
+ "digest": "62627702e3e3768808c12f153a527ffcc492ad74d8cdc1858cfde971efd0c8ee"
+ },
+ {
+ "name": "flag_cr",
+ "unicode": "1F1E8-1F1F7",
+ "digest": "0f3b54d8330c5bb136647547dafc598bda755697cfd6b7d872a2443ba7b5cad4"
+ },
+ {
+ "name": "flag_cu",
+ "unicode": "1F1E8-1F1FA",
+ "digest": "69bc973002475bb3d9b54cb0ba9ec9cb85f144c1cf54689da0ee8f414ebb0d83"
+ },
+ {
+ "name": "flag_cv",
+ "unicode": "1F1E8-1F1FB",
+ "digest": "af2e135cf3c1b03a5937c068a75061b5cd332e95902fd0f8dffb2ac2dc89692a"
+ },
+ {
+ "name": "flag_cw",
+ "unicode": "1F1E8-1F1FC",
+ "digest": "df4b2228a82f766c5c64c13c1388482a68549e59dd843671ee0eb43506e33411"
+ },
+ {
+ "name": "flag_cx",
+ "unicode": "1F1E8-1F1FD",
+ "digest": "db12e513345a7be53954167d359ede0b3effbfb292508ee4d726123e3a8f83d7"
+ },
+ {
+ "name": "flag_cy",
+ "unicode": "1F1E8-1F1FE",
+ "digest": "0cea41d4820746e2c6eb408f7ec7419afba9f7396401d92e6c1d77382f721d0b"
+ },
+ {
+ "name": "flag_cz",
+ "unicode": "1F1E8-1F1FF",
+ "digest": "a1c2405916963be306f761539123486a2845af53716c9dfe94ad5420e14d36c4"
+ },
+ {
+ "name": "flag_de",
+ "unicode": "1F1E9-1F1EA",
+ "digest": "74a80b64437bc4e31bdd7cbb753ecd2d719bf34c506cbac535db83a644174cce"
+ },
+ {
+ "name": "flag_dg",
+ "unicode": "1F1E9-1F1EC",
+ "digest": "13cb5ea872f94a9c3fb579cef417e2d1ed38e8cbe95059576380cacd59bc4b9d"
+ },
+ {
+ "name": "flag_dj",
+ "unicode": "1F1E9-1F1EF",
+ "digest": "5b479654c28d3eeb70055c5e25dc46ccaba9eeea7537cc45ca9dbb8186b743b6"
+ },
+ {
+ "name": "flag_dk",
+ "unicode": "1F1E9-1F1F0",
+ "digest": "dee7fa9644a9b447417518a353e7edcbb37b2af8bc7d13a6ed71d7210c43ca3c"
+ },
+ {
+ "name": "flag_dm",
+ "unicode": "1F1E9-1F1F2",
+ "digest": "2e339190a8a0a238140f42e329f6646af5be75763a787ea268488a2e0440dc4c"
+ },
+ {
+ "name": "flag_do",
+ "unicode": "1F1E9-1F1F4",
+ "digest": "be5dafcd32d7197a96d37299a91835a8009299452f05a66d91c5fdec17448230"
+ },
+ {
+ "name": "flag_dz",
+ "unicode": "1F1E9-1F1FF",
+ "digest": "cf525d56bac45fe689f92d441274fc0ecbed4f95591d2c066598f72b1ee8d618"
+ },
+ {
+ "name": "flag_ea",
+ "unicode": "1F1EA-1F1E6",
+ "digest": "1acb13950f7c3692f9a36e618d8ec10a73ead5d7fa80fb52b6b2a18e3d456002"
+ },
+ {
+ "name": "flag_ec",
+ "unicode": "1F1EA-1F1E8",
+ "digest": "4d9d35450efc6026651ccc2278e70fb90b001ca5e5eecd31361b1e4e23253dbd"
+ },
+ {
+ "name": "flag_ee",
+ "unicode": "1F1EA-1F1EA",
+ "digest": "86ec7b2f618fe71dddec3d5a621b56b878d683780f1e0ad446f965326d42df48"
+ },
+ {
+ "name": "flag_eg",
+ "unicode": "1F1EA-1F1EC",
+ "digest": "f06d36a6fec15af4c1a76de30e8469847dde2728bb5a48956b4e466098b778a4"
+ },
+ {
+ "name": "flag_eh",
+ "unicode": "1F1EA-1F1ED",
+ "digest": "eb63f5b92c62c98dc008dfa7ad8830aa17fa23964f812a28055bd8b6f5960c5b"
+ },
+ {
+ "name": "flag_er",
+ "unicode": "1F1EA-1F1F7",
+ "digest": "e901195f7b37b22a6872d36713de0ec176f6424c209e261e5c849ce318c772f6"
+ },
+ {
+ "name": "flag_es",
+ "unicode": "1F1EA-1F1F8",
+ "digest": "27ab5cc6c2e9f26ccdfa632887533eebcd9b514f80cec9e721cf8e5e2544339c"
+ },
+ {
+ "name": "flag_et",
+ "unicode": "1F1EA-1F1F9",
+ "digest": "6cdb3718c9b3ec713258dd36781db58b7da53f3017445056c1a76233e3b4a7de"
+ },
+ {
+ "name": "flag_eu",
+ "unicode": "1F1EA-1F1FA",
+ "digest": "363f60e8a747166d5cec8d70bfdf266411eec2ff07933b6187975075caadfd74"
+ },
+ {
+ "name": "flag_fi",
+ "unicode": "1F1EB-1F1EE",
+ "digest": "1a1959cb551a0e8bdaee8c04657fb7387a4d83173f7759f89468da12e1818a9e"
+ },
+ {
+ "name": "flag_fj",
+ "unicode": "1F1EB-1F1EF",
+ "digest": "f26dc36ea9c1f32d9bb54874ea384e7118b6e2585be69245fdd73acd8304ae78"
+ },
+ {
+ "name": "flag_fk",
+ "unicode": "1F1EB-1F1F0",
+ "digest": "0479e233499b704f91a9b13d083e66296efe2f28ed917ab1496b223bfb09adb8"
+ },
+ {
+ "name": "flag_fm",
+ "unicode": "1F1EB-1F1F2",
+ "digest": "142ea7b4b4a7004329925b495da43ab82351cbaac383c8da6e614b39ba58d05e"
+ },
+ {
+ "name": "flag_fo",
+ "unicode": "1F1EB-1F1F4",
+ "digest": "f1c800d4f4d39e2aead9a11ed500f16108d6bc48bd24bd2a1af7b966d8e76752"
+ },
+ {
+ "name": "flag_fr",
+ "unicode": "1F1EB-1F1F7",
+ "digest": "6f52f36b5199c65ab1cad13ff4e77d2d8b48a8ff79b92166976674ffdc7829ee"
+ },
+ {
+ "name": "flag_ga",
+ "unicode": "1F1EC-1F1E6",
+ "digest": "50a0d5a07466e419b74a4d532738f7958de9baa37df6191be4f3755dccc3b326"
+ },
+ {
+ "name": "flag_gb",
+ "unicode": "1F1EC-1F1E7",
+ "digest": "220f7da6d5a231b766c79f2e1b7d3fdb74ec0c0c17558cc00a8a8ccdf2afc2e0"
+ },
+ {
+ "name": "flag_gd",
+ "unicode": "1F1EC-1F1E9",
+ "digest": "3e162b0d13f4ceea7f663b1d425f13863d104e80df75a640f526e276bcd04081"
+ },
+ {
+ "name": "flag_ge",
+ "unicode": "1F1EC-1F1EA",
+ "digest": "35897f8254675d2efe9e3070c88af9ef214f08440e6ee75ebe81d28cdb57ea2b"
+ },
+ {
+ "name": "flag_gf",
+ "unicode": "1F1EC-1F1EB",
+ "digest": "3a34df321635f71a0f2cc4e1eda58d85c29230c77456362345196351bf56533d"
+ },
+ {
+ "name": "flag_gg",
+ "unicode": "1F1EC-1F1EC",
+ "digest": "c972f8d190b4e9ca8890df41503d202ffd73981833d3f3750f563302167bcd66"
+ },
+ {
+ "name": "flag_gh",
+ "unicode": "1F1EC-1F1ED",
+ "digest": "9c3d3569bd411389fa0af7c6938d4325cedeb9c0e8f059dc1d5a74c6b8d6d01b"
+ },
+ {
+ "name": "flag_gi",
+ "unicode": "1F1EC-1F1EE",
+ "digest": "ede638bc6fedc30a01821025d87ec19297500da9c04a7a155984fca186118649"
+ },
+ {
+ "name": "flag_gl",
+ "unicode": "1F1EC-1F1F1",
+ "digest": "a2ce3371eff1da8331671925f707232aa593ac7400d59555c9ca689729ce24ec"
+ },
+ {
+ "name": "flag_gm",
+ "unicode": "1F1EC-1F1F2",
+ "digest": "932bf6eb75ddd4278268dd2f09d8fffcfef89f8fd6b6e86a08a414cd3ceec94d"
+ },
+ {
+ "name": "flag_gn",
+ "unicode": "1F1EC-1F1F3",
+ "digest": "ebf543713895adaa09d64897f24bd461191191b8fcbbcede52bdaf4bd2dc67a8"
+ },
+ {
+ "name": "flag_gp",
+ "unicode": "1F1EC-1F1F5",
+ "digest": "2e6c48d80c571b34f31fa9b3622dcc51e1707c0118e991e9c177742ff02a8a96"
+ },
+ {
+ "name": "flag_gq",
+ "unicode": "1F1EC-1F1F6",
+ "digest": "b0f5810180d12fc48faf75e73f882dc59072d7bf957f8455bf7e1e336539dc41"
+ },
+ {
+ "name": "flag_gr",
+ "unicode": "1F1EC-1F1F7",
+ "digest": "8d60d6f8910f5179d851dbea0798b56a492c6be85f3d55e1a1126cd1d6663a3b"
+ },
+ {
+ "name": "flag_gs",
+ "unicode": "1F1EC-1F1F8",
+ "digest": "7b07915af0e2364ebc386a162d44846f3a7986fdd24e20ad2bc56d64a103fe9c"
+ },
+ {
+ "name": "flag_gt",
+ "unicode": "1F1EC-1F1F9",
+ "digest": "0c78108ede45bf34917b409a0867f5ec8253c74b694beda083f3e8d04d7a10d8"
+ },
+ {
+ "name": "flag_gu",
+ "unicode": "1F1EC-1F1FA",
+ "digest": "909f1bc98fa1507adb787eb3875503b21ea937d6ae8bb152153916c2da5e13bb"
+ },
+ {
+ "name": "flag_gw",
+ "unicode": "1F1EC-1F1FC",
+ "digest": "f5f34410c7b22d5ed9994b47d0e7a9d9a6a1f05c4d3142f7fef3e4409725f5e6"
+ },
+ {
+ "name": "flag_gy",
+ "unicode": "1F1EC-1F1FE",
+ "digest": "4939cf52ab34a924a31032b42668960a2c7d8d4f998b16b065c247110df334be"
+ },
+ {
+ "name": "flag_hk",
+ "unicode": "1F1ED-1F1F0",
+ "digest": "bde0916df6d62f6b1cf8f85a8a39526c97fc6ef6fedb0b0cae2adb127a08eafe"
+ },
+ {
+ "name": "flag_hm",
+ "unicode": "1F1ED-1F1F2",
+ "digest": "603e6c9bff9a0dc941970a313fe98fbf53ff5a57028f1a2766420be4211711cc"
+ },
+ {
+ "name": "flag_hn",
+ "unicode": "1F1ED-1F1F3",
+ "digest": "2953ad0909bc32c02615f6ad5a4e5f331ba794a41632b1f0fc366e1c640cc2b9"
+ },
+ {
+ "name": "flag_hr",
+ "unicode": "1F1ED-1F1F7",
+ "digest": "41c9ffc4f0faaa2d77e5cffb781329e7d2489ce879bd8eb9c503621e834abc50"
+ },
+ {
+ "name": "flag_ht",
+ "unicode": "1F1ED-1F1F9",
+ "digest": "6a56c3d71b4f858e1774aa2134a9f5584087fec968e9ee8bb1046d2ec93bf059"
+ },
+ {
+ "name": "flag_hu",
+ "unicode": "1F1ED-1F1FA",
+ "digest": "72f5809818d4cab8c0cee73df7f67b820fb8471eea4199911a5917ac099795e8"
+ },
+ {
+ "name": "flag_ic",
+ "unicode": "1F1EE-1F1E8",
+ "digest": "7e2a7667fcd05f927af47e64c5790c104a9956dd9f1a45f03cb0fdcc85d866d3"
+ },
+ {
+ "name": "flag_id",
+ "unicode": "1F1EE-1F1E9",
+ "digest": "4721f616fae2e443e52f1e9cc96e4835bddca16a2d75d7d5afea57cdee866b7f"
+ },
+ {
+ "name": "flag_ie",
+ "unicode": "1F1EE-1F1EA",
+ "digest": "84b19833e6c9fb43187f8a28d85045a3df58816f20a07edab90474323174b1f3"
+ },
+ {
+ "name": "flag_il",
+ "unicode": "1F1EE-1F1F1",
+ "digest": "c99d4bd8c2541cf3a7392c4faf4477d96bc47065dd1423b9e06450483e69b34f"
+ },
+ {
+ "name": "flag_im",
+ "unicode": "1F1EE-1F1F2",
+ "digest": "5eeb12c0315b527ce61649a38b64d76af726a73b2d381d1a1ddd1366bafb1bfc"
+ },
+ {
+ "name": "flag_in",
+ "unicode": "1F1EE-1F1F3",
+ "digest": "ecc3cfcff3368fe0875a51a8be9f4dfd449a187e5beb41a2b34241736247f73b"
+ },
+ {
+ "name": "flag_io",
+ "unicode": "1F1EE-1F1F4",
+ "digest": "26243d60e04ba3bc9eb8f008bfc77b2a64bcf1a3d0073eb0449a8c8121618c9c"
+ },
+ {
+ "name": "flag_iq",
+ "unicode": "1F1EE-1F1F6",
+ "digest": "a1fb5e59575081920b3be5290f654d57a9be099deb56d4ed69eba81a2b531cb3"
+ },
+ {
+ "name": "flag_ir",
+ "unicode": "1F1EE-1F1F7",
+ "digest": "ab89488b934af1d4bdae7ed16dfc74fffe658bb8e95d5161b48cdd06de44ae85"
+ },
+ {
+ "name": "flag_is",
+ "unicode": "1F1EE-1F1F8",
+ "digest": "55db1fc9e6c56d4c9bcb9a46e5e4300cf2a0c32fa91dc24b487a1d56c8097268"
+ },
+ {
+ "name": "flag_it",
+ "unicode": "1F1EE-1F1F9",
+ "digest": "36fc993fb00ab607578a4d0e573e988e17b9459a68a000a48de905a8238589d0"
+ },
+ {
+ "name": "flag_je",
+ "unicode": "1F1EF-1F1EA",
+ "digest": "c608dbfd1259330e2f8c40dc5d12ffd0489396f4fc5f3ca57bcb2f0d9d05c20c"
+ },
+ {
+ "name": "flag_jm",
+ "unicode": "1F1EF-1F1F2",
+ "digest": "a8224b68b2d324f848d75e4376875ef76a8174e6ba32790d9ca622fe1eabfd5f"
+ },
+ {
+ "name": "flag_jo",
+ "unicode": "1F1EF-1F1F4",
+ "digest": "2403563dc2ab4ed0e7e3a0761cc09f96801550bba6b177b54d651d8804ad987d"
+ },
+ {
+ "name": "flag_jp",
+ "unicode": "1F1EF-1F1F5",
+ "digest": "aea8eebd0a0139818cb7629d9c9a8e55160b458eb8ffeee2f36c5cff4b507fd3"
+ },
+ {
+ "name": "flag_ke",
+ "unicode": "1F1F0-1F1EA",
+ "digest": "9c8365f74858743bcdce4a9cf6a6f4110faf2dc6433e5dc7d98c24bb3b32a36d"
+ },
+ {
+ "name": "flag_kg",
+ "unicode": "1F1F0-1F1EC",
+ "digest": "0c72bdb1d64b1e3be3d9516a50655a6162d8501851d2cf2fadb8c6ef7740df4e"
+ },
+ {
+ "name": "flag_kh",
+ "unicode": "1F1F0-1F1ED",
+ "digest": "49e41e488732d789e395091e144cd6215c6818ba2073e5e22ea21203a737d03c"
+ },
+ {
+ "name": "flag_ki",
+ "unicode": "1F1F0-1F1EE",
+ "digest": "9d7f168adbcf5f4cfe28470addfdb0a8b231438d593edb70f633981bfa4c7638"
+ },
+ {
+ "name": "flag_km",
+ "unicode": "1F1F0-1F1F2",
+ "digest": "9318c28957fa7a19eba5ec452c1cbce01a5a83d41d29d081614d3abb0585d478"
+ },
+ {
+ "name": "flag_kn",
+ "unicode": "1F1F0-1F1F3",
+ "digest": "eac7e7d0f023dee5c0c8559bc2c9a96273adda54ce47598025120b30d8d6ebc1"
+ },
+ {
+ "name": "flag_kp",
+ "unicode": "1F1F0-1F1F5",
+ "digest": "d4d53db6f8363174de6db864c056267ba8a7d7e87b5527f2f42bb9b8ac3f362b"
+ },
+ {
+ "name": "flag_kr",
+ "unicode": "1F1F0-1F1F7",
+ "digest": "5c7e61ab4a2aae70cbe51f0ca4718516002bc943b35d870bd853a0c98c4e0ed5"
+ },
+ {
+ "name": "flag_kw",
+ "unicode": "1F1F0-1F1FC",
+ "digest": "5d229cd99d25f4285bd30d98cfcc3cd8346648897476e2905a1811ceeef48d37"
+ },
+ {
+ "name": "flag_ky",
+ "unicode": "1F1F0-1F1FE",
+ "digest": "9ce3d8dfc273d3a400960876c434b702f93df92c6c00682dbed2ec8e3966d8a8"
+ },
+ {
+ "name": "flag_kz",
+ "unicode": "1F1F0-1F1FF",
+ "digest": "a6f0be0a767fa4824495d568d9fc2bd8d4c1a26f363873d3b65362e9383e2a50"
+ },
+ {
+ "name": "flag_la",
+ "unicode": "1F1F1-1F1E6",
+ "digest": "ab2ae96da87f7b53ab212f8dcd897a591cff9ea6666270097a8e739ee0b8f8cb"
+ },
+ {
+ "name": "flag_lb",
+ "unicode": "1F1F1-1F1E7",
+ "digest": "0c3fcab22e9fae1c78658290aff97de785d0b6adb5e3702d00073ce774b7ed54"
+ },
+ {
+ "name": "flag_lc",
+ "unicode": "1F1F1-1F1E8",
+ "digest": "e154b0b3a1635a36e0d9ad518c0ea12259320e5f1ebbda982248486492065d28"
+ },
+ {
+ "name": "flag_li",
+ "unicode": "1F1F1-1F1EE",
+ "digest": "bbc393a89e73cc8c29a0a9297428d07aa1d4717ea9b7d4dd9d69f21ac7d0605d"
+ },
+ {
+ "name": "flag_lk",
+ "unicode": "1F1F1-1F1F0",
+ "digest": "376bd501d113a844971ca1006ab31aa086cd55d74842ea5f3dedaba997b58693"
+ },
+ {
+ "name": "flag_lr",
+ "unicode": "1F1F1-1F1F7",
+ "digest": "9a6ebe1c9d9a53079ee77292a5ad0965f96409b0417f92876a1c3bd463d6a9bc"
+ },
+ {
+ "name": "flag_ls",
+ "unicode": "1F1F1-1F1F8",
+ "digest": "e2f4b05414f6e0c3d629a92b0534d4145475f0214a83a62c902fe0884c833c89"
+ },
+ {
+ "name": "flag_lt",
+ "unicode": "1F1F1-1F1F9",
+ "digest": "d5e2f8b2ffa820a33ea6d612fccd61e32467d25154342f5be134d3520e48387f"
+ },
+ {
+ "name": "flag_lu",
+ "unicode": "1F1F1-1F1FA",
+ "digest": "f43277103292195b51981d08e2dde68eab660a65c7875f510e09a8b2370f1b5c"
+ },
+ {
+ "name": "flag_lv",
+ "unicode": "1F1F1-1F1FB",
+ "digest": "e1288ac5c80d6e9d577d652e34be247ca39bf9d3d7cfc8a6cae13c1f9ac9dc47"
+ },
+ {
+ "name": "flag_ly",
+ "unicode": "1F1F1-1F1FE",
+ "digest": "5122294b769a174e3b6e3d238bb846b3e760929f5bb3c1a708d8a429f3f32f68"
+ },
+ {
+ "name": "flag_ma",
+ "unicode": "1F1F2-1F1E6",
+ "digest": "615a6447ff284de7689b4fd7b04fdda308f65dbbec958cfb96d2977514981d16"
+ },
+ {
+ "name": "flag_mc",
+ "unicode": "1F1F2-1F1E8",
+ "digest": "08b48b28938acbfc0fbc15c25ee14dbad7164c5165d03df2eee370755ee7b4cf"
+ },
+ {
+ "name": "flag_md",
+ "unicode": "1F1F2-1F1E9",
+ "digest": "93d61de68f821e1e08b30e63d91e8b4a657766475128538894cf9da9a3b4e3c0"
+ },
+ {
+ "name": "flag_me",
+ "unicode": "1F1F2-1F1EA",
+ "digest": "ee55c0eb78241aec2baf1822a47fa46d63209ceae3db7617ae886b823ae229ff"
+ },
+ {
+ "name": "flag_mf",
+ "unicode": "1F1F2-1F1EB",
+ "digest": "62627702e3e3768808c12f153a527ffcc492ad74d8cdc1858cfde971efd0c8ee"
+ },
+ {
+ "name": "flag_mg",
+ "unicode": "1F1F2-1F1EC",
+ "digest": "86ec8140e2c4854f52cff74757baf0cbb75a4aacca8be6af8c8f9c939a7b866c"
+ },
+ {
+ "name": "flag_mh",
+ "unicode": "1F1F2-1F1ED",
+ "digest": "8311ea3422c9d5e94b55e19b03bedd6fe6e2a191b7657e15ac75a48932958a5b"
+ },
+ {
+ "name": "flag_mk",
+ "unicode": "1F1F2-1F1F0",
+ "digest": "5c6f504f88c5a875c06ac8b26fa6e81a9d79c42a1c7d1fad9a5d4c8ad06ca502"
+ },
+ {
+ "name": "flag_ml",
+ "unicode": "1F1F2-1F1F1",
+ "digest": "d08a4973db40cf28e58ca3c80e8bd4e50d68ba1080b31917aeefdb0e210b5c50"
+ },
+ {
+ "name": "flag_mm",
+ "unicode": "1F1F2-1F1F2",
+ "digest": "5e95089514ca09bb93afb481b317477c9d053adcf450e0b711d78ed1078c7470"
+ },
+ {
+ "name": "flag_mn",
+ "unicode": "1F1F2-1F1F3",
+ "digest": "7a0ca72715dd2a36eeeed2f8c888497cb752f0000af8f07d6930743caf6e4273"
+ },
+ {
+ "name": "flag_mo",
+ "unicode": "1F1F2-1F1F4",
+ "digest": "d2c7c2191bc1bc83d85f2270968cb4de5cf26a11f70e166a8b32c108287ef729"
+ },
+ {
+ "name": "flag_mp",
+ "unicode": "1F1F2-1F1F5",
+ "digest": "89ad06121fd7981338fe188464491bea371f85125bfb4fc01fb5cad606613b1e"
+ },
+ {
+ "name": "flag_mq",
+ "unicode": "1F1F2-1F1F6",
+ "digest": "98176f3af823b26a3657a17c5073ee22367898b40bd3973de76329aa87ca5a2e"
+ },
+ {
+ "name": "flag_mr",
+ "unicode": "1F1F2-1F1F7",
+ "digest": "cc3e705ad84f83fe2d544385c39564743024dab26595d62469b35fdb791f6015"
+ },
+ {
+ "name": "flag_ms",
+ "unicode": "1F1F2-1F1F8",
+ "digest": "465e3d5700b557f2589bd6e34a0c6b12c634a6ed4dcfbee3c1c841c5de3413f0"
+ },
+ {
+ "name": "flag_mt",
+ "unicode": "1F1F2-1F1F9",
+ "digest": "e610ba22d8d8ad750ed10dff8e1b4d89bc34f066c3424bfa77dbdc1a5d79743a"
+ },
+ {
+ "name": "flag_mu",
+ "unicode": "1F1F2-1F1FA",
+ "digest": "3daf015d3b95218677dafbb282b7804686aa68875a6bd1d70c165b7b149e19cb"
+ },
+ {
+ "name": "flag_mv",
+ "unicode": "1F1F2-1F1FB",
+ "digest": "d30e4bfd04f08177de92f3c175600aaafa89b9668bbe2b83f35f07a74382065c"
+ },
+ {
+ "name": "flag_mw",
+ "unicode": "1F1F2-1F1FC",
+ "digest": "f364b1c8bfda3f86b5e26422eedc571ba11e312dcc634197631a6840cb22aede"
+ },
+ {
+ "name": "flag_mx",
+ "unicode": "1F1F2-1F1FD",
+ "digest": "eafb02ec0be9cefab7cef7c426c7d860d98e4947f4da04054154dc86d8f487c4"
+ },
+ {
+ "name": "flag_my",
+ "unicode": "1F1F2-1F1FE",
+ "digest": "9a690b357bc6b970781bd122c1e546ade3ccb73d930c2af1008b82027e36c7cf"
+ },
+ {
+ "name": "flag_mz",
+ "unicode": "1F1F2-1F1FF",
+ "digest": "36d0548ebfef9e0443ec1d0597ebfa6e95c25b997381f30c8c74008820743bb9"
+ },
+ {
+ "name": "flag_na",
+ "unicode": "1F1F3-1F1E6",
+ "digest": "4989dc9452b0bdfa101cfd3b7c83ef1195a7e45128b9ed00193fe712a6d02fca"
+ },
+ {
+ "name": "flag_nc",
+ "unicode": "1F1F3-1F1E8",
+ "digest": "7fc9d865eebf729d5496c4cd7576476ec599f65b379d4a6df66b4e399553c2eb"
+ },
+ {
+ "name": "flag_ne",
+ "unicode": "1F1F3-1F1EA",
+ "digest": "d3f10fb44ec44a04112bc66d05f0a44c6ec46dae73cfd3fe26cdc8b32ec06713"
+ },
+ {
+ "name": "flag_nf",
+ "unicode": "1F1F3-1F1EB",
+ "digest": "d390e0d52215a025380af221ba9e955e5886edbb4c9f4b124f2fb60a8e019e42"
+ },
+ {
+ "name": "flag_ng",
+ "unicode": "1F1F3-1F1EC",
+ "digest": "e69d1bb8f1db4a0c295c90dda23d8f97c2dea59f9a2da2ecb0e9a1dc4dbea101"
+ },
+ {
+ "name": "flag_ni",
+ "unicode": "1F1F3-1F1EE",
+ "digest": "dbaccc942637469b0ee75bd5f956958c3c5a89d8f69b69c96f02ab6594124894"
+ },
+ {
+ "name": "flag_nl",
+ "unicode": "1F1F3-1F1F1",
+ "digest": "bda2eb0315763c3c19d37c664dab1ee4280f20888a0ca57677fd33cfa4240910"
+ },
+ {
+ "name": "flag_no",
+ "unicode": "1F1F3-1F1F4",
+ "digest": "42b49dec756a220781ea271ca8fbcaba524dc3b38d5d8f999bfaa40ef9ebd302"
+ },
+ {
+ "name": "flag_np",
+ "unicode": "1F1F3-1F1F5",
+ "digest": "b5259257db079235310d5d9537d2b5b61ae0326bc8920ba13084b009844e2957"
+ },
+ {
+ "name": "flag_nr",
+ "unicode": "1F1F3-1F1F7",
+ "digest": "1bd7d1fe2c3a5e98cfd4dff6e8d6dd6d3c74f0051ad615587d77d2291a9784cc"
+ },
+ {
+ "name": "flag_nu",
+ "unicode": "1F1F3-1F1FA",
+ "digest": "e2a7a398e07d2232147cc0917d72d18b519246d3d314e9f6f03dcf98d312d4ce"
+ },
+ {
+ "name": "flag_nz",
+ "unicode": "1F1F3-1F1FF",
+ "digest": "ce8b1cb87dae3a3ec865575b57a0b4987a7f4bd3f170e7b210dd764fc2588cd4"
+ },
+ {
+ "name": "flag_om",
+ "unicode": "1F1F4-1F1F2",
+ "digest": "29da72505a276a8a372a00c197388ebc5098c221cab26b3ff755bd62b10f740f"
+ },
+ {
+ "name": "flag_pa",
+ "unicode": "1F1F5-1F1E6",
+ "digest": "180b673c9aceea43a8b55823a82d80600257e4982d0757d129860e3d8a14f458"
+ },
+ {
+ "name": "flag_pe",
+ "unicode": "1F1F5-1F1EA",
+ "digest": "b61823ea2cd91e371e40832df5764558b81d44fac41030827a3f6d2564643c00"
+ },
+ {
+ "name": "flag_pf",
+ "unicode": "1F1F5-1F1EB",
+ "digest": "e560421911f4af90c73a0dbdf8f42e69316003799304c9394fb127e3b83326fa"
+ },
+ {
+ "name": "flag_pg",
+ "unicode": "1F1F5-1F1EC",
+ "digest": "880e87db2ce0eac38db037683a5db46fd6ce30623cf56ae4a93a747103570044"
+ },
+ {
+ "name": "flag_ph",
+ "unicode": "1F1F5-1F1ED",
+ "digest": "49aae2f56bfd1385741dc76857aa1f1459778b2d39a1c955e469c5367585bfd5"
+ },
+ {
+ "name": "flag_pk",
+ "unicode": "1F1F5-1F1F0",
+ "digest": "64379dbfc932df3a07935b5cfa11ca151f761d3728939e982604e12c663cd646"
+ },
+ {
+ "name": "flag_pl",
+ "unicode": "1F1F5-1F1F1",
+ "digest": "3b688b074c2735d3dea0b7ab74b80eba243ce50cb05d68e585c9d701c1f14617"
+ },
+ {
+ "name": "flag_pm",
+ "unicode": "1F1F5-1F1F2",
+ "digest": "a13a69ee3131501dd8138173cfb669a35ee8039d84aa665e69dd7f0d0aa3e717"
+ },
+ {
+ "name": "flag_pn",
+ "unicode": "1F1F5-1F1F3",
+ "digest": "d7ae3985cf66024e4a3001e79a8efbb3e75571f2b0abbd0fb87fc1efc795a2b3"
+ },
+ {
+ "name": "flag_pr",
+ "unicode": "1F1F5-1F1F7",
+ "digest": "4910dc984bc908158506b770f28af56150cbb4509a4291947dfa2479b9e4b308"
+ },
+ {
+ "name": "flag_ps",
+ "unicode": "1F1F5-1F1F8",
+ "digest": "b2bca7619fced25de94d7bd398537857460348a552e7d73d189aef3f428e6a13"
+ },
+ {
+ "name": "flag_pt",
+ "unicode": "1F1F5-1F1F9",
+ "digest": "177282613b4b8b4d9551f1da6a1c3f66f1b96cf67c71c7d164213b26b3237395"
+ },
+ {
+ "name": "flag_pw",
+ "unicode": "1F1F5-1F1FC",
+ "digest": "2ff42a14bdc7df76b5f989dca381f94765032b26ae47d47b97844abde458cefe"
+ },
+ {
+ "name": "flag_py",
+ "unicode": "1F1F5-1F1FE",
+ "digest": "80169b69a46c4c67d0090dc2c6bf05d1a14f133ac7ae56f811547e8e8f70d81b"
+ },
+ {
+ "name": "flag_qa",
+ "unicode": "1F1F6-1F1E6",
+ "digest": "589b44b975aa97426afb8db7f8b355491fca246b693903485824bf0f5a6953a2"
+ },
+ {
+ "name": "flag_re",
+ "unicode": "1F1F7-1F1EA",
+ "digest": "77d242261742831a142c9ec74cd17d76b1e6d1af751ff3c6a356646744bc798a"
+ },
+ {
+ "name": "flag_ro",
+ "unicode": "1F1F7-1F1F4",
+ "digest": "d7d17026ea81f27456983722540f9a23343a3a1b22e7697c4fba118ce8b4719e"
+ },
+ {
+ "name": "flag_rs",
+ "unicode": "1F1F7-1F1F8",
+ "digest": "e466a18cc0368e623d3fe33a036c1e88db91ae24f7510e17caacc85c41f1bac8"
+ },
+ {
+ "name": "flag_ru",
+ "unicode": "1F1F7-1F1FA",
+ "digest": "86bf53a62dfc4c434d910f43df70f430fc67c0070fe3fc466c4fbfd6a5d8e646"
+ },
+ {
+ "name": "flag_rw",
+ "unicode": "1F1F7-1F1FC",
+ "digest": "38ec5a01896c9747a8dbf865d5e8584770e587253b7af3d3b9c36cd993f67518"
+ },
+ {
+ "name": "flag_sa",
+ "unicode": "1F1F8-1F1E6",
+ "digest": "a44d0b145f2a0b68eace24ecfd27519e9525ec764836728bc9c1fe96ccb811a0"
+ },
+ {
+ "name": "flag_sb",
+ "unicode": "1F1F8-1F1E7",
+ "digest": "8ffa24c5cb92be4dbe43f6cd85b61b9608a3101bd78ebccff4fe99c209b3e241"
+ },
+ {
+ "name": "flag_sc",
+ "unicode": "1F1F8-1F1E8",
+ "digest": "227d090ac2cbf317e594567b6114b5063a13cfe33abf990d37b200debcfadabb"
+ },
+ {
+ "name": "flag_sd",
+ "unicode": "1F1F8-1F1E9",
+ "digest": "350f3332e8ea1138e54facc870dd0fea5f2ab7d3fd4baa02ed8627ae79642f6c"
+ },
+ {
+ "name": "flag_se",
+ "unicode": "1F1F8-1F1EA",
+ "digest": "c1b09f36c263727de83b54376f05e083a17a61941af9a1640b826629256a280d"
+ },
+ {
+ "name": "flag_sg",
+ "unicode": "1F1F8-1F1EC",
+ "digest": "e6fc26920dfc07e4fd3c8d897de9c607e0bf48a3b64a13630c858d707a8e7660"
+ },
+ {
+ "name": "flag_sh",
+ "unicode": "1F1F8-1F1ED",
+ "digest": "f2c22ab0eb49e3104c35f1c0268b1e63c3a67f41b0cfa9861b189525988e53b6"
+ },
+ {
+ "name": "flag_si",
+ "unicode": "1F1F8-1F1EE",
+ "digest": "1ef0b10e498f71591322f9d8ec122d39838f479370cf7ee922560986ef6c4f2e"
+ },
+ {
+ "name": "flag_sj",
+ "unicode": "1F1F8-1F1EF",
+ "digest": "ce913b007f84a9cba2add8d754aa791901624c60e4200de426dfa25271cb0f78"
+ },
+ {
+ "name": "flag_sk",
+ "unicode": "1F1F8-1F1F0",
+ "digest": "d8f8fc4024c82f906effe98facbef9d543fb3708b1134dc502c74dc4a442b30a"
+ },
+ {
+ "name": "flag_sl",
+ "unicode": "1F1F8-1F1F1",
+ "digest": "dd7fd0452498d8d1c894cf0d5a662ddff9c5bcc02148bdc3dc7e6f25d0bb586e"
+ },
+ {
+ "name": "flag_sm",
+ "unicode": "1F1F8-1F1F2",
+ "digest": "2b499606aee2b5cbf4037338753c80a4c8f75f4abcef2c8657bd9337e602bbd3"
+ },
+ {
+ "name": "flag_sn",
+ "unicode": "1F1F8-1F1F3",
+ "digest": "03b46a9d8b129da13f60c23b820b04fba52050ca58a41b859ad57d5c3cc2515d"
+ },
+ {
+ "name": "flag_so",
+ "unicode": "1F1F8-1F1F4",
+ "digest": "ea416b6a05ddc5b16291ebe5101735360b08c834d55ac82c663ac1dd3e459048"
+ },
+ {
+ "name": "flag_sr",
+ "unicode": "1F1F8-1F1F7",
+ "digest": "012179fbcbcb7343e7b09d33e283fb63c7964a6eca35ccb9407d468e495a9874"
+ },
+ {
+ "name": "flag_ss",
+ "unicode": "1F1F8-1F1F8",
+ "digest": "6723150482c640643c9dd7e33ea749f4a8b46aceacbd4f5e11aa33b3ee13aab7"
+ },
+ {
+ "name": "flag_st",
+ "unicode": "1F1F8-1F1F9",
+ "digest": "0947fcec2e3cb1b0e9943c3d00891e8ee226e8d0532e9b1fe807ddf2e8fbc49d"
+ },
+ {
+ "name": "flag_sv",
+ "unicode": "1F1F8-1F1FB",
+ "digest": "ce7e583db833c4b10e2f7a2d09b97bb522c02e96ea0b3f3a48a955f7d8f970d8"
+ },
+ {
+ "name": "flag_sx",
+ "unicode": "1F1F8-1F1FD",
+ "digest": "c01fb238c7ba439f24a5ef821b6457f2a0fd0b99a1b2d02395bed87f0a4a88e5"
+ },
+ {
+ "name": "flag_sy",
+ "unicode": "1F1F8-1F1FE",
+ "digest": "a77d87ef98c96140c59998d10d94837e2a056dd3ac5c7522e89e5c62eac69e69"
+ },
+ {
+ "name": "flag_sz",
+ "unicode": "1F1F8-1F1FF",
+ "digest": "2904ad01040a9107ad556ec4c2561781d96746005cca250babb1127b8ba21050"
+ },
+ {
+ "name": "flag_ta",
+ "unicode": "1F1F9-1F1E6",
+ "digest": "eda84db90e1a8854e8ff3c15b3b38ee65f7d6532b76970a6fbac304c30d8c959"
+ },
+ {
+ "name": "flag_tc",
+ "unicode": "1F1F9-1F1E8",
+ "digest": "4628fdf6dc598a2846beefe97f7d4c6812f4961394cec132924b44bbe79b3322"
+ },
+ {
+ "name": "flag_td",
+ "unicode": "1F1F9-1F1E9",
+ "digest": "125ff31e4285cb2a5493a52a2703ebe8e7138b918ec4dae3d0f8693632372df6"
+ },
+ {
+ "name": "flag_tf",
+ "unicode": "1F1F9-1F1EB",
+ "digest": "489d591e11764ac341f2234020f7879db782b8f673fc9aae425fd713e4082334"
+ },
+ {
+ "name": "flag_tg",
+ "unicode": "1F1F9-1F1EC",
+ "digest": "4ceedfcfcc22cd14d9add9d86d6748447995f19f7095fa4be883e21eb1aa86bc"
+ },
+ {
+ "name": "flag_th",
+ "unicode": "1F1F9-1F1ED",
+ "digest": "2798cc660af1c5dc4891c30aded3a53d7cfa0af128cc495df8141907b165902d"
+ },
+ {
+ "name": "flag_tj",
+ "unicode": "1F1F9-1F1EF",
+ "digest": "0483506fc5b5f2d4fc18ea3cd2f8a5da985d68fe4bf90bd3fd05e67e38f32398"
+ },
+ {
+ "name": "flag_tk",
+ "unicode": "1F1F9-1F1F0",
+ "digest": "d5d4a8c6ce3207731b7c154a9d8d8fa2af055a48f03b3cbbcfd3317d3b8a75f2"
+ },
+ {
+ "name": "flag_tl",
+ "unicode": "1F1F9-1F1F1",
+ "digest": "7a2ba8f91a6b627c60c88244223a9b9d0c12707f50b174f9c2eca07dd3440df7"
+ },
+ {
+ "name": "flag_tm",
+ "unicode": "1F1F9-1F1F2",
+ "digest": "adcf5f23adcf983ce626b44559482f8728251eab34b3ff5d8b125112f3a1010f"
+ },
+ {
+ "name": "flag_tn",
+ "unicode": "1F1F9-1F1F3",
+ "digest": "5ee690ee1f3c3c0cba9b36efdef902894ec59cefbc60c4baa341efd3d7bb9ba2"
+ },
+ {
+ "name": "flag_to",
+ "unicode": "1F1F9-1F1F4",
+ "digest": "cde8672ca25b0e3a423865283fab9bc3ab10f472e04979b3b2f8032b71e96300"
+ },
+ {
+ "name": "flag_tr",
+ "unicode": "1F1F9-1F1F7",
+ "digest": "3d83c03ed084cfc81fa633310382acd7213e1eaa19d0ed97d142e7824032b55d"
+ },
+ {
+ "name": "flag_tt",
+ "unicode": "1F1F9-1F1F9",
+ "digest": "d66d272ac27e2b398289d6b60128ccd3508aeb1f4a00a3920c5e6a21bfe357ed"
+ },
+ {
+ "name": "flag_tv",
+ "unicode": "1F1F9-1F1FB",
+ "digest": "8716527383854cf1569f737d0f0f9ad77b46747255f24e02f5b2fbc850c2e35c"
+ },
+ {
+ "name": "flag_tw",
+ "unicode": "1F1F9-1F1FC",
+ "digest": "fb17b97e18e4423c5f60d60ec3ec60b917be579fc4dd9b5b23236786dcb35108"
+ },
+ {
+ "name": "flag_tz",
+ "unicode": "1F1F9-1F1FF",
+ "digest": "a8a8cf57ae5227cb54620bf31d2d6e154d2067d6d049b8db64bc4e538222948b"
+ },
+ {
+ "name": "flag_ua",
+ "unicode": "1F1FA-1F1E6",
+ "digest": "03aca4b3ffd60d944a5793eb7530f8d8ae527782f642f6606194e46ee314b12c"
+ },
+ {
+ "name": "flag_ug",
+ "unicode": "1F1FA-1F1EC",
+ "digest": "70226a1585e88390b3b815b8b79a0ddb36d2961c6b465c4ff72aa444abfe982e"
+ },
+ {
+ "name": "flag_um",
+ "unicode": "1F1FA-1F1F2",
+ "digest": "aa83bf051149acf907140a860de5de1700710e4164ae5549ad1040b24d0a142b"
+ },
+ {
+ "name": "flag_us",
+ "unicode": "1F1FA-1F1F8",
+ "digest": "32ba2aa09a30514247e91d60762791b582f547a37d9151f98b700dff50f355ea"
+ },
+ {
+ "name": "flag_uy",
+ "unicode": "1F1FA-1F1FE",
+ "digest": "0e01b3f1df4bdf6d616dacc9c5825151b941bf074be750e8b24a07ea5d5bcacb"
+ },
+ {
+ "name": "flag_uz",
+ "unicode": "1F1FA-1F1FF",
+ "digest": "903029ce83812a2134f24b65db35b183443a440ea5fecaa6ef7dcaaf65b2519c"
+ },
+ {
+ "name": "flag_va",
+ "unicode": "1F1FB-1F1E6",
+ "digest": "fd3c1c5d0ac030e838f807288912c98a3e258f87901e252e46942a4dab9f8cb7"
+ },
+ {
+ "name": "flag_vc",
+ "unicode": "1F1FB-1F1E8",
+ "digest": "7cd554ea8ca817b5366701160274587ab44167ae5a89c430bbaf237ea18b7421"
+ },
+ {
+ "name": "flag_ve",
+ "unicode": "1F1FB-1F1EA",
+ "digest": "72930094fb088c1facabea07616035ec4771374358a90c3045219d087b350dd8"
+ },
+ {
+ "name": "flag_vg",
+ "unicode": "1F1FB-1F1EC",
+ "digest": "78a59afd368b7a8312bfdb2f49927ff09e6b8f46aab0136c0453e3319e81df49"
+ },
+ {
+ "name": "flag_vi",
+ "unicode": "1F1FB-1F1EE",
+ "digest": "e070879f9605a9bae66bb84f2abf5a40c8b264baee65cd4f7a6720b826739f29"
+ },
+ {
+ "name": "flag_vn",
+ "unicode": "1F1FB-1F1F3",
+ "digest": "100ddf06e0f239b170f4d6cb459450bf4945281ee818f7d3c061828b80562219"
+ },
+ {
+ "name": "flag_vu",
+ "unicode": "1F1FB-1F1FA",
+ "digest": "59fc9d16818295bba4f7f551598f85378cd07f2bd7e31a4eef2589aaa3847563"
+ },
+ {
+ "name": "flag_wf",
+ "unicode": "1F1FC-1F1EB",
+ "digest": "62627702e3e3768808c12f153a527ffcc492ad74d8cdc1858cfde971efd0c8ee"
+ },
+ {
+ "name": "flag_white",
+ "unicode": "1F3F3",
+ "digest": "96307e3a28e92d1e7147a06f154ffc291ee3cd1765cf8b7bfb06412294112559"
+ },
+ {
+ "name": "flag_ws",
+ "unicode": "1F1FC-1F1F8",
+ "digest": "0c95271d0f4b23f0d215ee0fba05cf08ecb70665d4c028e17463ecda2754b164"
+ },
+ {
+ "name": "flag_xk",
+ "unicode": "1F1FD-1F1F0",
+ "digest": "713aa7d228e96f4a06d58d1fb8c2a55296c3e56842f8177ca936f3e09f50da1e"
+ },
+ {
+ "name": "flag_ye",
+ "unicode": "1F1FE-1F1EA",
+ "digest": "3bb65bae9c913357bcae8b8b5878efc9e194ca308442ab69639c29716b49f078"
+ },
+ {
+ "name": "flag_yt",
+ "unicode": "1F1FE-1F1F9",
+ "digest": "f86c86f4c194610a3af78971fcf221ad97b9499d08f6d64476e417a2f52a611e"
+ },
+ {
+ "name": "flag_za",
+ "unicode": "1F1FF-1F1E6",
+ "digest": "4dd4fa49a01fdcfc7c1c099a7869e0e9acba83a6a3debf6c8505ada4c796b872"
+ },
+ {
+ "name": "flag_zm",
+ "unicode": "1F1FF-1F1F2",
+ "digest": "ab6790d89875447de3d1c7f4713b102761bc3e9afdd714b818689e175ca03011"
+ },
+ {
+ "name": "flag_zw",
+ "unicode": "1F1FF-1F1FC",
+ "digest": "9d39b934fe922174b2250f2cd1b174a548d2904091d3298f35b7cc59fbceb181"
+ },
+ {
+ "name": "flags",
+ "unicode": "1F38F",
+ "digest": "c3f4a66786e524a5562919afcba9486113091ed205f1342e91d2f6439845ad61"
+ },
+ {
+ "name": "flashlight",
+ "unicode": "1F526",
+ "digest": "5f641b8fd1c7f1dcd43ec3b1ef78d14ef9929d723789c5567aca8b95d3d39803"
+ },
+ {
+ "name": "fleur-de-lis",
+ "unicode": "269C",
+ "digest": "d6ddeeea355ed55103b7fc65ac1ee0dbaa79d01e0d136b265363a6b92284c073"
+ },
+ {
+ "name": "flip_phone",
+ "unicode": "1F581",
+ "digest": "be59efba4bc0759af5a726c06619090ef5071bf2541611d71691dedecee6c697"
+ },
+ {
+ "name": "floppy_black",
+ "unicode": "1F5AA",
+ "digest": "9022f51bb09c5130c6d46bb2accb159bed6f54d6fbffda6ecad62965ebc958ea"
+ },
+ {
+ "name": "floppy_disk",
+ "unicode": "1F4BE",
+ "digest": "e987961ca516032a90942ef6c398836f2da68a5981714bd172acfe7b0e369d0a"
+ },
+ {
+ "name": "floppy_white",
+ "unicode": "1F5AB",
+ "digest": "ec79c400117c4506ef8cf3eebef6c42dd37e60b3079d3e98b6ccd06e517e2af0"
+ },
+ {
+ "name": "flower_playing_cards",
+ "unicode": "1F3B4",
+ "digest": "451f361050b96ba9ed8dc5b64c8a90c1316fd9b83fb818152881a54e100eea6c"
+ },
+ {
+ "name": "flushed",
+ "unicode": "1F633",
+ "digest": "39cf51f9dec2a910c66ecd39a7bd616fea09d67e81801e57e84f03ed1e917750"
+ },
+ {
+ "name": "fog",
+ "unicode": "1F32B",
+ "digest": "da6fdb9b682ed9a3368adcd7531f1a29e22755a620e3cca163fc3f33a6a78107"
+ },
+ {
+ "name": "foggy",
+ "unicode": "1F301",
+ "digest": "b599f3178db289c6e30017f3f0a9d30b00a75417057c7a10c0c9eedac78edbf1"
+ },
+ {
+ "name": "folder",
+ "unicode": "1F5C0",
+ "digest": "8932141321911032ce8469ba85fe309b78384545c3b9946978b383670b956644"
+ },
+ {
+ "name": "folder_open",
+ "unicode": "1F5C1",
+ "digest": "74f3b484771c3d6ef61cf003de25c1a59b875afa46c057b5b1d92d9f99460685"
+ },
+ {
+ "name": "football",
+ "unicode": "1F3C8",
+ "digest": "834fe5f431d6aa8ef1186aa79e71f813393535d273483b6af4cc4bdb8380e5b4"
+ },
+ {
+ "name": "footprints",
+ "unicode": "1F463",
+ "digest": "60dc938f6769ea21b05b5afcc481d3ddacf1f565e04f33310b271d5422e7ceb9"
+ },
+ {
+ "name": "fork_and_knife",
+ "unicode": "1F374",
+ "digest": "7e07c9dc555d172fa2eaa41cefd8d46d9624be0137aff196dd003a8a82610ec3"
+ },
+ {
+ "name": "fork_knife_plate",
+ "unicode": "1F37D",
+ "digest": "b4081b9edea6cdab5112fdd17535051ba17710953013f5020c7c40f84a1e3247"
+ },
+ {
+ "name": "fountain",
+ "unicode": "26F2",
+ "digest": "0acdca5e8f6d745a8d582d96012ec8fc55b9f5447e657ebfd998a4e332d99322"
+ },
+ {
+ "name": "four",
+ "unicode": "0034-20E3",
+ "digest": "36bd4ea6e2ae689835a79f8e60466eccd62fce7e91e84ed768cffd87dac628dd"
+ },
+ {
+ "name": "four_leaf_clover",
+ "unicode": "1F340",
+ "digest": "12ee2343df25bbd9077fdc12314c1edb51c0cdb556af7e22590e8a578ef57f17"
+ },
+ {
+ "name": "frame_photo",
+ "unicode": "1F5BC",
+ "digest": "6ff21063063989c6ae7dd69f4d6a781c676f9dba380d8e6f1dbac5d53b24f349"
+ },
+ {
+ "name": "frame_tiles",
+ "unicode": "1F5BD",
+ "digest": "34a5bb044b4b3ad94b116ad106f7b6747fb8612dc0e9f8ccd4313c2920508df0"
+ },
+ {
+ "name": "frame_x",
+ "unicode": "1F5BE",
+ "digest": "2e427688fd70361c8c59787d0722ad68abe1c3f968258ee99c0c77ce4b8a8e15"
+ },
+ {
+ "name": "free",
+ "unicode": "1F193",
+ "digest": "c1d9172a656717f78d941303c5da8790c6cd9827838d8f7dc3719afb53bcab80"
+ },
+ {
+ "name": "fried_shrimp",
+ "unicode": "1F364",
+ "digest": "c0c19e95f2c38f6cf870920bf3c2d4d69c36ea6e7dc9a5c45c3e8b285269d40a"
+ },
+ {
+ "name": "fries",
+ "unicode": "1F35F",
+ "digest": "0f546534684de29d319cbcbab4162acb321c4f8f3202fe17d69e1894ab7c8195"
+ },
+ {
+ "name": "frog",
+ "unicode": "1F438",
+ "digest": "6a417757fa6ee39e7a277cbd53c690ff88af0b1d76728d56f9bc645cb628aeb7"
+ },
+ {
+ "name": "frowning",
+ "unicode": "1F626",
+ "digest": "fb39f5c2aea98054adb02a3a0ac34a2e38d83f32cd590e9d2449e06a9702f2f5"
+ },
+ {
+ "name": "frowning2",
+ "unicode": "2639",
+ "digest": "7bb6c682a6c9f98bf3a5ae986e317fd26d1af497c857500deec2f06b6a3af5da"
+ },
+ {
+ "name": "fuelpump",
+ "unicode": "26FD",
+ "digest": "9cbb2646c93b255bd3de87dc01aa1193ab96e39a3013975d250472ab8aae61d6"
+ },
+ {
+ "name": "full_moon",
+ "unicode": "1F315",
+ "digest": "0b4f08ef2089397ead034b444a60e6e9810073454581b52a46b2369e3b9cd5f9"
+ },
+ {
+ "name": "full_moon_with_face",
+ "unicode": "1F31D",
+ "digest": "a371cb9e1f28a7db739dd058234642a2e333dff4b6df9882df85a6d984e4b5e8"
+ },
+ {
+ "name": "game_die",
+ "unicode": "1F3B2",
+ "digest": "6584909a4348c350c04417421b63eace1245087f7d239051b30a0cd37fe929f9"
+ },
+ {
+ "name": "gear",
+ "unicode": "2699",
+ "digest": "b0ff5fd007daa366a9eecb7422dbeb8a973e123a04267b88fef96c7453238294"
+ },
+ {
+ "name": "gem",
+ "unicode": "1F48E",
+ "digest": "d75d854f35975e4e291c3b9fcaf8437467f6d7eb27b29e2d7c0f0038fc666fe2"
+ },
+ {
+ "name": "gemini",
+ "unicode": "264A",
+ "digest": "392abe62872736a0bf92979a8c25a814985d0ff0a08dc7ab2a5c058aeda7e685"
+ },
+ {
+ "name": "ghost",
+ "unicode": "1F47B",
+ "digest": "f084b14483476e2d07563840f8c33b46da9c17f791da07fde3acffeb77342947"
+ },
+ {
+ "name": "gift",
+ "unicode": "1F381",
+ "digest": "c9a2ae6ea05c02e78e9567dcbd971701a2f869eb46c62d85cef23d0834388d8c"
+ },
+ {
+ "name": "gift_heart",
+ "unicode": "1F49D",
+ "digest": "e0c5aacf1ce89117d86b148f10a02dc18fe0cd22a75fbf6f0f88f2fad3ca80fe"
+ },
+ {
+ "name": "girl",
+ "unicode": "1F467",
+ "digest": "0758cbc4cbc7d72d6df8f66fc3a6b2b283c6634b053e59d61c6cac44cf8bffda"
+ },
+ {
+ "name": "girl_tone1",
+ "unicode": "1F467-1F3FB",
+ "digest": "7afdece55cb64e8056e2202de8c17b66ddb616f224ac374ec9a160d06b3138cc"
+ },
+ {
+ "name": "girl_tone2",
+ "unicode": "1F467-1F3FC",
+ "digest": "c160aa65fee70ad52930d01246ac9f282ff6abf1d93c5cc5b299fc257ee81db1"
+ },
+ {
+ "name": "girl_tone3",
+ "unicode": "1F467-1F3FD",
+ "digest": "b8a5687cd637855a41b8c7dc686f0e69fda379875408cd269f1b330a805c72f4"
+ },
+ {
+ "name": "girl_tone4",
+ "unicode": "1F467-1F3FE",
+ "digest": "a9cf743936b733634f323790a1abe3a410601b6841484baebea484b392f4e98e"
+ },
+ {
+ "name": "girl_tone5",
+ "unicode": "1F467-1F3FF",
+ "digest": "c902170e67b81eee35eeefb6a5c62c6109cb423dcae88d4e036ddd50b240c072"
+ },
+ {
+ "name": "girls_symbol",
+ "unicode": "1F6CA",
+ "digest": "2c55aee81defd7a1620ffeaad8d9bcc1835f19237c72c79633aec45671ddb9ff"
+ },
+ {
+ "name": "globe_with_meridians",
+ "unicode": "1F310",
+ "digest": "945646de3d8f057760fe374494a253d9a6aa8a132309154b0a5bdbffb5b20c3f"
+ },
+ {
+ "name": "goat",
+ "unicode": "1F410",
+ "digest": "f99cbc6755d119cb5c1dce08cabd20871f98d009bb773da4a146dae60476a235"
+ },
+ {
+ "name": "golf",
+ "unicode": "26F3",
+ "digest": "74a7876d185f8ff6a6533e4db2e1eb787119b2f8d8b07c36d99ec3163fb48485"
+ },
+ {
+ "name": "golfer",
+ "unicode": "1F3CC",
+ "digest": "6458295a5e4a6e4323c32a7f1f7182fb2d3918083839efc380d995860ce360b1"
+ },
+ {
+ "name": "grapes",
+ "unicode": "1F347",
+ "digest": "7f6873d65180ab476f49d207ac2d1f7dbaf6c8b0b561d50b64325e192cf97a86"
+ },
+ {
+ "name": "green_apple",
+ "unicode": "1F34F",
+ "digest": "effc3fe60f2ab704a034c794bfccfa023b41332f8f16ca44cc8ea41698f03873"
+ },
+ {
+ "name": "green_book",
+ "unicode": "1F4D7",
+ "digest": "6652c4d2ccfa4a287a5d45007bd06cadc16d34b0a1ca4b6b13b46f976c8d8319"
+ },
+ {
+ "name": "green_heart",
+ "unicode": "1F49A",
+ "digest": "f4bcb660a1d3cf3692238359d8b9de9a725a9af81f166253e487d61b8ccf9d86"
+ },
+ {
+ "name": "grey_exclamation",
+ "unicode": "2755",
+ "digest": "ac8cdab7496d133e7bc9475f2fdb0cf59b3ccba20f2f156c8b693e72b5948078"
+ },
+ {
+ "name": "grey_question",
+ "unicode": "2754",
+ "digest": "c173e1b2a16ab62b0abd7a58deb7a6df709b072d30d001627b92d0123a3a3e4a"
+ },
+ {
+ "name": "grimacing",
+ "unicode": "1F62C",
+ "digest": "8c54b73f5d2c1c6347e2c0ab01616519e0fb34490daa9c36664d442c6851c57e"
+ },
+ {
+ "name": "grin",
+ "unicode": "1F601",
+ "digest": "916eabdabd8b7ca698e638bbbd14affff97464ec11a3b59c0cb96cd7705600d8"
+ },
+ {
+ "name": "grinning",
+ "unicode": "1F600",
+ "digest": "3d8665c03f272ca3063e96145989926355a7ac315ed1a032d30fcefa6f0c3923"
+ },
+ {
+ "name": "guardsman",
+ "unicode": "1F482",
+ "digest": "ebbd29fa138005232d64fca4a8ec015d097fa14e6ded57b35ac257b4570b3c36"
+ },
+ {
+ "name": "guardsman_tone1",
+ "unicode": "1F482-1F3FB",
+ "digest": "b6082c8fee5dbc3ce2540f3939d5e344b5366c9f07827345facaba438e7017ff"
+ },
+ {
+ "name": "guardsman_tone2",
+ "unicode": "1F482-1F3FC",
+ "digest": "2b813afe1c2bbdaf9a47493393a0e6c400a16e453ed25a9a9c0035197927b56e"
+ },
+ {
+ "name": "guardsman_tone3",
+ "unicode": "1F482-1F3FD",
+ "digest": "49b2fa1ad0bc50a5ef6d73fb140aa1876506b9ebb9d45782ccb8dbb6818f8dde"
+ },
+ {
+ "name": "guardsman_tone4",
+ "unicode": "1F482-1F3FE",
+ "digest": "a584e1e3a8ad7be4871a6bdb7996d4f649abeaa77eb5d1cae998058d8b23ca0f"
+ },
+ {
+ "name": "guardsman_tone5",
+ "unicode": "1F482-1F3FF",
+ "digest": "e853b67ee13fda99e98f47083529ca80c404df1b19352c78b9c69850eb8f2c76"
+ },
+ {
+ "name": "guitar",
+ "unicode": "1F3B8",
+ "digest": "8c041b961649cc5917f56f2fb543f9a5280724647ed2fc67bc94a05eff9da805"
+ },
+ {
+ "name": "gun",
+ "unicode": "1F52B",
+ "digest": "d7f5aa657cc0ba04d878511820632b89c305a9b4d6c4a4b90ff691dad9906607"
+ },
+ {
+ "name": "haircut",
+ "unicode": "1F487",
+ "digest": "369dbab1b138c31d3eca04c950fdab4ec9f085272268c241f100d44e7b0f229e"
+ },
+ {
+ "name": "haircut_tone1",
+ "unicode": "1F487-1F3FB",
+ "digest": "c56f32d7c1d8a92d22429133f87f31a159818939cfdc570cb48b6d243cc58cf2"
+ },
+ {
+ "name": "haircut_tone2",
+ "unicode": "1F487-1F3FC",
+ "digest": "e916e040ffb8e869e930d1256343af2ad2bbaa683f01a11564d0777019944bec"
+ },
+ {
+ "name": "haircut_tone3",
+ "unicode": "1F487-1F3FD",
+ "digest": "f07cdfbea964ac42a9a050f832107ef0f2fa8115b27689f93d1be954de07b7c1"
+ },
+ {
+ "name": "haircut_tone4",
+ "unicode": "1F487-1F3FE",
+ "digest": "32ec7f5e999f7c43676768c8320ffaa346c713d340a94b948b1f564b345a2d11"
+ },
+ {
+ "name": "haircut_tone5",
+ "unicode": "1F487-1F3FF",
+ "digest": "5aad997d09e7975700927906d41a10bae774356ccddbe5197980bde670272262"
+ },
+ {
+ "name": "hamburger",
+ "unicode": "1F354",
+ "digest": "24ebae9a69cf283ab198499cb38d0cdcd82bac74c8e8d1e769ad78eb320a4294"
+ },
+ {
+ "name": "hammer",
+ "unicode": "1F528",
+ "digest": "a43a66b0efdc4cd2c84fd0ccc2cb8e9ede1f89c5d62eefa6ae521d3aed9d81b3"
+ },
+ {
+ "name": "hammer_pick",
+ "unicode": "2692",
+ "digest": "2e4fe33406ca03fbb0df1596d63e903d8ee6bd78ecc3ec38a67dd2cecbc584e2"
+ },
+ {
+ "name": "hamster",
+ "unicode": "1F439",
+ "digest": "f47da088ff5792532a382b6e3a47d2dd7c5e6fc19abd5ff6c5ba3ce420b4192e"
+ },
+ {
+ "name": "hand_splayed",
+ "unicode": "1F590",
+ "digest": "a43e52f7cdec5e9d51497888b0988d7bbd42846ad7e492b196293fbce576d197"
+ },
+ {
+ "name": "hand_splayed_reverse",
+ "unicode": "1F591",
+ "digest": "ff0af0fe9def7388adca6836e5958492282b1afae99f1b6e1e65d11ba68b96db"
+ },
+ {
+ "name": "hand_splayed_tone1",
+ "unicode": "1F590-1F3FB",
+ "digest": "73cceec7117280d330f8a149979190f0f355dd8d0a92821be89fb70344bb8dfe"
+ },
+ {
+ "name": "hand_splayed_tone2",
+ "unicode": "1F590-1F3FC",
+ "digest": "b06fac698128f4c3a7b8ea56e8bc4de088bb5461aa0f9c84553f16b43d347145"
+ },
+ {
+ "name": "hand_splayed_tone3",
+ "unicode": "1F590-1F3FD",
+ "digest": "a94ee9a2f8cdec6d2f7dd6887d1c7b8e064fcad63030c2c7c001742d72b5603e"
+ },
+ {
+ "name": "hand_splayed_tone4",
+ "unicode": "1F590-1F3FE",
+ "digest": "501792b4126c6f32e755accee0fc8b4d1915e1d36c4ceaa40f3bd0066efe76c3"
+ },
+ {
+ "name": "hand_splayed_tone5",
+ "unicode": "1F590-1F3FF",
+ "digest": "22ed533d587cf44f286e2d6ad77be20b4b5f133c422af4ca51e9af86a75002d8"
+ },
+ {
+ "name": "hand_victory",
+ "unicode": "1F594",
+ "digest": "2d512ced4e8a438f2a346aed67310d3080f9828c748ade1be95943c32ba1c735"
+ },
+ {
+ "name": "handbag",
+ "unicode": "1F45C",
+ "digest": "f1e2822c67f659b52c76821dd9db001332215a8566fc1846c89b6019c9758038"
+ },
+ {
+ "name": "hard_disk",
+ "unicode": "1F5B4",
+ "digest": "df8549d4281f5ae70fb6792a02c078e651764b0276aa43b7407236bd38fc21b4"
+ },
+ {
+ "name": "hash",
+ "unicode": "0023-20E3",
+ "digest": "5bd5c7180485fa71accdec5378bdc196ce0602f594f91e4eadc1e7514d5d0f90"
+ },
+ {
+ "name": "hatched_chick",
+ "unicode": "1F425",
+ "digest": "7995c3eb503a8b9662694eba80a9b551216473a31928091e35cd6ebc21cee083"
+ },
+ {
+ "name": "hatching_chick",
+ "unicode": "1F423",
+ "digest": "22905b42fa65dbc9aad8940d2db13691cacc62014f54e0960978ee0002178e1b"
+ },
+ {
+ "name": "head_bandage",
+ "unicode": "1F915",
+ "digest": "d690b740ff4f58e89dfc764c6411a4e84cfedffd7694eb5efa839a642dbabd08"
+ },
+ {
+ "name": "headphones",
+ "unicode": "1F3A7",
+ "digest": "219da138032c01c97a94f02b211049418191a3beb3d159804b9033f5916fd3c8"
+ },
+ {
+ "name": "hear_no_evil",
+ "unicode": "1F649",
+ "digest": "8120060238eaca645809dd113862a144f10395afcb3837ab60c0f04009b49a2f"
+ },
+ {
+ "name": "heart",
+ "unicode": "2764",
+ "digest": "a646a25a36f431cadc7e56afd1a4d1b7cbae5292a25d7783bd31462d0d3d719b"
+ },
+ {
+ "name": "heart_decoration",
+ "unicode": "1F49F",
+ "digest": "a83989669347c98cb74065d4f0befedbc37f82c91214e773245cb6810ab359b4"
+ },
+ {
+ "name": "heart_exclamation",
+ "unicode": "2763",
+ "digest": "9751c89dcf10805f2011949ff3ddcb6bcb13de8c32ae5de9e03955e8a4235df2"
+ },
+ {
+ "name": "heart_eyes",
+ "unicode": "1F60D",
+ "digest": "335ea73efca4824e623a5a51ccdb494c8b1f5f10b4139b39b250a2a771876b0d"
+ },
+ {
+ "name": "heart_eyes_cat",
+ "unicode": "1F63B",
+ "digest": "9346b85afb80f7b498cc255426ea15a287f81d8fb3c26dab61337635f439d3ce"
+ },
+ {
+ "name": "heart_tip",
+ "unicode": "1F394",
+ "digest": "2178829e2c85accda55d2f685544587f6de5c8398a127ae1e08ff1c4ab282204"
+ },
+ {
+ "name": "heartbeat",
+ "unicode": "1F493",
+ "digest": "cd6921ce55c155873220a09416d695c4bcca1556007066d6d185e93d6561e825"
+ },
+ {
+ "name": "heartpulse",
+ "unicode": "1F497",
+ "digest": "f869357b9e678d9671ec38c569fc88efec48006c159b69297277cee795dc4dc9"
+ },
+ {
+ "name": "hearts",
+ "unicode": "2665",
+ "digest": "17dc9b2941561f58ca0f04d0754b1eff3490b63b17241580b3d4aa4638fa85e8"
+ },
+ {
+ "name": "heavy_check_mark",
+ "unicode": "2714",
+ "digest": "b5fa24f6e0f1dcbd6278e9125154522f2efd79e6dd0836ccb792a1f3aeeff2b2"
+ },
+ {
+ "name": "heavy_division_sign",
+ "unicode": "2797",
+ "digest": "59a6983d788f347c64eecb3df6f7d3b36779d92df6cc811820993ff9e18d77e1"
+ },
+ {
+ "name": "heavy_dollar_sign",
+ "unicode": "1F4B2",
+ "digest": "d2e89c54b3fdeda4d1fd4d29454b69dcf750181110894e6e71a40df99c95bfe8"
+ },
+ {
+ "name": "heavy_minus_sign",
+ "unicode": "2796",
+ "digest": "dd5ab3722fe49cfdbc5e1fbab5b342dc960de7b412d4fba59d66e06ce3dc3bcd"
+ },
+ {
+ "name": "heavy_multiplication_x",
+ "unicode": "2716",
+ "digest": "7d77742f91377785675802f40bd8dde9bd1feeb513735760a58ea9bee8a65d44"
+ },
+ {
+ "name": "heavy_plus_sign",
+ "unicode": "2795",
+ "digest": "9aa9dcdbba120a4b485c21f67589609b789c6e3edf08479ff8268fa0db973ad7"
+ },
+ {
+ "name": "helicopter",
+ "unicode": "1F681",
+ "digest": "b259ea8d2bdca36766075894da650b1d3ff4c8602259cd0d30cb8214cd585340"
+ },
+ {
+ "name": "helmet_with_cross",
+ "unicode": "26D1",
+ "digest": "affbe9dd87b87ff9235b4858c59c2a73e9ed30dd5221e5b666b8d7747378a9c4"
+ },
+ {
+ "name": "herb",
+ "unicode": "1F33F",
+ "digest": "3c452106b1966f643751bf161fa7d1762a33e6fff381b2109bb53b55c4fdd129"
+ },
+ {
+ "name": "hibiscus",
+ "unicode": "1F33A",
+ "digest": "268963a1f3cdad9050d9ae31c558e010f33812e3b09bbf9088ba876c033d8b2f"
+ },
+ {
+ "name": "high_brightness",
+ "unicode": "1F506",
+ "digest": "d607f6269d95dd16c2a7932e49ac09e44f4c19e0a34f6c0f21ecb945a2316361"
+ },
+ {
+ "name": "high_heel",
+ "unicode": "1F460",
+ "digest": "5c320d5954bf4f4dacacddd562c1598ab101731077a6656ac5d2bfd41405483e"
+ },
+ {
+ "name": "hockey",
+ "unicode": "1F3D2",
+ "digest": "008904c1b8db139215492a6d96c09f2c3eeda769f858a9bbae13f8c54d439d0e"
+ },
+ {
+ "name": "hole",
+ "unicode": "1F573",
+ "digest": "36bbafa5e89b1410ec74919aaf60b09ac3525a421cb5b475b9bb2f20357db8de"
+ },
+ {
+ "name": "homes",
+ "unicode": "1F3D8",
+ "digest": "9980d6dd6cbd23b820747ecac4cb10974dd24b0c94b4acfe21fa87793ad065c9"
+ },
+ {
+ "name": "honey_pot",
+ "unicode": "1F36F",
+ "digest": "94cb1624491076b5cb145e7a309f91a7be3d4c0bed712af6a51d641eb73edee7"
+ },
+ {
+ "name": "horse",
+ "unicode": "1F434",
+ "digest": "624ad9dc9ed7af3f6e1a2f9d4ed483702ae64ed5fbcf5e9918af6bfef24e76f9"
+ },
+ {
+ "name": "horse_racing",
+ "unicode": "1F3C7",
+ "digest": "c2702b7225e9839a789dda7c43f0cc86dced2b4d5d3787116106396633362de6"
+ },
+ {
+ "name": "horse_racing_tone1",
+ "unicode": "1F3C7-1F3FB",
+ "digest": "a7ed284f9d5cd8a4fe4a09cb91c3f99e5db99c7e31c5f525c14de97b06857d92"
+ },
+ {
+ "name": "horse_racing_tone2",
+ "unicode": "1F3C7-1F3FC",
+ "digest": "20b4d61b21ee6ba860b029f0ad0e38f5ecb6dd2c774f7b7801fba07ed33f96be"
+ },
+ {
+ "name": "horse_racing_tone3",
+ "unicode": "1F3C7-1F3FD",
+ "digest": "dd65f7bb96ee44507d26e524202d567d2d7679d571245299a2a84f68bd5def4c"
+ },
+ {
+ "name": "horse_racing_tone4",
+ "unicode": "1F3C7-1F3FE",
+ "digest": "36afaad218a4c820b19c7c9bbbc187119d47b41273d8f48ab14cc3e32dd7c21f"
+ },
+ {
+ "name": "horse_racing_tone5",
+ "unicode": "1F3C7-1F3FF",
+ "digest": "2e0efd501a4471428533ce7909972a49ff045369261c27e4abb97ee2aede2f47"
+ },
+ {
+ "name": "hospital",
+ "unicode": "1F3E5",
+ "digest": "df5c774fa36b2601e6960a7b81cdfac71c1d2d71f04dea88068d1c9043e313bb"
+ },
+ {
+ "name": "hot_pepper",
+ "unicode": "1F336",
+ "digest": "62e4dade3c793f6d83530bd1f60f3e3e26c1e10a41786c3a15f5aec0ff2b8e76"
+ },
+ {
+ "name": "hotdog",
+ "unicode": "1F32D",
+ "digest": "58b829e26b5c4642942898d9c7873cb08e048fd7deaacba8292899d5d895cb2b"
+ },
+ {
+ "name": "hotel",
+ "unicode": "1F3E8",
+ "digest": "428120a35b38a217901e10d704751eb8fdbc9f805e6eccd8aab070f4311b2085"
+ },
+ {
+ "name": "hotsprings",
+ "unicode": "2668",
+ "digest": "df4f946218445f97a6f28c6abe4c1d1dac56ff97a8cd81df59f1b3c320e0092f"
+ },
+ {
+ "name": "hourglass",
+ "unicode": "231B",
+ "digest": "07aece9413e6898717b4f0757e073d7a593f3e8044c56855127033b796207ccb"
+ },
+ {
+ "name": "hourglass_flowing_sand",
+ "unicode": "23F3",
+ "digest": "92dbc68e9d16fb9f706236367e1882f0d2b6817b83ca490820a000021f2c6483"
+ },
+ {
+ "name": "house",
+ "unicode": "1F3E0",
+ "digest": "a6221fc84a9b0e11ae71bfa1e0020982b55ff8c89a374a6d755dba710b4e058c"
+ },
+ {
+ "name": "house_abandoned",
+ "unicode": "1F3DA",
+ "digest": "e404631e3a296bdeae3de7510da8934c32327bc0fa0f7ae4e676b61932165668"
+ },
+ {
+ "name": "house_with_garden",
+ "unicode": "1F3E1",
+ "digest": "22d0d911da96b7ae3bf6692d3cf3590afbca959fc99c13e7a088f7194f43a35d"
+ },
+ {
+ "name": "hugging",
+ "unicode": "1F917",
+ "digest": "68ed6c4e0eae9071cf67770a39e07a2290b4f7763170f765b3cd3ac67ae43240"
+ },
+ {
+ "name": "hushed",
+ "unicode": "1F62F",
+ "digest": "69faa8e0b170ee8cf41977ca4a5154406360ed9699d5c62ecdaa01f50e8e4276"
+ },
+ {
+ "name": "ice_cream",
+ "unicode": "1F368",
+ "digest": "d48ec98a8789148b96c30f19595201a0f85ed899659d97d1d3596091162909ff"
+ },
+ {
+ "name": "ice_skate",
+ "unicode": "26F8",
+ "digest": "6fb044d9fbe62605f6728062c35c345ddd3ae4cc51203c925b0e69f1b3ef2dbf"
+ },
+ {
+ "name": "icecream",
+ "unicode": "1F366",
+ "digest": "abd5774157575dd304dc1a393244757853972c863861a654ca29b2d528e48b28"
+ },
+ {
+ "name": "id",
+ "unicode": "1F194",
+ "digest": "860ffb36d37d84e2c1cf0ab991b95c1cf73e458bef0e4d85bb0c1e26115cb2d1"
+ },
+ {
+ "name": "ideograph_advantage",
+ "unicode": "1F250",
+ "digest": "37892a5642cd49ef7828646f36f48b5a83dc02437624c05da428579256118030"
+ },
+ {
+ "name": "imp",
+ "unicode": "1F47F",
+ "digest": "f8c93d03bd9f1d5ef86738541e11695d6811bf6fef06759eba98321b6d038814"
+ },
+ {
+ "name": "inbox_tray",
+ "unicode": "1F4E5",
+ "digest": "066a2d75633eb50329496f6866b5b0645c2e48135a03118f1bf53244f8529043"
+ },
+ {
+ "name": "incoming_envelope",
+ "unicode": "1F4E8",
+ "digest": "ef6e5c5aa679d174181dae77113717f26e295778dde1e2c3bdf1d64de8a4af8c"
+ },
+ {
+ "name": "info",
+ "unicode": "1F6C8",
+ "digest": "59c35e77d5ee663c5d56f7d8af845ce8aeb9935e526ae4a06e02ae70e71212ca"
+ },
+ {
+ "name": "information_desk_person",
+ "unicode": "1F481",
+ "digest": "acae6d272e348aee87dd60360f16ac58cea7cb4e1ea962cc1655005c7f4aed27"
+ },
+ {
+ "name": "information_desk_person_tone1",
+ "unicode": "1F481-1F3FB",
+ "digest": "709ebb0481ca981d76ece2d4fc68db693ddf18b9c1aaa0b6ac5d3c42e71bf07f"
+ },
+ {
+ "name": "information_desk_person_tone2",
+ "unicode": "1F481-1F3FC",
+ "digest": "d5bc3563bc721d66b73850db93ac827be3715e7ca6420dc0051396ffe26bef47"
+ },
+ {
+ "name": "information_desk_person_tone3",
+ "unicode": "1F481-1F3FD",
+ "digest": "af67fd4ef2fc402bec2d446b2e8ff5e9f636b5a9bbb6639587cdb88bd780d265"
+ },
+ {
+ "name": "information_desk_person_tone4",
+ "unicode": "1F481-1F3FE",
+ "digest": "fd3174d1adfe13e8c0d6b6ae9c3a26ea35bb40f98f0728f91d1798809a74933b"
+ },
+ {
+ "name": "information_desk_person_tone5",
+ "unicode": "1F481-1F3FF",
+ "digest": "4b773c443830a02de8b4d6471077b5d1387b560b537cabba7cdc667110cbde69"
+ },
+ {
+ "name": "information_source",
+ "unicode": "2139",
+ "digest": "50cd8bf46d20b7c18d5f00a69fc79452aa32934245ba8d0929e51632d73876bd"
+ },
+ {
+ "name": "innocent",
+ "unicode": "1F607",
+ "digest": "a3510fd51c17093ebe2371cfde7611aa44aed2d120a0e5500cfaae0f1d3486a4"
+ },
+ {
+ "name": "interrobang",
+ "unicode": "2049",
+ "digest": "1f843ff672486154f9f3df549bb1b528a5eac8d15264f447649ba57f45ee4d00"
+ },
+ {
+ "name": "iphone",
+ "unicode": "1F4F1",
+ "digest": "be6f96c02ddae557f700fd20fe7b3f94c9e1c928acb82b2b8b214d231273fece"
+ },
+ {
+ "name": "island",
+ "unicode": "1F3DD",
+ "digest": "17f02b309b62ed9542b1d8943168302846040e420f413e56d799bb5fba7064fa"
+ },
+ {
+ "name": "izakaya_lantern",
+ "unicode": "1F3EE",
+ "digest": "ddb20f475aa119c3a64a55dff40f7a9dbc3a14f7ffc6cfbac89210c652f10d02"
+ },
+ {
+ "name": "jack_o_lantern",
+ "unicode": "1F383",
+ "digest": "62a701ac472619bcb3859e0d9a61b98c7f5c32150d2d04ca8c3e8fc3bec4dbd5"
+ },
+ {
+ "name": "japan",
+ "unicode": "1F5FE",
+ "digest": "2535300fff2b2e4b75fc73c187be6c0ea4bc4753e443db498ea55e268e627ab7"
+ },
+ {
+ "name": "japanese_castle",
+ "unicode": "1F3EF",
+ "digest": "70645aa05599e23a9ac4327e4a2e78bffe7ea06c38ec1935c15ae420619c5c1c"
+ },
+ {
+ "name": "japanese_goblin",
+ "unicode": "1F47A",
+ "digest": "59b6901dc6eedc6509c25b4eef6702bf461ded06c5ff12fe2a02a5b3301577c0"
+ },
+ {
+ "name": "japanese_ogre",
+ "unicode": "1F479",
+ "digest": "dab7e68cd4cbf99c13d64792c7104c4f0a846bc63aa12950fa8fab028dca301d"
+ },
+ {
+ "name": "jeans",
+ "unicode": "1F456",
+ "digest": "ddd032ac77cdfe49152a0e0a0eaaaea9f183590fb1f493ec30e9e39f679e3914"
+ },
+ {
+ "name": "jet_up",
+ "unicode": "1F6E6",
+ "digest": "3708e5e034b1c64d1268d66527e13c369aa0f8903bce9172bef773b2d1940948"
+ },
+ {
+ "name": "joy",
+ "unicode": "1F602",
+ "digest": "f90cfbcb14f906f8d786b61f022c978f381fc99ca422805f605631314e101805"
+ },
+ {
+ "name": "joy_cat",
+ "unicode": "1F639",
+ "digest": "6ca24a94490de66d1ca2cbc080bcd805f54ca295051d8e6588cae3fe6658c80a"
+ },
+ {
+ "name": "joystick",
+ "unicode": "1F579",
+ "digest": "ec172df88ef8e8a5512d6d906c13296875b7057ed0cca79f4ac8cddd9e1de34b"
+ },
+ {
+ "name": "kaaba",
+ "unicode": "1F54B",
+ "digest": "30f1a27a148399bbb811586eff795eff858701c42055c23e4d5bef7ae77f5f32"
+ },
+ {
+ "name": "key",
+ "unicode": "1F511",
+ "digest": "c68ed648350d3976c8d27a709020c8873ecf553929e66453acff96231684a1a2"
+ },
+ {
+ "name": "key2",
+ "unicode": "1F5DD",
+ "digest": "87a7d42531d7a11dcb11b0d6d1be611ee8cec35b5d22226a8ac6083fedef4f5d"
+ },
+ {
+ "name": "keyboard",
+ "unicode": "1F5AE",
+ "digest": "3b254cbf19946df3af05e501d11653d89fcda91684b7248d86186f842b83bf16"
+ },
+ {
+ "name": "keyboard_mouse",
+ "unicode": "1F5A6",
+ "digest": "95b523e55d8afeaeb06442bbe20e47f49643bb0c32d89a8cdbbccdead20532b3"
+ },
+ {
+ "name": "keyboard_with_jacks",
+ "unicode": "1F398",
+ "digest": "e29a0d0b8018d13458469edca13c60a882a2817957c1aa11b050684c995a47ee"
+ },
+ {
+ "name": "keycap_ten",
+ "unicode": "1F51F",
+ "digest": "7593aa7ffe7192a2e35c6ccec76522f6243777783c9152c7c03419835ea58c03"
+ },
+ {
+ "name": "kimono",
+ "unicode": "1F458",
+ "digest": "e92bea044fe013f1993c2229d86e9cca9d43f14aab00564ce6ff559bdc5ce93a"
+ },
+ {
+ "name": "kiss",
+ "unicode": "1F48B",
+ "digest": "c060eb09af2a0d0f77d307b995c15719b0e59c9162a490b8a553fac9b779c8f0"
+ },
+ {
+ "name": "kiss_mm",
+ "unicode": "1F468-2764-1F48B-1F468",
+ "digest": "381364ad988ec07cc3708fd60f71838092224009088fff587069b4e8ab01ee63"
+ },
+ {
+ "name": "kiss_ww",
+ "unicode": "1F469-2764-1F48B-1F469",
+ "digest": "7705ca707b73f44c856ea324bdfe30ed05244c8d192d1111f6e1d62ab3f2f8a5"
+ },
+ {
+ "name": "kissing",
+ "unicode": "1F617",
+ "digest": "3142617e8b9488689bd9efc67c0e4cc71a1870df8ffc308f949eedc5c3684051"
+ },
+ {
+ "name": "kissing_cat",
+ "unicode": "1F63D",
+ "digest": "ed26cee8c438ba41365b55c48457cdad3e8d43bf90db3128ac5b277718b82ed3"
+ },
+ {
+ "name": "kissing_closed_eyes",
+ "unicode": "1F61A",
+ "digest": "22d3369d21b4c2cb4c0c2cab9551cd848dd4f9adecfa64977d3f1a80fc0c8b53"
+ },
+ {
+ "name": "kissing_heart",
+ "unicode": "1F618",
+ "digest": "1f089b07447bdcc1baada6a2a9607d4ef4f2de9a6093fcab47a553a64b9acb76"
+ },
+ {
+ "name": "kissing_smiling_eyes",
+ "unicode": "1F619",
+ "digest": "e37d282861669adfa3953b9af833acfab7d55e787621d4318d77de7e3529d5c5"
+ },
+ {
+ "name": "knife",
+ "unicode": "1F52A",
+ "digest": "3fef068a6ada61630dc868e47d25e0e0550b44bc7cf530afe88ca63dc7ab2a39"
+ },
+ {
+ "name": "koala",
+ "unicode": "1F428",
+ "digest": "fe020ab9048f3c2a881474f8b1335db6bfaf37d115ff9b2d264f668d136122dd"
+ },
+ {
+ "name": "koko",
+ "unicode": "1F201",
+ "digest": "734a5cb296826a598e02be3f4ec22f318633ede2ce274914586256421e2df97b"
+ },
+ {
+ "name": "label",
+ "unicode": "1F3F7",
+ "digest": "9fe8195c3efab4d905b1cfcba0ae58cda12496030b0908de8076ff5e6777742e"
+ },
+ {
+ "name": "large_blue_circle",
+ "unicode": "1F535",
+ "digest": "ba4d0f84a9c2be9a65b25c8cfa78f30d4856d021b1853154dd1d2fd0c5bcfb6a"
+ },
+ {
+ "name": "large_blue_diamond",
+ "unicode": "1F537",
+ "digest": "d5aa5e315126859c10c83507be6b9e11cbf423f7a27145de089468cff9b94a94"
+ },
+ {
+ "name": "large_orange_diamond",
+ "unicode": "1F536",
+ "digest": "108600badd0ef267842325c0fbf326cb3504306332c64f6f5694de2b54c9438a"
+ },
+ {
+ "name": "last_quarter_moon",
+ "unicode": "1F317",
+ "digest": "68315b85bc1cb17bb82629bd1a6024a5124f3641b9878a732a8aad016c587546"
+ },
+ {
+ "name": "last_quarter_moon_with_face",
+ "unicode": "1F31C",
+ "digest": "146a419109b7f662bf87cf9de299e47d025a8758c8970b7dabf3483e1956b559"
+ },
+ {
+ "name": "laughing",
+ "unicode": "1F606",
+ "digest": "f22d3be77f1daf058d04c3cbc1fd7f76b4dc069d2d300b45e63e768b08d269c5"
+ },
+ {
+ "name": "leaves",
+ "unicode": "1F343",
+ "digest": "f65e2db125564eb04fc427a49fff175d6e2dae847bd12314d5e6a131610d5ccd"
+ },
+ {
+ "name": "ledger",
+ "unicode": "1F4D2",
+ "digest": "62df1772cec10c035ae0646e6cca4ba7d75b10636a520d091c5b42c2dc36b742"
+ },
+ {
+ "name": "left_luggage",
+ "unicode": "1F6C5",
+ "digest": "62292758715115e55ab6239805b7f99b7b35bdfa8d40da07fe391424f1f083d8"
+ },
+ {
+ "name": "left_receiver",
+ "unicode": "1F57B",
+ "digest": "8052e44951afee04c87296128744b5019ec783c9ed1a231f659af6c8ddaa50f3"
+ },
+ {
+ "name": "left_right_arrow",
+ "unicode": "2194",
+ "digest": "28a6945972451b1f4dadec5c55310b8868ffd9f3b0a07803287bc4e07a56e7d4"
+ },
+ {
+ "name": "leftwards_arrow_with_hook",
+ "unicode": "21A9",
+ "digest": "d672afc39fd50f78d7370be243173fe76ba50292f0c401305b562898939a8b7f"
+ },
+ {
+ "name": "lemon",
+ "unicode": "1F34B",
+ "digest": "e0e293a8b8c1b3c87534f5e05cf006671eb3c6d52b4d17d40f2e23bce215a8be"
+ },
+ {
+ "name": "leo",
+ "unicode": "264C",
+ "digest": "b0fd4e5f4637de530b62323521c6edcd80312d67ea4043eedd959acb6763474a"
+ },
+ {
+ "name": "leopard",
+ "unicode": "1F406",
+ "digest": "ede891be8484a17e6277431c64ec1bfd6b742544a41947ebc85005bc2d558bb1"
+ },
+ {
+ "name": "level_slider",
+ "unicode": "1F39A",
+ "digest": "49777cf160d9130d723e3bfef765c3de54033e6b059000fb0e22fb559b5ed190"
+ },
+ {
+ "name": "levitate",
+ "unicode": "1F574",
+ "digest": "3e4e9a5ac6a8dbd7909c58a9d915f16f1a0fc59cc019714ae5935f18e4704044"
+ },
+ {
+ "name": "libra",
+ "unicode": "264E",
+ "digest": "ec8e2e7a735abc9f2bddb115fc0e09f4bdc7a164679e2b57d127f58eee1155c2"
+ },
+ {
+ "name": "lifter",
+ "unicode": "1F3CB",
+ "digest": "f64db037fd21e5918e5de35d6a561ef4b44668e307ed351338de00fcf3e771e3"
+ },
+ {
+ "name": "lifter_tone1",
+ "unicode": "1F3CB-1F3FB",
+ "digest": "f9e0d161b12c4908ac3409b11c1a77ee38f33ba018f12416545876214bfb7c01"
+ },
+ {
+ "name": "lifter_tone2",
+ "unicode": "1F3CB-1F3FC",
+ "digest": "631eb6ed5bd147dc6f1f8b94149abe44d62a0f78e7809e37a4bfe127c40ed98f"
+ },
+ {
+ "name": "lifter_tone3",
+ "unicode": "1F3CB-1F3FD",
+ "digest": "406b5707a47d9066f016acf0b64fa695e3505acc2453758a0428de21efd7eb6d"
+ },
+ {
+ "name": "lifter_tone4",
+ "unicode": "1F3CB-1F3FE",
+ "digest": "d917164ed8c4bb1ffcc887ca256ec329e7fa1b9516eaf8c159f8b43fdb071ed6"
+ },
+ {
+ "name": "lifter_tone5",
+ "unicode": "1F3CB-1F3FF",
+ "digest": "f79ea93e8a40b3c895b693bf49eb4ce6e7b3f4413595e5881ea44839fd7fe8e5"
+ },
+ {
+ "name": "light_check_mark",
+ "unicode": "1F5F8",
+ "digest": "7842b0df8c2b6703bed0cce5d2790d394eec7120b2a245a76f375528f2729a7b"
+ },
+ {
+ "name": "light_rail",
+ "unicode": "1F688",
+ "digest": "7c2be55456f1332e849ff6699a26dda2e1641c280f45c9ec88dedf6d9b7b7fe2"
+ },
+ {
+ "name": "link",
+ "unicode": "1F517",
+ "digest": "cc4873f8a612dd721dddcd507a4430b4fb6c4abc15a8848456f0ffd97811b163"
+ },
+ {
+ "name": "lion_face",
+ "unicode": "1F981",
+ "digest": "935b1076815f51fafcd860a395d0a03c536acfcea61ffcf542a377da046fa7d9"
+ },
+ {
+ "name": "lips",
+ "unicode": "1F444",
+ "digest": "e3bc20f9e210fa1711271234fe61bf1c9ddf36dd6ffc5b832c6c3a769a1e59a8"
+ },
+ {
+ "name": "lips2",
+ "unicode": "1F5E2",
+ "digest": "c6ba915982ac47d8aaf14ad3605949df95588acfb4e147bf608f8c1714cdf19b"
+ },
+ {
+ "name": "lipstick",
+ "unicode": "1F484",
+ "digest": "335b912e163020df3d6d9f0a19a55d6547bd59b471c5a3e374c2968e49911ccc"
+ },
+ {
+ "name": "lock",
+ "unicode": "1F512",
+ "digest": "c20eacfb8ccd9bb85919a837c0d4650ee608edb48c85bff46945f613e95d7038"
+ },
+ {
+ "name": "lock_with_ink_pen",
+ "unicode": "1F50F",
+ "digest": "5cab25cea08e22d9c3f5de16de6d0ab658ca15cc93d7830f29b0f3e9348ec45f"
+ },
+ {
+ "name": "lollipop",
+ "unicode": "1F36D",
+ "digest": "33d2334a00bf0e15869ccc75fadc36f27f89abf0525bb71f859aad9e1dc4ad66"
+ },
+ {
+ "name": "loop",
+ "unicode": "27BF",
+ "digest": "fa1174ddc44e317d0796e07868c7ac8ac9c9274fbc8a6c3d0ec78d543c3c6bf0"
+ },
+ {
+ "name": "loud_sound",
+ "unicode": "1F50A",
+ "digest": "fb70229e13b690ffc1031d2e631123f8c908035a15218c297c1c4a3ff3624aa0"
+ },
+ {
+ "name": "loudspeaker",
+ "unicode": "1F4E2",
+ "digest": "e2d6cf9ec6412ee62f3128a1afd8c63ec74755c4833f01a4f99722407fe154d6"
+ },
+ {
+ "name": "love_hotel",
+ "unicode": "1F3E9",
+ "digest": "184670ebc4045043a7b18d576da3255d216551da522a11cde7df34524e9c7d50"
+ },
+ {
+ "name": "love_letter",
+ "unicode": "1F48C",
+ "digest": "9a4c52e2622fc7d364995ebc93ca530d972134621d117b72053a659dffc90ffc"
+ },
+ {
+ "name": "low_brightness",
+ "unicode": "1F505",
+ "digest": "c177b7fa9fdbef959cc47e7d16becd71117470b767a81ed6d15f80f464776c02"
+ },
+ {
+ "name": "m",
+ "unicode": "24C2",
+ "digest": "2eaf011e74d69613923dad424daaec4c13b592388dbcc5757b645bc058eedecb"
+ },
+ {
+ "name": "mag",
+ "unicode": "1F50D",
+ "digest": "029427bd73d2c79fffc5194ded01f6011952ec0124b7634c6230e0afa7ad7c95"
+ },
+ {
+ "name": "mag_right",
+ "unicode": "1F50E",
+ "digest": "f99de50bb59ec3bf1d4ccb8584ca09d4a7ceb5bf9f600ea8d3f84930efbf01b8"
+ },
+ {
+ "name": "mahjong",
+ "unicode": "1F004",
+ "digest": "da5d1fa980c38e092d414516161ca26046aa65ace3261999ea750f72e676ac6e"
+ },
+ {
+ "name": "mailbox",
+ "unicode": "1F4EB",
+ "digest": "14217df8f39a95fc0a0c527f97db1ca8564764034e921614decc5be705629352"
+ },
+ {
+ "name": "mailbox_closed",
+ "unicode": "1F4EA",
+ "digest": "e0c7beb205ec548a66d8afc7f103b64c6c79c08417ab550f19c36cc6d1a62bc4"
+ },
+ {
+ "name": "mailbox_with_mail",
+ "unicode": "1F4EC",
+ "digest": "6d381c0c4181be628d9409df1d85f8a9438c21ef5b92d82ef8ae1ff0079236de"
+ },
+ {
+ "name": "mailbox_with_no_mail",
+ "unicode": "1F4ED",
+ "digest": "74843d5ea9e03b48323f2252bdd000585f549b7fffe1fe181a25c38b99b5e23d"
+ },
+ {
+ "name": "man",
+ "unicode": "1F468",
+ "digest": "0275935258b4c832c3fcb06531d3e6972e2c3d46bab2973004750a9f00bd4cb6"
+ },
+ {
+ "name": "man_tone1",
+ "unicode": "1F468-1F3FB",
+ "digest": "1f6603d040f4a025f49d384170dd16b8da169663fc3282af1dc8710d9c1a7adf"
+ },
+ {
+ "name": "man_tone2",
+ "unicode": "1F468-1F3FC",
+ "digest": "d65bb03071b483946c69c61769d19b29a2af76fa7e43020e55f0bbc046492221"
+ },
+ {
+ "name": "man_tone3",
+ "unicode": "1F468-1F3FD",
+ "digest": "9af8ede7211b19a7dc0c60db083dd2bdc4897dda4d71e57feadf2e39d847f060"
+ },
+ {
+ "name": "man_tone4",
+ "unicode": "1F468-1F3FE",
+ "digest": "6555de60976aafeb024db78addb44eab2a412dd7277013f44d06757d03b6a252"
+ },
+ {
+ "name": "man_tone5",
+ "unicode": "1F468-1F3FF",
+ "digest": "b58b97a28a6adc1777acc05194cd917c730f90e37441124c384ded12e9a7d2a4"
+ },
+ {
+ "name": "man_with_gua_pi_mao",
+ "unicode": "1F472",
+ "digest": "88663173a6ccbebec5e24883c90d965447e022c6688773273110fe544d5b1607"
+ },
+ {
+ "name": "man_with_gua_pi_mao_tone1",
+ "unicode": "1F472-1F3FB",
+ "digest": "3c8bad3923a619f888e14544d357499a26a517e8fbe7a51027117b960c9eb842"
+ },
+ {
+ "name": "man_with_gua_pi_mao_tone2",
+ "unicode": "1F472-1F3FC",
+ "digest": "da125a3310fab19c9282497d53e2fc71ad07920ce60a0ef52dcdb31500023f09"
+ },
+ {
+ "name": "man_with_gua_pi_mao_tone3",
+ "unicode": "1F472-1F3FD",
+ "digest": "1d5842558847367966bf3ea473ff80fe744359bc5d969f4cc06cf2e452ed2fb6"
+ },
+ {
+ "name": "man_with_gua_pi_mao_tone4",
+ "unicode": "1F472-1F3FE",
+ "digest": "92be490f3ba602a43e2be8160d8bfd8a0691b2f81fe017b06df10f476a89ffab"
+ },
+ {
+ "name": "man_with_gua_pi_mao_tone5",
+ "unicode": "1F472-1F3FF",
+ "digest": "669f6b31bc7a8bf50b169d0600f14e00addaeb24144a1bace8b94950372839b0"
+ },
+ {
+ "name": "man_with_turban",
+ "unicode": "1F473",
+ "digest": "87d30d35ba40ee39c2df8ce19d975ce34a9c54688bafeac7377d7d481e55f1a4"
+ },
+ {
+ "name": "man_with_turban_tone1",
+ "unicode": "1F473-1F3FB",
+ "digest": "33b8b8154e0691e2ad66177dbf1e0101411fd8b3a16bf4e54c36d4a874f2a275"
+ },
+ {
+ "name": "man_with_turban_tone2",
+ "unicode": "1F473-1F3FC",
+ "digest": "1a6b83faa8d6e6a7d12a04898a6f22243287330a1faa081d2626b17dfb07174d"
+ },
+ {
+ "name": "man_with_turban_tone3",
+ "unicode": "1F473-1F3FD",
+ "digest": "5d43da5109e688ff8ca0675f33ebbaf930e206f1f01e3ee773f2844663fe572b"
+ },
+ {
+ "name": "man_with_turban_tone4",
+ "unicode": "1F473-1F3FE",
+ "digest": "bfaf7293c5ea75d0ecdc6fe5afe8f48e7b29b2e0df06ef974d3e1732f5db5dd4"
+ },
+ {
+ "name": "man_with_turban_tone5",
+ "unicode": "1F473-1F3FF",
+ "digest": "fba2404dd3d7eab5268519894cc0b386e1b17fdf14a04760c346014aa0e25acd"
+ },
+ {
+ "name": "mans_shoe",
+ "unicode": "1F45E",
+ "digest": "45dc13ac44c922b4c4b8ecb2e1a870a78e09d53da86843431ab0e9ec96ebcd97"
+ },
+ {
+ "name": "map",
+ "unicode": "1F5FA",
+ "digest": "f56116d09996d6d08fb5cdfb46622b545253f2649008170fc2011a9713fa875b"
+ },
+ {
+ "name": "maple_leaf",
+ "unicode": "1F341",
+ "digest": "40c5ee93396301911391cf6e70454b6fa8020fe5c85d3364136bcedb5d052cdb"
+ },
+ {
+ "name": "mask",
+ "unicode": "1F637",
+ "digest": "e0301cd27eb8c74c9772ff05b880215fc031ac1ae7f3177cd24ba0acb43b3834"
+ },
+ {
+ "name": "massage",
+ "unicode": "1F486",
+ "digest": "856d0fb1144ee91c58dfad74f9a2cababf6bae4b3ceba2a95c03ecd44ae3aa21"
+ },
+ {
+ "name": "massage_tone1",
+ "unicode": "1F486-1F3FB",
+ "digest": "fd53b06eb0967303c0914ebb79fd872900ec0f71b2852c7238517e192e5023e1"
+ },
+ {
+ "name": "massage_tone2",
+ "unicode": "1F486-1F3FC",
+ "digest": "7ef57359a339ae1ca4488f9a6195a352e74daf5b67d8e1ae1e91fe866921c40c"
+ },
+ {
+ "name": "massage_tone3",
+ "unicode": "1F486-1F3FD",
+ "digest": "e4fb643b6242bedb395e503ae337a88b2a255b5fda88b4aaa93396f948614a6e"
+ },
+ {
+ "name": "massage_tone4",
+ "unicode": "1F486-1F3FE",
+ "digest": "94f007c2daf9455fa8d2b10cc7ccff7db9bc9daf835ef5c3699be091938db833"
+ },
+ {
+ "name": "massage_tone5",
+ "unicode": "1F486-1F3FF",
+ "digest": "d18e800b728bf45b500f492062dc81312ca1ad7b1a0277a3d5bc150e4632ea1c"
+ },
+ {
+ "name": "meat_on_bone",
+ "unicode": "1F356",
+ "digest": "674a2a58e174b7681eef3b6c5b39c098ed9374cc610d037166c0092ee5269a97"
+ },
+ {
+ "name": "medal",
+ "unicode": "1F3C5",
+ "digest": "270d438b6e2155e944dc734ea3e4d02409e51f59db2db636398fbf96e5edb0e6"
+ },
+ {
+ "name": "mega",
+ "unicode": "1F4E3",
+ "digest": "540ab4fd5bab041a681749b85e6de598ebcbfc4fbf5c3cdbd9ca1e8256191733"
+ },
+ {
+ "name": "melon",
+ "unicode": "1F348",
+ "digest": "39dd0ecb23e2d3da6cbb7309333fed5d7e2cb38c0afc526ade78520eca11b5f4"
+ },
+ {
+ "name": "menorah",
+ "unicode": "1F54E",
+ "digest": "5f81bc2e5a34bf76481d2958fdb0b4e4540c599aa837a6453609a39023885d8c"
+ },
+ {
+ "name": "mens",
+ "unicode": "1F6B9",
+ "digest": "5ed56cff80e8ee7ed581f2a2e365915db5cb29df89e850e0add0b68db4b0c788"
+ },
+ {
+ "name": "metal",
+ "unicode": "1F918",
+ "digest": "45e5fac0b9b019cf217dcfd1380cafb0d03063454612178278dac1ca5f8476a6"
+ },
+ {
+ "name": "metal_tone1",
+ "unicode": "1F918-1F3FB",
+ "digest": "9b3596fe7c063df838f0a43fb680ce10fb88e2b73c5c3324abfa357a224c17aa"
+ },
+ {
+ "name": "metal_tone2",
+ "unicode": "1F918-1F3FC",
+ "digest": "e15a4898a0efca4354ac48d6b01ff0618ce8b110b1246a4f5d78e19b54658be6"
+ },
+ {
+ "name": "metal_tone3",
+ "unicode": "1F918-1F3FD",
+ "digest": "c159e8179cb1907c246b432d87c5253b914fd7cebb6ac05292c4e38eff4815b0"
+ },
+ {
+ "name": "metal_tone4",
+ "unicode": "1F918-1F3FE",
+ "digest": "a8a43a88028c97074321e3da56df1045db41ede58bf286c21d7ae90f222f2011"
+ },
+ {
+ "name": "metal_tone5",
+ "unicode": "1F918-1F3FF",
+ "digest": "e6611e826e867e2c73a8cadb138e4aa6365e3583dd229ff24b3e8f161904bf56"
+ },
+ {
+ "name": "metro",
+ "unicode": "1F687",
+ "digest": "532378cf385f9a7fafe2f5c8203e675be6d38798871f4c8e2c50498a1529f956"
+ },
+ {
+ "name": "microphone",
+ "unicode": "1F3A4",
+ "digest": "46da2b94e4dc233f640249103f09ec915aaa812cce90afe68fedb6774a27ad4b"
+ },
+ {
+ "name": "microphone2",
+ "unicode": "1F399",
+ "digest": "f9df32cd207808f67a895d3460a215d1ecc42e377907bcd64731c02b697d4f32"
+ },
+ {
+ "name": "microscope",
+ "unicode": "1F52C",
+ "digest": "79918f5fe0a39f31f270a481f4c6e00ea49fc09d64b1ae78770971293c2b1ed8"
+ },
+ {
+ "name": "middle_finger",
+ "unicode": "1F595",
+ "digest": "c6320b236a4a9593aeade511b52dd3114207e947458cb3b818c78737a505fdf6"
+ },
+ {
+ "name": "middle_finger_tone1",
+ "unicode": "1F595-1F3FB",
+ "digest": "93c7aa994856185519d576cb779bdcff3a33f7077eef98e70968125f92f02448"
+ },
+ {
+ "name": "middle_finger_tone2",
+ "unicode": "1F595-1F3FC",
+ "digest": "a0de802294717b80e08d9d30f5fd64eacb90b5b3b9d7a0c27d6226a22822597f"
+ },
+ {
+ "name": "middle_finger_tone3",
+ "unicode": "1F595-1F3FD",
+ "digest": "8bbbab07c838257416bbf8377904362c07019fca9d5abf9fd048ccf6370178da"
+ },
+ {
+ "name": "middle_finger_tone4",
+ "unicode": "1F595-1F3FE",
+ "digest": "d9eed8db540fdb669c6ae5ef168b77659660589f5ddd9b66062274d335a3ef04"
+ },
+ {
+ "name": "middle_finger_tone5",
+ "unicode": "1F595-1F3FF",
+ "digest": "0519c3298040e57db202294476df239edb9b23b44848bab296bc45eda7cf8664"
+ },
+ {
+ "name": "military_medal",
+ "unicode": "1F396",
+ "digest": "bd1da0004768f404c6bb4db85d4b748f766a77ab3edb74e709d0c0064509a043"
+ },
+ {
+ "name": "milky_way",
+ "unicode": "1F30C",
+ "digest": "598b4e641c1081bb03ce38a29f9711fc8616373216a833e4daa14fbe97a358f5"
+ },
+ {
+ "name": "minibus",
+ "unicode": "1F690",
+ "digest": "3d15791ca96349c3abb5bd5d1014b6b33b984db19609f56f5fd1e8d2fc551809"
+ },
+ {
+ "name": "minidisc",
+ "unicode": "1F4BD",
+ "digest": "83c4bfda4e0a80785fa1c3f2bbf3c15aca2bda8ea3727ce78bc4236e1e377a36"
+ },
+ {
+ "name": "mobile_phone_off",
+ "unicode": "1F4F4",
+ "digest": "cfe6dfd766b9e0b4768df25d6e943c9abc0e910ff5e5c7a8a0f425c786bbab8d"
+ },
+ {
+ "name": "money_mouth",
+ "unicode": "1F911",
+ "digest": "3ac2f9b5409e1426eef6966938ca04cf78aeffefd43f44b6c86af4af7836e22f"
+ },
+ {
+ "name": "money_with_wings",
+ "unicode": "1F4B8",
+ "digest": "f7f1fa502d2f6804169869aeb5ca7f0ea64bc2d6a0204f08875d65da4f8cb332"
+ },
+ {
+ "name": "moneybag",
+ "unicode": "1F4B0",
+ "digest": "442db49cda27360d2eb781489c9879730a6094c3267bb0a0a8687d84f8fed078"
+ },
+ {
+ "name": "monkey",
+ "unicode": "1F412",
+ "digest": "3141c971aacbadaba21f970a515e192740212be2a49fa1f5eb0fc4dc576e209f"
+ },
+ {
+ "name": "monkey_face",
+ "unicode": "1F435",
+ "digest": "e2397431d2befe44bf5298fa81d865d80722bf954113bceacc2aa98b84d856e2"
+ },
+ {
+ "name": "monorail",
+ "unicode": "1F69D",
+ "digest": "b546153200d6fbe8d65b1b34f62ff4a19b1b6a159eb1b536c5c2ecb56dab0ec9"
+ },
+ {
+ "name": "mood_bubble",
+ "unicode": "1F5F0",
+ "digest": "1df7061217e478d43ab9a87d4f351c4ca56705acd6b4e0b0bedfdece77635f1b"
+ },
+ {
+ "name": "mood_bubble_lightning",
+ "unicode": "1F5F1",
+ "digest": "4af3e4e53eaa328b0d20542ab31705a74bf9fd368cd0673b706838ce1681d3c9"
+ },
+ {
+ "name": "mood_lightning",
+ "unicode": "1F5F2",
+ "digest": "6784635e81ec722fd50a1c2a23b0f9679e4bf1b5ae2b5a01eeb995bc1f7a426f"
+ },
+ {
+ "name": "mortar_board",
+ "unicode": "1F393",
+ "digest": "cb59edb08f75c374088b65284e4d0f77b9bc9573de3e6a5127f865431011e54c"
+ },
+ {
+ "name": "mosque",
+ "unicode": "1F54C",
+ "digest": "a08ddb74342dea8f79063db6f98ba03eb08fe99481de8ce9123827ca7f17c7f3"
+ },
+ {
+ "name": "motorboat",
+ "unicode": "1F6E5",
+ "digest": "9dbea67bbe2e95dcc68c049a58f87390a44350b32308342615d75214af3d1cef"
+ },
+ {
+ "name": "motorcycle",
+ "unicode": "1F3CD",
+ "digest": "8429fb6dfeb873abdffcc179c32d4f23e91c9e6b27b06cd204fd2e83cc11189e"
+ },
+ {
+ "name": "motorway",
+ "unicode": "1F6E3",
+ "digest": "fc05a36c917637c135b0a60db8afcd58cee2b335070fe3888697f8026c9d11a5"
+ },
+ {
+ "name": "mount_fuji",
+ "unicode": "1F5FB",
+ "digest": "22bfffef033637b3c9b2fe7e539c74a659d2a49e594d2b33be894da00654d059"
+ },
+ {
+ "name": "mountain",
+ "unicode": "26F0",
+ "digest": "486cf4e9d5f3913d138fdb7878fe869b39caa3fca53876365957a89dc8f7edb8"
+ },
+ {
+ "name": "mountain_bicyclist",
+ "unicode": "1F6B5",
+ "digest": "b547b96951b6837df8ae3be1e846f15e7e2ac06d976e1fe7f1442dcc5d3a0942"
+ },
+ {
+ "name": "mountain_bicyclist_tone1",
+ "unicode": "1F6B5-1F3FB",
+ "digest": "68ce0d55163c7b89ee1d87b752ece127bb25ca9deb3421b31df549a00ac5f69d"
+ },
+ {
+ "name": "mountain_bicyclist_tone2",
+ "unicode": "1F6B5-1F3FC",
+ "digest": "5bfa82180bfb8bc4444cf301688aff02884895574a7ba66b398aaf20bde0f101"
+ },
+ {
+ "name": "mountain_bicyclist_tone3",
+ "unicode": "1F6B5-1F3FD",
+ "digest": "33cb64a792123b81a05080465a0ea1035a2cdfdab01c71f5f725a5f92251c3e8"
+ },
+ {
+ "name": "mountain_bicyclist_tone4",
+ "unicode": "1F6B5-1F3FE",
+ "digest": "9c3fa4e65dcb0ad69b963292e77c7a75853ae3c1d18a90670f81ffb65b5d020c"
+ },
+ {
+ "name": "mountain_bicyclist_tone5",
+ "unicode": "1F6B5-1F3FF",
+ "digest": "871de9e3fddb49b305e5f91000143878b0288c107a125c4e60acf2b6cf8b7f3f"
+ },
+ {
+ "name": "mountain_cableway",
+ "unicode": "1F6A0",
+ "digest": "f248ed5bf864f4a81e365b30d2825d2e6fc15a200c4ccf69e9f797341529f955"
+ },
+ {
+ "name": "mountain_railway",
+ "unicode": "1F69E",
+ "digest": "7dd08745ab56c95c3dfcebcca517ff231cef61b670cedf9d7c53f3244c34e30b"
+ },
+ {
+ "name": "mountain_snow",
+ "unicode": "1F3D4",
+ "digest": "9939aade3d4d972ba3af16fcc6cc2454978f5426e4c92838734a44db065ce0ff"
+ },
+ {
+ "name": "mouse",
+ "unicode": "1F42D",
+ "digest": "fb20b3a82f407a6316bbbac68d58018c3d5b93a9a6ae968f44ace18d1c5698d9"
+ },
+ {
+ "name": "mouse2",
+ "unicode": "1F401",
+ "digest": "87be4099523ec32440e6d091f1193a8ed90730b9fbecaafed4912585bfe7818c"
+ },
+ {
+ "name": "mouse_one",
+ "unicode": "1F5AF",
+ "digest": "e0d2055ccba489d24e0c0b6d2f22793efe48a734b0fd50f5af88f721b40665c0"
+ },
+ {
+ "name": "mouse_three_button",
+ "unicode": "1F5B1",
+ "digest": "6a5629fee01145211cc8f4e8f59c5f1e61affed38c650502213d76c7d8861b01"
+ },
+ {
+ "name": "movie_camera",
+ "unicode": "1F3A5",
+ "digest": "d6633b89a637b64d617c3032eed74bb82d3fa732dd9975486b2b5841b473808a"
+ },
+ {
+ "name": "moyai",
+ "unicode": "1F5FF",
+ "digest": "bf948c26cd98e2f5e48da363f2924a9d7c217232115a00cec372d0d5293402a8"
+ },
+ {
+ "name": "muscle",
+ "unicode": "1F4AA",
+ "digest": "c85147efb786bdea3e7d53e2edf6b827280cd9fa881661a6102a614bf5b3579f"
+ },
+ {
+ "name": "muscle_tone1",
+ "unicode": "1F4AA-1F3FB",
+ "digest": "38d071df2b25031b61f3605b03c34d2e5d3e35d29f3c4aada14be37e19750eb8"
+ },
+ {
+ "name": "muscle_tone2",
+ "unicode": "1F4AA-1F3FC",
+ "digest": "dcf11b76c8ffb58dc7e4f9ecd32a4c291d9772d51df2853d41081e041e7e0876"
+ },
+ {
+ "name": "muscle_tone3",
+ "unicode": "1F4AA-1F3FD",
+ "digest": "a3d5f8f2dbfc28f9713ee657428ea3292c47d0b22f11a51c13594be22b0f5204"
+ },
+ {
+ "name": "muscle_tone4",
+ "unicode": "1F4AA-1F3FE",
+ "digest": "eb220fc19be58d16cacc6b721e1011078b03256c0245756f251a4c2bcf50586c"
+ },
+ {
+ "name": "muscle_tone5",
+ "unicode": "1F4AA-1F3FF",
+ "digest": "4e18708cbd61eaad288f913c86ad2d45108dd4484bc35879c5dcdd075eeb09fd"
+ },
+ {
+ "name": "mushroom",
+ "unicode": "1F344",
+ "digest": "a2b252cd759244409d9a8066470059948e2c50b8cc86b59821c1c86b5190f640"
+ },
+ {
+ "name": "musical_keyboard",
+ "unicode": "1F3B9",
+ "digest": "dcb3e84d27bfe373e5ea7ede457908de52002f0fd6105e9f3f5525c54d2a43dd"
+ },
+ {
+ "name": "musical_note",
+ "unicode": "1F3B5",
+ "digest": "76a0f598f8e251a9dab44f2e14f2b7a6fb0c0c351e0f37862c8c99d380f1c261"
+ },
+ {
+ "name": "musical_score",
+ "unicode": "1F3BC",
+ "digest": "a132c6b35236005b45c830a42fa97b454d3061c14991c6320f34807f10ba6a4a"
+ },
+ {
+ "name": "mute",
+ "unicode": "1F507",
+ "digest": "73a99b7f9e00f92cab78cd304dee4e893a112c3a6f2285c13d44916ea547458e"
+ },
+ {
+ "name": "nail_care",
+ "unicode": "1F485",
+ "digest": "62f721d3610d1647dba4b3f53cd4f2bc4180dae298314c2cca2a6a8ab1664525"
+ },
+ {
+ "name": "nail_care_tone1",
+ "unicode": "1F485-1F3FB",
+ "digest": "11b82ed2e6b6619c9b74702fdacfb0ddc91310191c8b89f355c7c69a72673f8f"
+ },
+ {
+ "name": "nail_care_tone2",
+ "unicode": "1F485-1F3FC",
+ "digest": "5195c76bccb9149d9080347d785dae2cce947bada5b198fae8c23e42f5553154"
+ },
+ {
+ "name": "nail_care_tone3",
+ "unicode": "1F485-1F3FD",
+ "digest": "50eab0bf825c5e00db07a3f5ad26b1bb221f54efb5c55549f392b2f5aec09e5a"
+ },
+ {
+ "name": "nail_care_tone4",
+ "unicode": "1F485-1F3FE",
+ "digest": "d05a9ccfad02191c89e4cbd00aa48fdaf908c0de6681f4a587d500be448e528f"
+ },
+ {
+ "name": "nail_care_tone5",
+ "unicode": "1F485-1F3FF",
+ "digest": "62466354dcf6717a8b9e942ca2c5ad15a26aa815c213e3b01faba9a2e302ecdd"
+ },
+ {
+ "name": "name_badge",
+ "unicode": "1F4DB",
+ "digest": "0a1cb0f7d489d3356a4d3e01f9faf78449d82d8ec4595c8639a55c3606c97c40"
+ },
+ {
+ "name": "necktie",
+ "unicode": "1F454",
+ "digest": "029e1140391ef559a9316021c2db94f05653751fdf9d8f366446467a70fee6df"
+ },
+ {
+ "name": "negative_squared_cross_mark",
+ "unicode": "274E",
+ "digest": "0ba0e705fdeac99edd712db31a8846320b9d2cf53c9cb4d4bcfd22ba4e1488ea"
+ },
+ {
+ "name": "nerd",
+ "unicode": "1F913",
+ "digest": "94efd551700aae8909b8dd7a78a54a33e070d24b2e0a10534353645084614e98"
+ },
+ {
+ "name": "network",
+ "unicode": "1F5A7",
+ "digest": "1dbaa54deeb2328fd8a3f044e450c97ac3ff39627c598bb2f4312d677482ee06"
+ },
+ {
+ "name": "neutral_face",
+ "unicode": "1F610",
+ "digest": "df01da8501e1f588049c8ed66e504e9abcce83f74ce5790f4d3dc547408f77ee"
+ },
+ {
+ "name": "new",
+ "unicode": "1F195",
+ "digest": "24e80abd29750d8b297335cdd4751b6250bb820560cf0392a6cc8783d34db63a"
+ },
+ {
+ "name": "new_moon",
+ "unicode": "1F311",
+ "digest": "2d697e431eac53d6e1ea367b5da03c15fc535cd7e8c214f801fe595b768a8e11"
+ },
+ {
+ "name": "new_moon_with_face",
+ "unicode": "1F31A",
+ "digest": "ea469a4668ded071f35e5898ae229fdb5d02b0730ce233169b83e22f81292baa"
+ },
+ {
+ "name": "newspaper",
+ "unicode": "1F4F0",
+ "digest": "0aaf6747a43fb60cd15e6e64ca0eccaade331b376c6fe6712fd5e8294e9868cc"
+ },
+ {
+ "name": "newspaper2",
+ "unicode": "1F5DE",
+ "digest": "0ca6b5850091f23295c970815a8e64a52e3c3dae492029ecb1e0726c2693f9bf"
+ },
+ {
+ "name": "ng",
+ "unicode": "1F196",
+ "digest": "4994c9b795033ed788e98c4af571a1dffe28c0a1479e3b42dcae21bb08381b5f"
+ },
+ {
+ "name": "night_with_stars",
+ "unicode": "1F303",
+ "digest": "56bb4a59a897c1836ee1a49cc99f468891b790b0f8bce203c201c13bb7b8ae9a"
+ },
+ {
+ "name": "nine",
+ "unicode": "0039-20E3",
+ "digest": "7e3644a98cb6417a351530c9ce6b368e637a22c847a8c04133897dc1c5d7419f"
+ },
+ {
+ "name": "no_bell",
+ "unicode": "1F515",
+ "digest": "f4fb42836132000101624fecef8b9358736a0fc76beae460e6986aaa479204fd"
+ },
+ {
+ "name": "no_bicycles",
+ "unicode": "1F6B3",
+ "digest": "b3c258bea7d6988640e3348598c03c97632ca00a11cbf0352995b801ff4a296b"
+ },
+ {
+ "name": "no_entry",
+ "unicode": "26D4",
+ "digest": "ac807d54092efdc3aea417790a7d0c50b59800c9ea49b37f1aec6d2e453c5f6d"
+ },
+ {
+ "name": "no_entry_sign",
+ "unicode": "1F6AB",
+ "digest": "5a17d677ec1c7595a7970a1cbe0d20909341b30d3ab31471ced590f51fff1ff7"
+ },
+ {
+ "name": "no_good",
+ "unicode": "1F645",
+ "digest": "8ce921e5e13e1203cf43fdc3e7c5ec1fb2a1f9ff79f21539cff542c80af2e5fe"
+ },
+ {
+ "name": "no_good_tone1",
+ "unicode": "1F645-1F3FB",
+ "digest": "aab4d354aaac06e8348eb354487c6381e475b44651cb2716660904a36c47a1b6"
+ },
+ {
+ "name": "no_good_tone2",
+ "unicode": "1F645-1F3FC",
+ "digest": "8fb66b1a7b8f72062794281294515d47471a8c59de300b99d656c3412ca19d64"
+ },
+ {
+ "name": "no_good_tone3",
+ "unicode": "1F645-1F3FD",
+ "digest": "aeecf73fb9dca24b4002db2802fc9b5a483644c49f834c19f143d4e56ec46c1a"
+ },
+ {
+ "name": "no_good_tone4",
+ "unicode": "1F645-1F3FE",
+ "digest": "fadeb23307d5ccabbf08c848cf81c66c05b152aa32b85f86061caf14760f8eb9"
+ },
+ {
+ "name": "no_good_tone5",
+ "unicode": "1F645-1F3FF",
+ "digest": "cf26d5d6463d0febf4e1f08e343308742ffe0811cfc30c459b87d4cc812f5d04"
+ },
+ {
+ "name": "no_mobile_phones",
+ "unicode": "1F4F5",
+ "digest": "3b4ead88beca33f1e303d0a45268849be7aaaff7830b761732c7a5afc5a2de3a"
+ },
+ {
+ "name": "no_mouth",
+ "unicode": "1F636",
+ "digest": "2af81a3e07a8b7827a1e58f6f5036ccff2f6e7b0027a4f934c9fa34c6a780963"
+ },
+ {
+ "name": "no_pedestrians",
+ "unicode": "1F6B7",
+ "digest": "9f9ed90bb8f9964fa8cb0048fc092ecc0612a1994c98d19ef0b5a58607888476"
+ },
+ {
+ "name": "no_smoking",
+ "unicode": "1F6AD",
+ "digest": "fb90290ff5c917b7307a97c8ba793d20c61042525cf2f7bfd4cd2a7878aeefa5"
+ },
+ {
+ "name": "non-potable_water",
+ "unicode": "1F6B1",
+ "digest": "c4ddca2ab1a97260e9b2c2aa33fb03455c0e8174541c3a9416fc44143a3ee567"
+ },
+ {
+ "name": "nose",
+ "unicode": "1F443",
+ "digest": "308e28b15b7f734f6f184ae367789d7cf258656b24861cf8d5935127d810aa3f"
+ },
+ {
+ "name": "nose_tone1",
+ "unicode": "1F443-1F3FB",
+ "digest": "392d24b38ac3edc2d7b83945a60bbe9115a6a97658d8af35281a7cbef79449e8"
+ },
+ {
+ "name": "nose_tone2",
+ "unicode": "1F443-1F3FC",
+ "digest": "409a790339c405770492e49fdb0b5ba34087c27e2f9018ecd845ab078e61476a"
+ },
+ {
+ "name": "nose_tone3",
+ "unicode": "1F443-1F3FD",
+ "digest": "92b52b479a935f31e460257d809c531edad1a6bb4583ad18233b12c4e45202fe"
+ },
+ {
+ "name": "nose_tone4",
+ "unicode": "1F443-1F3FE",
+ "digest": "78ad2e857792e86cded6ba5620f634f7d1f79a92c82c266e48fab9bd73df3688"
+ },
+ {
+ "name": "nose_tone5",
+ "unicode": "1F443-1F3FF",
+ "digest": "dbef6813c1965d3e93f70f33f118f9950130af21c622cea97ea215a36b4fa73f"
+ },
+ {
+ "name": "note",
+ "unicode": "1F5C9",
+ "digest": "073660fdaa02ecf98d04f61f8d65d6cc447ccae3825fccaff19a2c99ebba52af"
+ },
+ {
+ "name": "note_empty",
+ "unicode": "1F5C6",
+ "digest": "06b56eeaca6349bbcf1020bea98f937450a7e086db65cd5d7497748e0fb607be"
+ },
+ {
+ "name": "notebook",
+ "unicode": "1F4D3",
+ "digest": "64bd4a3e7ca7b22fc704c7b7bd4d13540c16bc69b9d8dd76e69e6ad573ab3823"
+ },
+ {
+ "name": "notebook_with_decorative_cover",
+ "unicode": "1F4D4",
+ "digest": "4b45f28fbde1be5c214a6bc2413abc91db02bccd86f74c21b7f4a4da8b75a46f"
+ },
+ {
+ "name": "notepad",
+ "unicode": "1F5CA",
+ "digest": "85069e2d13540886457368a57295072aec44c7137d9223bfcf908ce1f0e5124e"
+ },
+ {
+ "name": "notepad_empty",
+ "unicode": "1F5C7",
+ "digest": "8be5053e74c13d8220917c5aee1f4afdecb001612886438f283b0c2a0fecf6af"
+ },
+ {
+ "name": "notepad_spiral",
+ "unicode": "1F5D2",
+ "digest": "c181b6c1cc6063ec1848e46cbbf1d8b890c53b59cdc5218311ce06889570e727"
+ },
+ {
+ "name": "notes",
+ "unicode": "1F3B6",
+ "digest": "bf3868386e17eac40ac7fbabea027042027ff061daafe406c869cdd8ce94641d"
+ },
+ {
+ "name": "nut_and_bolt",
+ "unicode": "1F529",
+ "digest": "fdb9d7408202fad7a52ff21608042c08c3b0beb195999fff233df36a29dc9e96"
+ },
+ {
+ "name": "o",
+ "unicode": "2B55",
+ "digest": "8e119dba4130bd33b3ee5c862fb4fa5a691173911ffee51cb9359fee3398e330"
+ },
+ {
+ "name": "o2",
+ "unicode": "1F17E",
+ "digest": "00d751124c25633611055bd61e74fc3f3d1779f0d09e1e707837686f613367b4"
+ },
+ {
+ "name": "ocean",
+ "unicode": "1F30A",
+ "digest": "9b1fbfd2a64f417d0c2cb91085b29a12d14e15844bc21798bdee938bb7bf6222"
+ },
+ {
+ "name": "octopus",
+ "unicode": "1F419",
+ "digest": "3fdfbc02f47ad434bdeb7f3a15cd4e8f8118ee1cd754627e358f1c2f4616f5e3"
+ },
+ {
+ "name": "oden",
+ "unicode": "1F362",
+ "digest": "afed1c5166943e5803602ffacc67652e3b29ee4222a6c36aba2daf88bd21ad3c"
+ },
+ {
+ "name": "office",
+ "unicode": "1F3E2",
+ "digest": "dc1836ef152d88fd628df18db770594f5dbc8d7f20d6ce982588b25b78b19c92"
+ },
+ {
+ "name": "oil",
+ "unicode": "1F6E2",
+ "digest": "f8b7626cb09e229203105b9c8c7f3fbb38c0650021092fc50115ad517248644a"
+ },
+ {
+ "name": "ok",
+ "unicode": "1F197",
+ "digest": "6b05bbab4a7104541c2f4bce553884d17ae0ad07589b19d6b53b6949c14f2269"
+ },
+ {
+ "name": "ok_hand",
+ "unicode": "1F44C",
+ "digest": "9981f32ef200b011a10f6bfa2066c41b6b5e7bcd6c3c21647980b640bc1fa93b"
+ },
+ {
+ "name": "ok_hand_tone1",
+ "unicode": "1F44C-1F3FB",
+ "digest": "e5933a9b64b03ce0634f15f02ff7b6424530dbdc0e283461e0c9992d0c2ca2ad"
+ },
+ {
+ "name": "ok_hand_tone2",
+ "unicode": "1F44C-1F3FC",
+ "digest": "4c04741c9f2c8731da8df3015e9aae00061a01848c2d22aab1e9853c271deed3"
+ },
+ {
+ "name": "ok_hand_tone3",
+ "unicode": "1F44C-1F3FD",
+ "digest": "216dc5a72f9e34bbb7b39f680c388bd5b52abf9b41b843342e53e285b7933076"
+ },
+ {
+ "name": "ok_hand_tone4",
+ "unicode": "1F44C-1F3FE",
+ "digest": "7139de7ec9d5a962cf87b9fbbeef3a53aa482bb840ab3b64d8d0da81bdc19886"
+ },
+ {
+ "name": "ok_hand_tone5",
+ "unicode": "1F44C-1F3FF",
+ "digest": "e18b0a1bc5d970cc63466bd6da6e9f855db37d1eada3230d19f600c1f5a402a3"
+ },
+ {
+ "name": "ok_woman",
+ "unicode": "1F646",
+ "digest": "3b2fa732d9c9addb056f136192428e99d805d4cb1c7dab724fd552c7e93197e4"
+ },
+ {
+ "name": "ok_woman_tone1",
+ "unicode": "1F646-1F3FB",
+ "digest": "017aca3797701b043a44f22e67dcad8b531a3ca14e629ae0d2fbc601ed3e49cb"
+ },
+ {
+ "name": "ok_woman_tone2",
+ "unicode": "1F646-1F3FC",
+ "digest": "036bed032bc5a616668775cda0d5640c810e2836aa28009c8e8bf2b487259c59"
+ },
+ {
+ "name": "ok_woman_tone3",
+ "unicode": "1F646-1F3FD",
+ "digest": "d9a4414caddda43d1a36828cfbecce5f2b7e5c1b67b4a47991b2ae0a34cf7ab7"
+ },
+ {
+ "name": "ok_woman_tone4",
+ "unicode": "1F646-1F3FE",
+ "digest": "942e1b9aa495c4c4de0804e4d4348422201299d649e5d65829ba4a308880df1c"
+ },
+ {
+ "name": "ok_woman_tone5",
+ "unicode": "1F646-1F3FF",
+ "digest": "e8d0fb5b999d5d63404493aa505b5af2260c76001023431d5e788773d0a9e2de"
+ },
+ {
+ "name": "older_man",
+ "unicode": "1F474",
+ "digest": "620f763325827acbeb9d57798ef55d87827d0dfc77b84d942e25bc5057f2cbfe"
+ },
+ {
+ "name": "older_man_tone1",
+ "unicode": "1F474-1F3FB",
+ "digest": "e0f35c12362eae503d1c30a345c3a4978196d351d8a1eb9d5f107c60ea4bbf52"
+ },
+ {
+ "name": "older_man_tone2",
+ "unicode": "1F474-1F3FC",
+ "digest": "671766ce9fa47c3fa009d4f138344c87d73032a1c38e48614c663f8ea5d0f673"
+ },
+ {
+ "name": "older_man_tone3",
+ "unicode": "1F474-1F3FD",
+ "digest": "6ff4885ef8c416b8970780a691fef74c8d89421ab11e0aa8c522c33e1c67fbe8"
+ },
+ {
+ "name": "older_man_tone4",
+ "unicode": "1F474-1F3FE",
+ "digest": "0ae7d4e316dcd4d27a5a6cdaabab88a4f992bd1b75f6ceaeb5b906ed1eb5269c"
+ },
+ {
+ "name": "older_man_tone5",
+ "unicode": "1F474-1F3FF",
+ "digest": "abe2757bd5e35f30d2a6daec09637ea5382a46d14d239b77282e9bf874229b57"
+ },
+ {
+ "name": "older_woman",
+ "unicode": "1F475",
+ "digest": "3ed599443eed25399aac999fc234c9e97f8fb6ec567e37a553c26e01021b097c"
+ },
+ {
+ "name": "older_woman_tone1",
+ "unicode": "1F475-1F3FB",
+ "digest": "7421c5dba67cfd1eeabb2fa8faf4aa0d615d23f191cf7d7c0ad9c1fa884edfda"
+ },
+ {
+ "name": "older_woman_tone2",
+ "unicode": "1F475-1F3FC",
+ "digest": "65edeef25648ac7f8be535df06af1286441691fa15176e99a6e83fc779aa2cde"
+ },
+ {
+ "name": "older_woman_tone3",
+ "unicode": "1F475-1F3FD",
+ "digest": "5d27bbcc5796227a9caec1c7612d3f691055655b96f7303e420839463d76c269"
+ },
+ {
+ "name": "older_woman_tone4",
+ "unicode": "1F475-1F3FE",
+ "digest": "75b858e910175fc0233503d672120fd43ac035ba3fd2052fbb44df39f6e3695c"
+ },
+ {
+ "name": "older_woman_tone5",
+ "unicode": "1F475-1F3FF",
+ "digest": "9da1cf10a605c470877d7f4a840f99344b1ec2e7b1ec7db61e930cde77025e3b"
+ },
+ {
+ "name": "om_symbol",
+ "unicode": "1F549",
+ "digest": "c8c1c9d445b1fc50a627b71bee21fba978e04532e4685ec032a0174f51fc12bb"
+ },
+ {
+ "name": "on",
+ "unicode": "1F51B",
+ "digest": "08e1159a68d3334a87ffa75b9e70826cb557d0f73a2c1d08f4c3d60476ecacc8"
+ },
+ {
+ "name": "oncoming_automobile",
+ "unicode": "1F698",
+ "digest": "6bff7f40fe223df6d16c7512532b8aa6f83e8c13e1007b63eb9aabf774c1a322"
+ },
+ {
+ "name": "oncoming_bus",
+ "unicode": "1F68D",
+ "digest": "127a357fcd96ce4b9ab11c3dba95d8ff811bab193dd8ba38efb7067a44752ce8"
+ },
+ {
+ "name": "oncoming_police_car",
+ "unicode": "1F694",
+ "digest": "57cb70e05e70c1f68ab42259f307ed9782c2b9d6e35d2dff2895aa23d7eb6b04"
+ },
+ {
+ "name": "oncoming_taxi",
+ "unicode": "1F696",
+ "digest": "174967ae4c3d5881d2408c71c020f704e933190af4caef5d2908e9ac382f35ea"
+ },
+ {
+ "name": "one",
+ "unicode": "0031-20E3",
+ "digest": "113b9d87c3e37c9c54e49cecccbfc40c15fb97fd03a51505df85e48b78702b2b"
+ },
+ {
+ "name": "open_file_folder",
+ "unicode": "1F4C2",
+ "digest": "def93715203aed464211798d773732895a19389a94a2e7ed43e7f229b2aab7da"
+ },
+ {
+ "name": "open_hands",
+ "unicode": "1F450",
+ "digest": "7c60a37ae11727c998908199b8709e52593b931843aef942f37b306b1edca12a"
+ },
+ {
+ "name": "open_hands_tone1",
+ "unicode": "1F450-1F3FB",
+ "digest": "09ffa9b3f28fc56a71e4e711bdfc87ce1a56721229377e71f1c00224523f8b9b"
+ },
+ {
+ "name": "open_hands_tone2",
+ "unicode": "1F450-1F3FC",
+ "digest": "21ecaba9f086bcb7eb07c17c2b2621bcd1ca28c57f79032d5e0eba356494cc85"
+ },
+ {
+ "name": "open_hands_tone3",
+ "unicode": "1F450-1F3FD",
+ "digest": "c7dbb8c44f78f7793b202ec215fee42b7e1e555d659fbf402383500217b89656"
+ },
+ {
+ "name": "open_hands_tone4",
+ "unicode": "1F450-1F3FE",
+ "digest": "867451d42492ab2277687447f421f744530b9ea057312326353fec39c94b18fd"
+ },
+ {
+ "name": "open_hands_tone5",
+ "unicode": "1F450-1F3FF",
+ "digest": "56335506cf68e29150cb68d7ebbb4a92aed390018966669a8144d20ae0d6cfe3"
+ },
+ {
+ "name": "open_mouth",
+ "unicode": "1F62E",
+ "digest": "f05fdf998e8b5c0b00ebd8b5ab17a67f5c0a45275f31a201af74e8ab0c2f7ba9"
+ },
+ {
+ "name": "ophiuchus",
+ "unicode": "26CE",
+ "digest": "98c61bb0c36d60c476d42d5e074297662e8d141dcab7004a5bd63c359eed3b84"
+ },
+ {
+ "name": "optical_disk",
+ "unicode": "1F5B8",
+ "digest": "df8c10028d29d65f144a6b789d1c3294e7b3293554c4c30d28d72dc7ba8d9a5d"
+ },
+ {
+ "name": "orange_book",
+ "unicode": "1F4D9",
+ "digest": "86d150ea3d62183ab7dfe2851cf7f4d1ae769b7ecbb1987b0f463e639e429598"
+ },
+ {
+ "name": "orthodox_cross",
+ "unicode": "2626",
+ "digest": "9c861285ca6d699cd2c72b6df44ec2b1e64138152f19c66e32df1ce770ff2e83"
+ },
+ {
+ "name": "outbox_tray",
+ "unicode": "1F4E4",
+ "digest": "b6a6015d5d7d528af485de23ff4518dc35408def1cc49bc6c9b01d880d613985"
+ },
+ {
+ "name": "ox",
+ "unicode": "1F402",
+ "digest": "cbcfe5c8c4d6b939e24e18e610785f171bb9410441e02c2eeb1bceb0a6246daf"
+ },
+ {
+ "name": "package",
+ "unicode": "1F4E6",
+ "digest": "4023cffce85384217a73609f457aec013876e689c44bcfff0bcc35f3e4e1ab00"
+ },
+ {
+ "name": "page",
+ "unicode": "1F5CF",
+ "digest": "cc745056525f59d9128d1d03b14770376bb09ab64b8ef4ac994ab7f38efd4783"
+ },
+ {
+ "name": "page_facing_up",
+ "unicode": "1F4C4",
+ "digest": "71a0872bf1b13c58746f9b41655227c75be107ab6083c0dce13cb16444af22e7"
+ },
+ {
+ "name": "page_with_curl",
+ "unicode": "1F4C3",
+ "digest": "cb4210464faea946c7b07db7067c7fc98920f778cf57721388f5362942ba3029"
+ },
+ {
+ "name": "pager",
+ "unicode": "1F4DF",
+ "digest": "209dbdc19aa650ecacc0569e17a9123c9a1e39df59c9b4120f3b0888b63cd6f1"
+ },
+ {
+ "name": "pages",
+ "unicode": "1F5D0",
+ "digest": "05bd47b78f089389356d9d839c736843f56b959ab4277056606ffcbb013390bc"
+ },
+ {
+ "name": "paintbrush",
+ "unicode": "1F58C",
+ "digest": "73eb33184f5f495d6c2699fafc1a8680069f82a70fbe519290c3a2ce30d1aee9"
+ },
+ {
+ "name": "palm_tree",
+ "unicode": "1F334",
+ "digest": "1589ff4b1b87296edc0118e4aa67b3b504ed85a5b8d47e7d0c3e309d0bbf8cd6"
+ },
+ {
+ "name": "panda_face",
+ "unicode": "1F43C",
+ "digest": "050ee87892f56ff485f460bc6c3846d98a0ca7083d2cf0b8ab24772b672273f2"
+ },
+ {
+ "name": "paperclip",
+ "unicode": "1F4CE",
+ "digest": "1463607a59345973f009fa53a719e2264b95743560adb99737bef29b1d133a95"
+ },
+ {
+ "name": "paperclips",
+ "unicode": "1F587",
+ "digest": "7071e031f4a100c3cb3573fbfa375360043f0276289a0818f2ffaf71b3580040"
+ },
+ {
+ "name": "park",
+ "unicode": "1F3DE",
+ "digest": "d257f0f1b1a0134573f80ba1a5f522a91c320ee7f93a1cb64877c077e7e19b50"
+ },
+ {
+ "name": "parking",
+ "unicode": "1F17F",
+ "digest": "e1d2cfd1c57ea85003ca4df066cbba4e506bf6c4d6c790e27b2f78ad8443fabf"
+ },
+ {
+ "name": "part_alternation_mark",
+ "unicode": "303D",
+ "digest": "b3cc2e803b255e858417345ba6ba52a1c22f511b483fec11b5d68c4432f759b6"
+ },
+ {
+ "name": "partly_sunny",
+ "unicode": "26C5",
+ "digest": "484990f5e1a3b14c731e7bd4b0b4a1c10cd5fb54ac7cf2751f40c8bf59d7e2b4"
+ },
+ {
+ "name": "passport_control",
+ "unicode": "1F6C2",
+ "digest": "224e8ef60d4d6587721727555de324948fb5b6c1cb5cc4b546960983d1ec85c4"
+ },
+ {
+ "name": "pause_button",
+ "unicode": "23F8",
+ "digest": "edd605ffaa39a7905ed0958b7cc69f00f5b271e579198d2df1746ad1b3648272"
+ },
+ {
+ "name": "peace",
+ "unicode": "262E",
+ "digest": "e0ee8a5c9fb18d5db6841b21527ed8fd955abdff9ffdb7b2684dca22107015fc"
+ },
+ {
+ "name": "peach",
+ "unicode": "1F351",
+ "digest": "a3f4fd5ff02e0a03104ab54456ee1a7521858ee68443856ee10e0972e5b6aaa5"
+ },
+ {
+ "name": "pear",
+ "unicode": "1F350",
+ "digest": "7a7a72568d53677cd1fff4d9e58e63327a742fa16d22a2bef03b4a6fa378d3b3"
+ },
+ {
+ "name": "pen_ballpoint",
+ "unicode": "1F58A",
+ "digest": "6becdc6f622c774bb09b7e7592bba2123ecccc9de32a35f0b18b50d7d54109cb"
+ },
+ {
+ "name": "pen_fountain",
+ "unicode": "1F58B",
+ "digest": "8c78cf0c2bd1d5e309d2d3356ff207e3fc76ca18dd6b90762cb62f6afbc95c6a"
+ },
+ {
+ "name": "pencil",
+ "unicode": "1F4DD",
+ "digest": "62b7ee5d9352114d09ee6f2c9a4c5e8b79f775a6c509e82ddfcdd61e13716249"
+ },
+ {
+ "name": "pencil2",
+ "unicode": "270F",
+ "digest": "aa2c572772187fee1f9125bb0950f5ce8a61f7dd2647258c40b4077ee5feb498"
+ },
+ {
+ "name": "pencil3",
+ "unicode": "1F589",
+ "digest": "52c1ba1228917eb491ac1745a495e0fdafba6b985a81caba250f71d1f94c725c"
+ },
+ {
+ "name": "penguin",
+ "unicode": "1F427",
+ "digest": "095de34b3f6a2521a342c21f5f2551a0092bf47429801c15b7bbf0913924f412"
+ },
+ {
+ "name": "pennant_black",
+ "unicode": "1F3F2",
+ "digest": "cd3c33bfc3c7fbe84b98d2d481d56a7bf5488ff94afadd8b5a0e454768b80269"
+ },
+ {
+ "name": "pennant_white",
+ "unicode": "1F3F1",
+ "digest": "818b1be73540f2cfeb1c514e1ee75d18715af317f0db817d9ae081b9ea33d4b0"
+ },
+ {
+ "name": "pensive",
+ "unicode": "1F614",
+ "digest": "2d9e7f1eed14dcc86674cec78e992567a40d0f223fc67d722b91eebcd1251269"
+ },
+ {
+ "name": "performing_arts",
+ "unicode": "1F3AD",
+ "digest": "a202755bab6427433975589bb8b63e61e5d7f55c6242676d8000e91eedabc55e"
+ },
+ {
+ "name": "persevere",
+ "unicode": "1F623",
+ "digest": "686ef3fc70ce8294d02a764ebd75b69f25cca6bff6b92e7905130366d22f6d8a"
+ },
+ {
+ "name": "person_frowning",
+ "unicode": "1F64D",
+ "digest": "16e8fbf22c0b4c237d0d45202fa32d1ebd04760a5b6975c9c9b477321ccb0e12"
+ },
+ {
+ "name": "person_frowning_tone1",
+ "unicode": "1F64D-1F3FB",
+ "digest": "a143b865976ce3cf307db854cfd1ca58c3832df0eee5e9b0ab307cf4f24ba3db"
+ },
+ {
+ "name": "person_frowning_tone2",
+ "unicode": "1F64D-1F3FC",
+ "digest": "4e7050d8a38019ba2293f66b9930e6a7e35dacf3b3bc9431edb586a0d9ea8054"
+ },
+ {
+ "name": "person_frowning_tone3",
+ "unicode": "1F64D-1F3FD",
+ "digest": "0750015d3ac1b5954d31e36cd59c70b6ed9f4df698082484b7ac59eb0b9964b0"
+ },
+ {
+ "name": "person_frowning_tone4",
+ "unicode": "1F64D-1F3FE",
+ "digest": "18d6cc92d0990624218d38d6eeed60bccb371d0fc9f1c889e9476b3b0c44b5e8"
+ },
+ {
+ "name": "person_frowning_tone5",
+ "unicode": "1F64D-1F3FF",
+ "digest": "4a898199cbaf083d37511f51d8a1d2560b7a20c62a1b09087831da7010fbd093"
+ },
+ {
+ "name": "person_with_blond_hair",
+ "unicode": "1F471",
+ "digest": "67d95a0801c65f62db55fa80ab35dec65c239601a44bf5f5902e4645f126770e"
+ },
+ {
+ "name": "person_with_blond_hair_tone1",
+ "unicode": "1F471-1F3FB",
+ "digest": "e79717bfe30a26eafc082a75fa7547d8f2ad3c123fb2d75a95e75f0ce7ecbd0c"
+ },
+ {
+ "name": "person_with_blond_hair_tone2",
+ "unicode": "1F471-1F3FC",
+ "digest": "c4a1961c292149ab6e1fd54a7894398599bf855de97a05ee4e836a86a400deb3"
+ },
+ {
+ "name": "person_with_blond_hair_tone3",
+ "unicode": "1F471-1F3FD",
+ "digest": "e2707d0cf778bee5b72d861ec76430eb1cf9f9820f066ee6327574d5697f445e"
+ },
+ {
+ "name": "person_with_blond_hair_tone4",
+ "unicode": "1F471-1F3FE",
+ "digest": "94da43f0b12ef4a98dabec096ff1184b0a9b5b6ee55824d257e5112cc7e88730"
+ },
+ {
+ "name": "person_with_blond_hair_tone5",
+ "unicode": "1F471-1F3FF",
+ "digest": "9e096a210ea720d32bc6a7005cd77f8b314ccf817fc3060da2e1796de39e9d60"
+ },
+ {
+ "name": "person_with_pouting_face",
+ "unicode": "1F64E",
+ "digest": "8c3199a422250d2db9a163156191ed2c6697d7f31699e2efe19e05ca26e5d225"
+ },
+ {
+ "name": "person_with_pouting_face_tone1",
+ "unicode": "1F64E-1F3FB",
+ "digest": "3e1f09bbf607381c992739ea92dd35cbd26b1bbc705a7d21b7c3156f50e9d8b3"
+ },
+ {
+ "name": "person_with_pouting_face_tone2",
+ "unicode": "1F64E-1F3FC",
+ "digest": "b5fc1cf3fdc5ff01105ee2452db90baa6a52c1e42f3795b2836c3e35197ece1f"
+ },
+ {
+ "name": "person_with_pouting_face_tone3",
+ "unicode": "1F64E-1F3FD",
+ "digest": "e8ec2539c458a8283c8c1050634c432b6363f3e64b68ba4c977994782f09b564"
+ },
+ {
+ "name": "person_with_pouting_face_tone4",
+ "unicode": "1F64E-1F3FE",
+ "digest": "5cab7a29699decd45682583446c2bf56ddcd69cd16e14db661b526a4076dfa17"
+ },
+ {
+ "name": "person_with_pouting_face_tone5",
+ "unicode": "1F64E-1F3FF",
+ "digest": "3caebd3626fd77d849859d1c99a747f80a2b59bfa5c1854494f1ce0485539a94"
+ },
+ {
+ "name": "pick",
+ "unicode": "26CF",
+ "digest": "24a3e8f592435b97272e6d134ea5503dce3012811659c4aadbad4e45d9fba679"
+ },
+ {
+ "name": "pig",
+ "unicode": "1F437",
+ "digest": "50b55fc74e8f6c89c6e04609381c99a660748908f0ef015f5da37089678ad0c3"
+ },
+ {
+ "name": "pig2",
+ "unicode": "1F416",
+ "digest": "e8189fb678608e8b9d69e11d2566f9a4765cbdff99ec8e66df30c7a2dabf742f"
+ },
+ {
+ "name": "pig_nose",
+ "unicode": "1F43D",
+ "digest": "7e299cb49a771884f5065c68733a5a1fe354a54cff009127230177f1717af4a5"
+ },
+ {
+ "name": "pill",
+ "unicode": "1F48A",
+ "digest": "53ae3379cc6721744979122569f157a5a13aa6b48e081a89f17b2d90134efe9e"
+ },
+ {
+ "name": "pineapple",
+ "unicode": "1F34D",
+ "digest": "ceda8ffa4a41594f28a4e69d03f8a1daeb2ba20740f0b8c56447cae833eea035"
+ },
+ {
+ "name": "ping_pong",
+ "unicode": "1F3D3",
+ "digest": "dd2a84716c93410a285ff759bfbc2dc31a10f90b203c7a657b908e5949e89a39"
+ },
+ {
+ "name": "piracy",
+ "unicode": "1F572",
+ "digest": "f42955ba75c598392e5e258be49968d858c876e0d6e7aa9dc795f7e8cff42be9"
+ },
+ {
+ "name": "pisces",
+ "unicode": "2653",
+ "digest": "75f11b9a094196b54a242420362fa7c0aeba7cfc497b187e1aaaba96d93684a7"
+ },
+ {
+ "name": "pizza",
+ "unicode": "1F355",
+ "digest": "ac94ae1c034f7b854ce2a483e1c219d101a84336f5065342f4824ff32ba705c4"
+ },
+ {
+ "name": "place_of_worship",
+ "unicode": "1F6D0",
+ "digest": "4fabc307b7e35f94288f6d53985485662a4814b11a9a382f0a3873d41b1290d3"
+ },
+ {
+ "name": "play_pause",
+ "unicode": "23EF",
+ "digest": "d69e8cdec33447283cf65d343b986115e27681d781b721db7894e5c587ca18ad"
+ },
+ {
+ "name": "point_down",
+ "unicode": "1F447",
+ "digest": "685f46a643be7f3033896e59a822f87d61ce50db6969bcdbacc743215a96bb7a"
+ },
+ {
+ "name": "point_down_tone1",
+ "unicode": "1F447-1F3FB",
+ "digest": "d3dd2608fe17d5649c960fcf8dbdb68466908d80fa349b7947b457da2a27ebb1"
+ },
+ {
+ "name": "point_down_tone2",
+ "unicode": "1F447-1F3FC",
+ "digest": "67ab236a14f6d63abcdb26433a66a183d223186c21ebc9f978fab50165ebe271"
+ },
+ {
+ "name": "point_down_tone3",
+ "unicode": "1F447-1F3FD",
+ "digest": "c8a2368f2cedb5bbb5cc0195b97fbf3787747637bf6e77bdc9a4edf4a3f22a04"
+ },
+ {
+ "name": "point_down_tone4",
+ "unicode": "1F447-1F3FE",
+ "digest": "6a92eab3bc8f950fa423e690f54a352887bda92f01e91c62eb3f3a9544c10cd8"
+ },
+ {
+ "name": "point_down_tone5",
+ "unicode": "1F447-1F3FF",
+ "digest": "6ad329f156414f421d6f8cf5e2a68d34b7a41f90d80e8e66b15bcbd3788126c7"
+ },
+ {
+ "name": "point_left",
+ "unicode": "1F448",
+ "digest": "cb520d6bba4c2b3bd7911315c9efce3261d048ff090437d7e24c9c5a7255043e"
+ },
+ {
+ "name": "point_left_tone1",
+ "unicode": "1F448-1F3FB",
+ "digest": "81813901bdaa8d261277f79aff9e9a21beb80a5855899941820b25f70786ec21"
+ },
+ {
+ "name": "point_left_tone2",
+ "unicode": "1F448-1F3FC",
+ "digest": "ecdc3dea0d644290aa7e0dab758c215822482a482ba35d825a33152453593c1e"
+ },
+ {
+ "name": "point_left_tone3",
+ "unicode": "1F448-1F3FD",
+ "digest": "84e73b6a37755016271c255eba164f349dbd2a2badf5d9ac1c6f4cbfcae589f0"
+ },
+ {
+ "name": "point_left_tone4",
+ "unicode": "1F448-1F3FE",
+ "digest": "d16800499b6c6ede94256796b1de8a8f723879f636849856b3bd8b7a092b5576"
+ },
+ {
+ "name": "point_left_tone5",
+ "unicode": "1F448-1F3FF",
+ "digest": "18b7108066cebf2d4090f29e595a2f01db94bd210f3b1d61dc269ec249a749b9"
+ },
+ {
+ "name": "point_right",
+ "unicode": "1F449",
+ "digest": "866180bf31e92de32aba336d5b5ce81773a29cdaadada1d93c944cf9ad9783bc"
+ },
+ {
+ "name": "point_right_tone1",
+ "unicode": "1F449-1F3FB",
+ "digest": "ebe2e4bf6bd46a5798b9a845a4ed055911c4fe58dbeacc4d39d6ea63e28e7cc9"
+ },
+ {
+ "name": "point_right_tone2",
+ "unicode": "1F449-1F3FC",
+ "digest": "b638662a67b1c6adde4f5abc789aae010b178404cdd1b71fcc982cdf8307c655"
+ },
+ {
+ "name": "point_right_tone3",
+ "unicode": "1F449-1F3FD",
+ "digest": "32c6ca2f992416ab2c36672dfbc1c0de8f102c77a13496dd8d63736a7b0261d2"
+ },
+ {
+ "name": "point_right_tone4",
+ "unicode": "1F449-1F3FE",
+ "digest": "89bd6828e9b82408a3829d49fa43332e2599f7d10bc6e5b14b750ef03267b173"
+ },
+ {
+ "name": "point_right_tone5",
+ "unicode": "1F449-1F3FF",
+ "digest": "390525048a12b0efa22de550c800e439b0deaad03f1f31155d179aef093354af"
+ },
+ {
+ "name": "point_up",
+ "unicode": "261D",
+ "digest": "31b5ca1303c1afabe1db322b24f73b23f3568c87a364f61c82f6e0c858c090e9"
+ },
+ {
+ "name": "point_up_2",
+ "unicode": "1F446",
+ "digest": "55c237054aa347c9847f3f3f577eb755db55dfcf793aa7de0f8f868574d70e8f"
+ },
+ {
+ "name": "point_up_2_tone1",
+ "unicode": "1F446-1F3FB",
+ "digest": "dc07e7732d973de96ae3b08b14c19e20b6c1aea7f5a30e7198679b750422e914"
+ },
+ {
+ "name": "point_up_2_tone2",
+ "unicode": "1F446-1F3FC",
+ "digest": "af2211fc4a1bd51d1e76f7bc43a6fa87bdd24e4295c52fdbdb01c1ca670a6cd7"
+ },
+ {
+ "name": "point_up_2_tone3",
+ "unicode": "1F446-1F3FD",
+ "digest": "917701169b3fb3e1b6e14a68e9572b25998ef2e38abac9ad8cf30100f8ea0dac"
+ },
+ {
+ "name": "point_up_2_tone4",
+ "unicode": "1F446-1F3FE",
+ "digest": "20843904764c6c3e55792cce0c55c76f72b97788c5229cad655ebf1f2873b439"
+ },
+ {
+ "name": "point_up_2_tone5",
+ "unicode": "1F446-1F3FF",
+ "digest": "1d0cca546027c717da50f90da65757af46fe7cd4e397da9b8e203446f707208d"
+ },
+ {
+ "name": "point_up_tone1",
+ "unicode": "261D-1F3FB",
+ "digest": "5ede60379dee23166c6b834d73da8b55268e330f67058843b8a3705dca6ed71a"
+ },
+ {
+ "name": "point_up_tone2",
+ "unicode": "261D-1F3FC",
+ "digest": "c94a15ef848d410aa5d32b8d0e453b59682fde6f39e6705cbb81cf0829833a81"
+ },
+ {
+ "name": "point_up_tone3",
+ "unicode": "261D-1F3FD",
+ "digest": "d319ce72876d97a3b1d4bc7c0679e546a983f02145d723a0da5ed0b73a51cfe7"
+ },
+ {
+ "name": "point_up_tone4",
+ "unicode": "261D-1F3FE",
+ "digest": "9171a27f86f27fd144347a17153fb56e30bd32e67a8f10f8c1f32a40cad4e009"
+ },
+ {
+ "name": "point_up_tone5",
+ "unicode": "261D-1F3FF",
+ "digest": "a894f87da4c3d33d5e6e74d003a33ec60c453db6507fe05d22235f807ead27d6"
+ },
+ {
+ "name": "police_car",
+ "unicode": "1F693",
+ "digest": "7999869cb75be404fc34942b6f9d8e84fa7e259aa892a1e8e1652a5f02cceea6"
+ },
+ {
+ "name": "poodle",
+ "unicode": "1F429",
+ "digest": "8a568d8688bf19b440b7c1b49fcfe6672b8f75af0031d89ab6212623430acadb"
+ },
+ {
+ "name": "poop",
+ "unicode": "1F4A9",
+ "digest": "140ce75a015ede5e764873e0ae9a56e7b2af333eddca0fe2796b14545c620258"
+ },
+ {
+ "name": "popcorn",
+ "unicode": "1F37F",
+ "digest": "12264cb16fca9317e3ba8d5924a2c8f15f790e36d2f29e7b12aaaf77e1beb73d"
+ },
+ {
+ "name": "post_office",
+ "unicode": "1F3E3",
+ "digest": "5e2d896cd646a2eecd5596af9e44ca1fa2745de5cedaf0f6d193b8243201c6cc"
+ },
+ {
+ "name": "postal_horn",
+ "unicode": "1F4EF",
+ "digest": "339aa61fa1567a1d159bb8204d15db889fbb6cc1106f6e1991b4a184d1bc1fc7"
+ },
+ {
+ "name": "postbox",
+ "unicode": "1F4EE",
+ "digest": "ef1a6543fccb9f1009cc3782c51883e51167721a0b49e8ba21e8e6049b216906"
+ },
+ {
+ "name": "potable_water",
+ "unicode": "1F6B0",
+ "digest": "4a2379835660dfa8b6780d662a10d1effab710f471eb9b5e6ade4772ba7e5aeb"
+ },
+ {
+ "name": "pouch",
+ "unicode": "1F45D",
+ "digest": "cbd47ec1a65f5c642773d8ea2e7e57f7041a2d7ed9df05fbdd7bc8743c6dece6"
+ },
+ {
+ "name": "poultry_leg",
+ "unicode": "1F357",
+ "digest": "d416e9464bd58073bd3e32eb06c0da96905609f47b9d667acdc0810e94237584"
+ },
+ {
+ "name": "pound",
+ "unicode": "1F4B7",
+ "digest": "1ac491bb8a91613b2b1faaac4e7b4bc794d2abef69ac79de17d54c824c3ef826"
+ },
+ {
+ "name": "pouting_cat",
+ "unicode": "1F63E",
+ "digest": "ba28d75401d5bb98773acd35aaf173356bae4d5a5520a226559478138364ebdf"
+ },
+ {
+ "name": "pray",
+ "unicode": "1F64F",
+ "digest": "fb0df9c1566014bd2df2a1afd59366b896f20c03ca3516e02e4be44ea556c8ea"
+ },
+ {
+ "name": "pray_tone1",
+ "unicode": "1F64F-1F3FB",
+ "digest": "c6d8cb46e65ad13a92e85f97e018176fd89513f23e899e15d1ad09e3b4009f4b"
+ },
+ {
+ "name": "pray_tone2",
+ "unicode": "1F64F-1F3FC",
+ "digest": "2cd68cbe1ba3254f173ec8136af79cae64873bd0f20480158c3e6babd5a1a442"
+ },
+ {
+ "name": "pray_tone3",
+ "unicode": "1F64F-1F3FD",
+ "digest": "d2e81863f74a87b96335fb108e7b206f28ed18185362ab4d42a3b0523801398b"
+ },
+ {
+ "name": "pray_tone4",
+ "unicode": "1F64F-1F3FE",
+ "digest": "ad1b91254b101d872325c325ebd1f2a6257cfe22e83de88e29dd16ffac191979"
+ },
+ {
+ "name": "pray_tone5",
+ "unicode": "1F64F-1F3FF",
+ "digest": "23f40a11321decbdc6a1d274b9ad571041d261d364d13d1063c306e73ad52254"
+ },
+ {
+ "name": "prayer_beads",
+ "unicode": "1F4FF",
+ "digest": "cb6f8700154f75749cf2642a25c03e255dc18428baf8b57f6bd807c92b83e28d"
+ },
+ {
+ "name": "princess",
+ "unicode": "1F478",
+ "digest": "47b93eb52d757c3c000d9760391ecb942776d883b28050d833fa11612483d8ee"
+ },
+ {
+ "name": "princess_tone1",
+ "unicode": "1F478-1F3FB",
+ "digest": "1e4073c2abdf51a61a1a85a3e063541fe96e9b9ec36ec6f7fb9c98deeb230869"
+ },
+ {
+ "name": "princess_tone2",
+ "unicode": "1F478-1F3FC",
+ "digest": "6a0a5dc447cd887798f908c15972e7a12d28d81f168b92bcb105786ac253bea0"
+ },
+ {
+ "name": "princess_tone3",
+ "unicode": "1F478-1F3FD",
+ "digest": "2f08d22fdfc7a7d66fcd87ae716b811f43077f5bb17fef87f5b7e2aa93700d70"
+ },
+ {
+ "name": "princess_tone4",
+ "unicode": "1F478-1F3FE",
+ "digest": "02129211bf7bf7ff6de35913b7069aee151532d878b8c4f7e24c012e5b09d4b4"
+ },
+ {
+ "name": "princess_tone5",
+ "unicode": "1F478-1F3FF",
+ "digest": "d676f103600b69dbfdb469469a77b9d561ec460ff862befa58ab30ddc909c9f7"
+ },
+ {
+ "name": "printer",
+ "unicode": "1F5A8",
+ "digest": "c44402c87071f8d31d3997abab53ab9f8f7c11434e747380928814ceb6b0a417"
+ },
+ {
+ "name": "prohibited",
+ "unicode": "1F6C7",
+ "digest": "bc6cdea2269a0ec39576d98dc4cda2bd9efa4dc330dde870148c6a85ad9cc63f"
+ },
+ {
+ "name": "projector",
+ "unicode": "1F4FD",
+ "digest": "fc361282f367926254c08150b02cb8fda7fa8d2c9c939d9360c78bf19a4f982e"
+ },
+ {
+ "name": "punch",
+ "unicode": "1F44A",
+ "digest": "5759db1d7093744c74b840bbb4761fb025d6633f8fa539bcb35dcf54fc05ceb6"
+ },
+ {
+ "name": "punch_tone1",
+ "unicode": "1F44A-1F3FB",
+ "digest": "793b3fa2a43c23b2c1e1b48b86ae35e8c4024cd065fac0a0a5ada87cb78d6de3"
+ },
+ {
+ "name": "punch_tone2",
+ "unicode": "1F44A-1F3FC",
+ "digest": "6fc2467e99982ab00b0c352c6f7793d34faf17b16a0312082c9bd1f0709e3938"
+ },
+ {
+ "name": "punch_tone3",
+ "unicode": "1F44A-1F3FD",
+ "digest": "bf747b29952550c5b4d3807b9ed85b5e5d4bcc3265b0e214791f7db547f861fb"
+ },
+ {
+ "name": "punch_tone4",
+ "unicode": "1F44A-1F3FE",
+ "digest": "3b6c0ccb682552f32d6744c438e3af04a1732c67a74bcafb14c723cf526fed87"
+ },
+ {
+ "name": "punch_tone5",
+ "unicode": "1F44A-1F3FF",
+ "digest": "945bae1aa3587cd1dc57d1ec4da18c67a59e0e7150dcc8735e5357b4ea1234ac"
+ },
+ {
+ "name": "purple_heart",
+ "unicode": "1F49C",
+ "digest": "e0eb886e74f22d40d059ff3a089d472af53c6c53de380f428cca140dfd046345"
+ },
+ {
+ "name": "purse",
+ "unicode": "1F45B",
+ "digest": "67d82ff9a4d76148b9d98538d4b786f880058a556e650ec3f93e1632aa42aaa7"
+ },
+ {
+ "name": "pushpin",
+ "unicode": "1F4CC",
+ "digest": "c4de129d5d8744caffeb2f499fcc0bc6b551843938f8166ffecd0de00bda66e3"
+ },
+ {
+ "name": "pushpin_black",
+ "unicode": "1F588",
+ "digest": "80ebac74edb9e8e1f8a219b32a676d318ed73b359cd8193b91b493d775307f63"
+ },
+ {
+ "name": "put_litter_in_its_place",
+ "unicode": "1F6AE",
+ "digest": "b26d3b68bd62d30ecfe75cfaf309a7a0f91e92db0aa18b0b97b97baf0609d4e6"
+ },
+ {
+ "name": "question",
+ "unicode": "2753",
+ "digest": "258e3169bae177fb0f01ed5f9b933f7f02dd2673e12a316af44a0c3729a78a2c"
+ },
+ {
+ "name": "rabbit",
+ "unicode": "1F430",
+ "digest": "9817a7454aeda77d28f63eb13c0dc0a6d9e6c9abe3dcf538b4b3477e494cddb6"
+ },
+ {
+ "name": "rabbit2",
+ "unicode": "1F407",
+ "digest": "67ba57a31b0768a2118faabdcb088f96f1441e1132397f65b6937d523ff7dabb"
+ },
+ {
+ "name": "race_car",
+ "unicode": "1F3CE",
+ "digest": "2e9828e3884c79ad7e9e1173d3470790f3f56cfa08ef4e38deff45db0728c66c"
+ },
+ {
+ "name": "racehorse",
+ "unicode": "1F40E",
+ "digest": "36aa3c7123ee7e15600657166032b21b8edeb192cf6d3ada39b5c65001f7fc40"
+ },
+ {
+ "name": "radio",
+ "unicode": "1F4FB",
+ "digest": "b1403f9a883405b909208f52c9474c2d3923681ea0b02609a6e9dc12460319a5"
+ },
+ {
+ "name": "radio_button",
+ "unicode": "1F518",
+ "digest": "9bcdac17b3620331a32f9bb876812231a701eb5a7f696e7d875f877ab92159fc"
+ },
+ {
+ "name": "radioactive",
+ "unicode": "2622",
+ "digest": "5ad8e8594617c0153672a76421deb836e05c6098020c33af3f975f8fcfe216e4"
+ },
+ {
+ "name": "rage",
+ "unicode": "1F621",
+ "digest": "02ac70551fc51478884c133b29539cae58b463c760db38c0aeec1bdf5b282312"
+ },
+ {
+ "name": "railway_car",
+ "unicode": "1F683",
+ "digest": "8490e2ecf94c7c1d1e22fea0d80cc18a49648741009e51984f583b17bbd022e2"
+ },
+ {
+ "name": "railway_track",
+ "unicode": "1F6E4",
+ "digest": "63ee881cc775d5b2711082b6c96ab44d5204c5d390afd6d8ee97e52aeeaa5e5e"
+ },
+ {
+ "name": "rainbow",
+ "unicode": "1F308",
+ "digest": "bbd8ecc8d0737948969a3539d2d202e599404e509f1a21bdbb0a0c41c2540522"
+ },
+ {
+ "name": "raised_hand",
+ "unicode": "270B",
+ "digest": "4192881a0d613b4fcb19b1c2d8b83aadee6f0b12170721c8dd7b1ccef6540199"
+ },
+ {
+ "name": "raised_hand_tone1",
+ "unicode": "270B-1F3FB",
+ "digest": "df2e046c99dceb9184c50a777b403d72bfb25ff473d6a4e20bb9a731db64ed8d"
+ },
+ {
+ "name": "raised_hand_tone2",
+ "unicode": "270B-1F3FC",
+ "digest": "ed179299a1c397cd51cf6067d6795d71a3831d35e1ec9eacbf0286c8992c1e7a"
+ },
+ {
+ "name": "raised_hand_tone3",
+ "unicode": "270B-1F3FD",
+ "digest": "cacbd0ddef65bc01a41bd921ea159f8cd89050309b10f15780d6199f79434a54"
+ },
+ {
+ "name": "raised_hand_tone4",
+ "unicode": "270B-1F3FE",
+ "digest": "04c934c7a55b83bcfa7f3880fc1f6aa0f188090c37b9670e6775a512a1cf59e9"
+ },
+ {
+ "name": "raised_hand_tone5",
+ "unicode": "270B-1F3FF",
+ "digest": "da0c4283b7b19861237c023234c6db28045b8f5a5971acb015733e08e2940e86"
+ },
+ {
+ "name": "raised_hands",
+ "unicode": "1F64C",
+ "digest": "308e475f38558e73bd66e28693d77478caa5bca4360cffaffc2a97b5858c56ba"
+ },
+ {
+ "name": "raised_hands_tone1",
+ "unicode": "1F64C-1F3FB",
+ "digest": "e39b9bc49dccc127e44f543e98961fcf5bcd44d6e216741bcd10ec3667263c84"
+ },
+ {
+ "name": "raised_hands_tone2",
+ "unicode": "1F64C-1F3FC",
+ "digest": "f376ab13071ffdc11888ec221ef5b4de546ca0f60bd9ae30bf3da4066c220462"
+ },
+ {
+ "name": "raised_hands_tone3",
+ "unicode": "1F64C-1F3FD",
+ "digest": "67694325a43e629c00fa9bd2ff7e19f84f216b2855ae2cf097762dfa7aca25e6"
+ },
+ {
+ "name": "raised_hands_tone4",
+ "unicode": "1F64C-1F3FE",
+ "digest": "a2254fe75a0770708916a4ddd5db4420221c6ea9db9f74068d14eadfc0f3772c"
+ },
+ {
+ "name": "raised_hands_tone5",
+ "unicode": "1F64C-1F3FF",
+ "digest": "bd7c9897cefb454ccdc46027bf56d6587565bdd345d7d0f081b7b671a53f6c99"
+ },
+ {
+ "name": "raising_hand",
+ "unicode": "1F64B",
+ "digest": "d57178fc77e9fa140682634da35f9ab12a65d9b4c506b7cd8a9697f1b5910bdb"
+ },
+ {
+ "name": "raising_hand_tone1",
+ "unicode": "1F64B-1F3FB",
+ "digest": "f46b34361ef79743f3187d6860182bbe1ae411031db7fe5c0f7292fa472b9c16"
+ },
+ {
+ "name": "raising_hand_tone2",
+ "unicode": "1F64B-1F3FC",
+ "digest": "20b85a2ebca150b2020a04b41d34884c78c22f42c251e2b9d23fd3724574143b"
+ },
+ {
+ "name": "raising_hand_tone3",
+ "unicode": "1F64B-1F3FD",
+ "digest": "5e0401b528c2b8edff766d39cdcedbe9abebe4c940df7a36ace61f59c08d508a"
+ },
+ {
+ "name": "raising_hand_tone4",
+ "unicode": "1F64B-1F3FE",
+ "digest": "e4f5624264269ad09cde207cd7d4eb0fd46de816880daeec457ac8cd51cc1b7b"
+ },
+ {
+ "name": "raising_hand_tone5",
+ "unicode": "1F64B-1F3FF",
+ "digest": "eb34b6c037bee5bbc4222f6aab421aa785f527ebf1b5e971769e5102244d60e1"
+ },
+ {
+ "name": "ram",
+ "unicode": "1F40F",
+ "digest": "b71950d7a286a4c4909c5ec7c35211c2a5c20b6bad341bd863c6a85c4bcf9c80"
+ },
+ {
+ "name": "ramen",
+ "unicode": "1F35C",
+ "digest": "7dd185b24852b577913edc78647cd53b27d42e225fde29aa2f3aba25c980b5c4"
+ },
+ {
+ "name": "rat",
+ "unicode": "1F400",
+ "digest": "7a10d9ba5ee1010d421d9cf73d7966507302a69617a32fe9f1a00d57a31f7bd7"
+ },
+ {
+ "name": "record_button",
+ "unicode": "23FA",
+ "digest": "c2ba672994ad0f99d7fdc449f3fee45a2dca68a58f9fe95825b38465a30ef44e"
+ },
+ {
+ "name": "recycle",
+ "unicode": "267B",
+ "digest": "74a54ed62a40dfbdcace1f08b085658a77d45c62570273927ad270bf9a8a2f4d"
+ },
+ {
+ "name": "red_car",
+ "unicode": "1F697",
+ "digest": "558730d6418aa5d85b73af58c8041efd12cff906e26ea47c50963f66d33d6eb8"
+ },
+ {
+ "name": "red_circle",
+ "unicode": "1F534",
+ "digest": "9dcf0132f6f2cc81702f0e3b15b37984e8439796705bf98f68ba449b3dfa5307"
+ },
+ {
+ "name": "registered",
+ "unicode": "00AE",
+ "digest": "ed924107384461aabb4924c401c6c087ffa047bc2ef735823e7c2be67804707c"
+ },
+ {
+ "name": "relaxed",
+ "unicode": "263A",
+ "digest": "65072f7b9bfaaa92b8a0ed012dffe2cfd2efa3748264aaf450aa31ba6bd44045"
+ },
+ {
+ "name": "relieved",
+ "unicode": "1F60C",
+ "digest": "1f2c7ae6a9d74a112de89403be6eca3d8155d70395e7fce51032fc961f235c7d"
+ },
+ {
+ "name": "reminder_ribbon",
+ "unicode": "1F397",
+ "digest": "e4a2afc7dce40589657f7043ba8acc9638fd4117252278233ea89f84cddad387"
+ },
+ {
+ "name": "repeat",
+ "unicode": "1F501",
+ "digest": "27b6dad9215e58e24c607a39dbf398ecf66ccb692c81e08eb2f5f4912db30522"
+ },
+ {
+ "name": "repeat_one",
+ "unicode": "1F502",
+ "digest": "052d13f2b08eaf70b31252aa78f95d06fbe22c58945c19381b13cbeb1c855651"
+ },
+ {
+ "name": "restroom",
+ "unicode": "1F6BB",
+ "digest": "b77fbc4247c241362e5ef9e6eb58b1b437aa9d16b65886cec0c55ceb55c1440e"
+ },
+ {
+ "name": "revolving_hearts",
+ "unicode": "1F49E",
+ "digest": "2b8925d3e78df2dba8534252fe60bf03285346f6b3697be7668bd568e6d85931"
+ },
+ {
+ "name": "rewind",
+ "unicode": "23EA",
+ "digest": "91a95b26d12ca76111556096f4d96484c9f1d7e1b20ccff5a3291b36e529a6d1"
+ },
+ {
+ "name": "ribbon",
+ "unicode": "1F380",
+ "digest": "9c0296d8c2baa84c99347c431bf79b288d98b5f17b1ce7605ad7ce1da265d5aa"
+ },
+ {
+ "name": "rice",
+ "unicode": "1F35A",
+ "digest": "e34849496a79e71ae4700df94f2a54895bf6de758a92edeae33fe78295a3ba21"
+ },
+ {
+ "name": "rice_ball",
+ "unicode": "1F359",
+ "digest": "52df5da8b0edbdeb56d66e0f30ad4549abdd81c064f7269d920dcac66a3df2e4"
+ },
+ {
+ "name": "rice_cracker",
+ "unicode": "1F358",
+ "digest": "d55f8f9d807f4619eb243c510938067a7417a64bd9435b05dfeb2a36fdb2b6a0"
+ },
+ {
+ "name": "rice_scene",
+ "unicode": "1F391",
+ "digest": "482d854d8d30edfc1ecd48a4ce476e6498606321405bf5a0b4ff74489a092af8"
+ },
+ {
+ "name": "right_speaker",
+ "unicode": "1F568",
+ "digest": "d268bb84be863c0884620dfc6d2a764b0c7466d2f9810549b138e21ac70add4e"
+ },
+ {
+ "name": "right_speaker_one",
+ "unicode": "1F569",
+ "digest": "5b92daa87bdf6ee15e798bec382a2ee885f4e6e77a68a3f626adcfe4c782b375"
+ },
+ {
+ "name": "right_speaker_three",
+ "unicode": "1F56A",
+ "digest": "4d00b720a65bd0f4c3682b290b1976ec2388d6ae61225398f4e70556ae9e5f80"
+ },
+ {
+ "name": "ring",
+ "unicode": "1F48D",
+ "digest": "ae2a93e7895b9b89f5a39f01d356ffed988f219ef8b658a56c55285826a4533b"
+ },
+ {
+ "name": "ringing_bell",
+ "unicode": "1F56D",
+ "digest": "d71ab7fa937fc4af507b5b07ea58a4f31e875d9e8304ef2b850d7cebe0e9cd66"
+ },
+ {
+ "name": "robot",
+ "unicode": "1F916",
+ "digest": "cc0e363774b86e21a5b2cea7f7af85bca9e92c124ebcd39c6067c125048baa60"
+ },
+ {
+ "name": "rocket",
+ "unicode": "1F680",
+ "digest": "65d8bd005ceac41904237b7a8c5f55f16713a55d971522f0bbe63a1d548e515d"
+ },
+ {
+ "name": "roller_coaster",
+ "unicode": "1F3A2",
+ "digest": "907baab1f3d7becf3f8a3b1264642b395bd73b4af49e23058b3abb5c69e9106a"
+ },
+ {
+ "name": "rolling_eyes",
+ "unicode": "1F644",
+ "digest": "f596f203030b6c9bd743848512aa3fc7919447020d35ae5c2bf13ccb16fa2dbe"
+ },
+ {
+ "name": "rooster",
+ "unicode": "1F413",
+ "digest": "6cefdaa45631ed8c9480e15f578c793d95af81b42687164fd7900eee325ccf07"
+ },
+ {
+ "name": "rose",
+ "unicode": "1F339",
+ "digest": "584909a4a2ece625c688f8479a39692bb8e816b692e6eb7dfd40cb045259b1b2"
+ },
+ {
+ "name": "rosette",
+ "unicode": "1F3F5",
+ "digest": "0ce3b85ca05124ab99d57ebc9aa17bb246ee614d2fcda1ef62bf42ac7e616148"
+ },
+ {
+ "name": "rosette_black",
+ "unicode": "1F3F6",
+ "digest": "ae8675891c88f9d98463d35178445950c39b0deb0f0e8b3f341228a6e0d0e477"
+ },
+ {
+ "name": "rotating_light",
+ "unicode": "1F6A8",
+ "digest": "369e069e0bfecc7413e75f4015e9c1de527a33c7cce3f6c2b4adb60a0d9d338c"
+ },
+ {
+ "name": "round_pushpin",
+ "unicode": "1F4CD",
+ "digest": "1bc5fe5a90a6e56ea00246f1b008a0e0cce0d77c226dc0300bf9a2804b543877"
+ },
+ {
+ "name": "rowboat",
+ "unicode": "1F6A3",
+ "digest": "c10e09bf8be8b1a8ef3113edd9327126d6a4644f3bc81c7ada2922851e4d1cfb"
+ },
+ {
+ "name": "rowboat_tone1",
+ "unicode": "1F6A3-1F3FB",
+ "digest": "a84fc1b30d1a284dcd3899dc4de8f11e7b65c258528eb41c7dbf8f82425fee12"
+ },
+ {
+ "name": "rowboat_tone2",
+ "unicode": "1F6A3-1F3FC",
+ "digest": "85f001430a2ad607a15901f7c2dcf8381471f42d6cc0775e76a2ff1f457151c1"
+ },
+ {
+ "name": "rowboat_tone3",
+ "unicode": "1F6A3-1F3FD",
+ "digest": "adf8b1e45a46a13f3db40c29df0312216558e9d0c615aa46a8e913cee5003a81"
+ },
+ {
+ "name": "rowboat_tone4",
+ "unicode": "1F6A3-1F3FE",
+ "digest": "05482749ec40bdf02e53fc42d316c51f4f3ed643f21e8fc16b81930e4a884bda"
+ },
+ {
+ "name": "rowboat_tone5",
+ "unicode": "1F6A3-1F3FF",
+ "digest": "d4bb337d948996d4a23d87f99988f02fc207815b862082ffd2eef5f0c1016aa9"
+ },
+ {
+ "name": "rugby_football",
+ "unicode": "1F3C9",
+ "digest": "e14aebbded78d4a5e9b4028f79a8ca840d02798c6758cb9e926e992e2a35a4f3"
+ },
+ {
+ "name": "runner",
+ "unicode": "1F3C3",
+ "digest": "58a884f06d37b0ce78197bebcd3f0e102dd90022ebd86ec70a2ef5a5cdf9683b"
+ },
+ {
+ "name": "runner_tone1",
+ "unicode": "1F3C3-1F3FB",
+ "digest": "65f1633d1517803de23686d2dbcc75a5787874266db4981138ccdbe4badc773c"
+ },
+ {
+ "name": "runner_tone2",
+ "unicode": "1F3C3-1F3FC",
+ "digest": "2bc81f3fb77445cdc75c34806ab0ce912bacfe47f63b5d2011a4f5d370cf7064"
+ },
+ {
+ "name": "runner_tone3",
+ "unicode": "1F3C3-1F3FD",
+ "digest": "beaf5f254cba2991fdd0c38ce2ddd1b4c1110e15b2b7bc026d32f162e295c4ef"
+ },
+ {
+ "name": "runner_tone4",
+ "unicode": "1F3C3-1F3FE",
+ "digest": "21d531ba9b3d13747ad636b8f7a6f184c974bf61d9f529975a64f9629263c407"
+ },
+ {
+ "name": "runner_tone5",
+ "unicode": "1F3C3-1F3FF",
+ "digest": "b02a5bcc58cc45f8219262ec44c77764172fd8f2624d9122ded4a5a5db04c0ed"
+ },
+ {
+ "name": "running_shirt_with_sash",
+ "unicode": "1F3BD",
+ "digest": "431bed35f4a55175bf99af769e74a81e8650c6ab34af6ecddaa1417ff7e437e6"
+ },
+ {
+ "name": "sa",
+ "unicode": "1F202",
+ "digest": "a47a480631f874e8a2cd69b5d513f90a1e81a96bfa2f6025bf244a82baca3656"
+ },
+ {
+ "name": "sagittarius",
+ "unicode": "2650",
+ "digest": "14871e6681c35e4a63a0b19613f77b3674d00cb78d06975e02ca29e61b5cea8c"
+ },
+ {
+ "name": "sailboat",
+ "unicode": "26F5",
+ "digest": "6f742dde6c180a174b771aa3942b558e98a3dc1eb212dd31add86c5fa5620865"
+ },
+ {
+ "name": "sake",
+ "unicode": "1F376",
+ "digest": "aa1392790c805950779dde7778292c937f8c1aaecb522876171d5ee542ec51f8"
+ },
+ {
+ "name": "sandal",
+ "unicode": "1F461",
+ "digest": "14f1e9003a6acd90a55f23c48ed87a758fca586f2e0b0edc4dc9d1deef9eb067"
+ },
+ {
+ "name": "santa",
+ "unicode": "1F385",
+ "digest": "12feddd84eb49ce30ae68d4f93d66e2c0dd11297a4d1275c9a50d4f35bea83a9"
+ },
+ {
+ "name": "santa_tone1",
+ "unicode": "1F385-1F3FB",
+ "digest": "a75813770efe27d5b4c80ad892d0c796d88d1a0dbb1bd02d5f68882d7abad479"
+ },
+ {
+ "name": "santa_tone2",
+ "unicode": "1F385-1F3FC",
+ "digest": "90f8072fdde5f4a275cbd1902d6c94689d453b1bee0336213dc9d6f7e1d038e1"
+ },
+ {
+ "name": "santa_tone3",
+ "unicode": "1F385-1F3FD",
+ "digest": "0973053e7b77d268080126a50b95b45429630e5d49f62210e7b71840794c7dc5"
+ },
+ {
+ "name": "santa_tone4",
+ "unicode": "1F385-1F3FE",
+ "digest": "5cd49c0d199a42846b400b3c1244d448ed6fe5ce993d379817cb2a5f7c0b609b"
+ },
+ {
+ "name": "santa_tone5",
+ "unicode": "1F385-1F3FF",
+ "digest": "a54c36dfa99b39549fb1d3dd7f0021a7aee28112960172ed466dacc67961c525"
+ },
+ {
+ "name": "satellite",
+ "unicode": "1F4E1",
+ "digest": "3b9797c8161526edce0bd8e9b8563055166f9307761c367ab3e2ad7645b6dee0"
+ },
+ {
+ "name": "satellite_orbital",
+ "unicode": "1F6F0",
+ "digest": "104b135e3736a4bcfd51a42dadb53bf3e00d7f85d77a94bcb86c6704fbfacd01"
+ },
+ {
+ "name": "saxophone",
+ "unicode": "1F3B7",
+ "digest": "1090da174ce8aa4f7d35025f65d5ac235e09310abde998d2a725ef3a989a2b75"
+ },
+ {
+ "name": "scales",
+ "unicode": "2696",
+ "digest": "b2984caa182b691a33650344708f47c61d6d319fd067760d7594c2ef60c1e27b"
+ },
+ {
+ "name": "school",
+ "unicode": "1F3EB",
+ "digest": "caf35260dc465a833521e4a0034201978fed41bbf72cd770756b3340c60e8a0c"
+ },
+ {
+ "name": "school_satchel",
+ "unicode": "1F392",
+ "digest": "a89a2cc46d24d57c2d6b95ed7a56ed829ae2f97b9e6201b2d5adc78c2b78518b"
+ },
+ {
+ "name": "scissors",
+ "unicode": "2702",
+ "digest": "a4e91127ac83acf5ebc64fbeca768cbbf24f2f0a484861c9c8104bee377b97ae"
+ },
+ {
+ "name": "scorpion",
+ "unicode": "1F982",
+ "digest": "a090a96731bc1171b054b51abec4c9b36faa62708fd51ac48277ccf5e55d9d12"
+ },
+ {
+ "name": "scorpius",
+ "unicode": "264F",
+ "digest": "1ad9bc1030a8f58f3f3223bac52c954cc7a0350805a9df7a42a26972c3b74728"
+ },
+ {
+ "name": "scream",
+ "unicode": "1F631",
+ "digest": "75d613786737ee9c0a74da7394b9ae190eacc7182164627ad8205ac64e4cc09a"
+ },
+ {
+ "name": "scream_cat",
+ "unicode": "1F640",
+ "digest": "eee04ff27c2c6b57d698cb87b0af8064ba8313ffc13aa090e38cd5aa8c3d2f76"
+ },
+ {
+ "name": "scroll",
+ "unicode": "1F4DC",
+ "digest": "b8205847649e3ce6b946f1d1da972ed015adde3841c62971b8169235f4b41c1f"
+ },
+ {
+ "name": "seat",
+ "unicode": "1F4BA",
+ "digest": "054c4db0bc8939e9dd951a3f73e9ae4b3c31652784f4d304b509c2bd32f98e31"
+ },
+ {
+ "name": "secret",
+ "unicode": "3299",
+ "digest": "77daef6e5c91d55228781ddec954a7089d1851297ec81daef6e813cd22915b5e"
+ },
+ {
+ "name": "see_no_evil",
+ "unicode": "1F648",
+ "digest": "aa5883fe605aeaa172d16640b8347580f9cb7d85a596da1b13955f27b0b79297"
+ },
+ {
+ "name": "seedling",
+ "unicode": "1F331",
+ "digest": "a75ec929402de1e653fd6bc89e5be2f92fe5fe52f39e4b6c290eae3c59172b56"
+ },
+ {
+ "name": "seven",
+ "unicode": "0037-20E3",
+ "digest": "c6a34020f6bb25871164fad44302a45c5bffced87f51dfbb816c2985ad7f6a1c"
+ },
+ {
+ "name": "shamrock",
+ "unicode": "2618",
+ "digest": "530e6b987ecb9bcbf0d6e0e11bd075e7949873c784da4f9e1e1b47efd37e5058"
+ },
+ {
+ "name": "shaved_ice",
+ "unicode": "1F367",
+ "digest": "fc22c3568f6be56771e83fd0e67b7eb3750041304d5d4979d3ec417f5201230e"
+ },
+ {
+ "name": "sheep",
+ "unicode": "1F411",
+ "digest": "3e3656b82784164ca02c5d775db7245260f0119d2c1d35ba552a6dc75ef02544"
+ },
+ {
+ "name": "shell",
+ "unicode": "1F41A",
+ "digest": "ff2f4f574b61bffd85c63bc2315c80d3cbcaba37a7c15a1f00783d312bd441d4"
+ },
+ {
+ "name": "shield",
+ "unicode": "1F6E1",
+ "digest": "062aec4a325da7b637c5710846c7e7319229be49b7e59f50428442a7ef725d60"
+ },
+ {
+ "name": "shinto_shrine",
+ "unicode": "26E9",
+ "digest": "9768fe94142a7dc169703d3707b203f285a546455e29fe2bbf185d44f160d6d0"
+ },
+ {
+ "name": "ship",
+ "unicode": "1F6A2",
+ "digest": "f8d5b0c8ec66287b732d9171ac1913be02efb656de11501213a207d8a6c801e1"
+ },
+ {
+ "name": "shirt",
+ "unicode": "1F455",
+ "digest": "e2e72c323f3bfaea02e8cf52201aa144dc56ec0f25ec97d5f04ee6c2ee99104e"
+ },
+ {
+ "name": "shopping_bags",
+ "unicode": "1F6CD",
+ "digest": "0194ba540c47e4fc6403be2df68f785d56810efc2dc011dfbf700f3778cb704a"
+ },
+ {
+ "name": "shower",
+ "unicode": "1F6BF",
+ "digest": "c945120182392510348de9a957c2b77a4645d118691298a2ad660dafa62a859c"
+ },
+ {
+ "name": "signal_strength",
+ "unicode": "1F4F6",
+ "digest": "7876ed9d602e1be746ca0629f072d85668d1f9715e9135745e803bdf89819a3c"
+ },
+ {
+ "name": "six",
+ "unicode": "0036-20E3",
+ "digest": "b409f23b73e46393c7a814442816b5880c38ef12a7feb5505e71276c195e8ca9"
+ },
+ {
+ "name": "six_pointed_star",
+ "unicode": "1F52F",
+ "digest": "4bc294dcbf4185250873b52b2fb5453fb7d80df912db929add6e4b7efc066363"
+ },
+ {
+ "name": "ski",
+ "unicode": "1F3BF",
+ "digest": "7ee81a2e2f7ff4e32dbf3d64b034e7542ec0c86d32e25eb125052e674943d75f"
+ },
+ {
+ "name": "skier",
+ "unicode": "26F7",
+ "digest": "49df9a4206ae0c7c2dbfc8a8b13fd3e14e6f7e750bd5a8581ab6a1626d4c165e"
+ },
+ {
+ "name": "skull",
+ "unicode": "1F480",
+ "digest": "dfd169764b192ac7c6e5101277dd9f1e010e86bdd32ad37e00ed4499fc0a5dd6"
+ },
+ {
+ "name": "skull_crossbones",
+ "unicode": "2620",
+ "digest": "e2acf0f36b6a6800c1829a1c6551b5d0eb6dcdef4b7f02070cf69570aeab608c"
+ },
+ {
+ "name": "sleeping",
+ "unicode": "1F634",
+ "digest": "4ead95079b1a542eedd0e5a0e93fddb318a002bdaffaa2fe5d8d7f20bf8143ed"
+ },
+ {
+ "name": "sleeping_accommodation",
+ "unicode": "1F6CC",
+ "digest": "10ee8cd925a75d7977b7cf004e08b5a8147b509ee4281e879a8b57c4a7c2cb04"
+ },
+ {
+ "name": "sleepy",
+ "unicode": "1F62A",
+ "digest": "dea3b246bb8af1b28e200358e3d5d59c8bba1813f35a7f4a57ec568ef43591db"
+ },
+ {
+ "name": "slight_frown",
+ "unicode": "1F641",
+ "digest": "3ae82b38b58ffa50eddebd87153428d880ca181f4f4178a9ca3bd813ea15ccbc"
+ },
+ {
+ "name": "slight_smile",
+ "unicode": "1F642",
+ "digest": "5eee09f634a4e2031927d008a6530a258a00e611ead0c386dd5b7ebb5e75a306"
+ },
+ {
+ "name": "slot_machine",
+ "unicode": "1F3B0",
+ "digest": "9d516b389299431b608c89d3f02ac68d28cb8df2a780f2048923bbcfbb49f416"
+ },
+ {
+ "name": "small_blue_diamond",
+ "unicode": "1F539",
+ "digest": "97389e82755dc43015089dee635072357ec347f0117b2d3e9b006c46514948ee"
+ },
+ {
+ "name": "small_orange_diamond",
+ "unicode": "1F538",
+ "digest": "67442d3b707501b7768f606115688373d13617ecf0b3b03ace0f1a6d38f66ddf"
+ },
+ {
+ "name": "small_red_triangle",
+ "unicode": "1F53A",
+ "digest": "e0a556a3dd5bbf0290ed7c00eb6f6307dc2ea98d1fb3111fd85a7f46242a3638"
+ },
+ {
+ "name": "small_red_triangle_down",
+ "unicode": "1F53B",
+ "digest": "7a11dcb8a517df220493d471759e4f4bca0db3769e2d942bbf596a88a3e57f72"
+ },
+ {
+ "name": "smile",
+ "unicode": "1F604",
+ "digest": "46a7c3545b0038dfce6825d97544f6665f28512ad05c404d668e32ac599c7ecb"
+ },
+ {
+ "name": "smile_cat",
+ "unicode": "1F638",
+ "digest": "c1db961f0fa261532b842816aca7ea7f6d8b461c7e930a1a1c91f96efd9db515"
+ },
+ {
+ "name": "smiley",
+ "unicode": "1F603",
+ "digest": "deeaaee64ebdd9fc0bcb719db75c3f7e0c33ddbcc97f6cd51f9f84377a4368ce"
+ },
+ {
+ "name": "smiley_cat",
+ "unicode": "1F63A",
+ "digest": "85ad852cb3881c4b754af172fdfc6231af42578033ea9f2981ceae944c41e72f"
+ },
+ {
+ "name": "smiling_imp",
+ "unicode": "1F608",
+ "digest": "e777bdf186d89921df106d23bf002967b69afffd7e981b3cbb19f89630a06e87"
+ },
+ {
+ "name": "smirk",
+ "unicode": "1F60F",
+ "digest": "2e7fddd8bed33ef4b7d8c13320302b87a28203e576ef87bd43716952cf0b5ace"
+ },
+ {
+ "name": "smirk_cat",
+ "unicode": "1F63C",
+ "digest": "9ca0721f4c18592b4b809ade8f716b95fa30cd31dd87d1e41db29a319becd705"
+ },
+ {
+ "name": "smoking",
+ "unicode": "1F6AC",
+ "digest": "3d14b3f0c57eb7a6a31ff371b0a454986533b79dbbeac78a76e4063478911b8d"
+ },
+ {
+ "name": "snail",
+ "unicode": "1F40C",
+ "digest": "57d946c7ec84dfad71bc4f7a042927ec5712aef50c66d21af892b6c8a7faf5e1"
+ },
+ {
+ "name": "snake",
+ "unicode": "1F40D",
+ "digest": "d084da540162288721364992f3b8059cbf2efd9f5b48f49a196ddbe23a073870"
+ },
+ {
+ "name": "snowboarder",
+ "unicode": "1F3C2",
+ "digest": "de9e1767526de606f4908743af94cc17e89fdb0a2a44167d3d021ef09d033ab9"
+ },
+ {
+ "name": "snowflake",
+ "unicode": "2744",
+ "digest": "e476863ccd7d7b549c6191fb25c121c6a467b4baef4683b7dc3e0a793c2e5d76"
+ },
+ {
+ "name": "snowman",
+ "unicode": "26C4",
+ "digest": "792946b8446f2243d11b89d07c73a774be3abd36573f3918640b1ba8714270b5"
+ },
+ {
+ "name": "snowman2",
+ "unicode": "2603",
+ "digest": "571acabaa4d55782c4529b762423a7e34cb1fb6bb7852cbd013e2e846d8311d1"
+ },
+ {
+ "name": "sob",
+ "unicode": "1F62D",
+ "digest": "562f02ab584bcbcf9ba73cf7fa7d7129965266abd28db2c73913b8c42f2f5aca"
+ },
+ {
+ "name": "soccer",
+ "unicode": "26BD",
+ "digest": "5fd0d534659b63dc862c65a80561b255bece0b76708fe8ecbae8e01b08d8cad0"
+ },
+ {
+ "name": "soon",
+ "unicode": "1F51C",
+ "digest": "d2a1ab16a4056d80c827ea23f9332bb73235fc841b857cbf545062ff8aeed81d"
+ },
+ {
+ "name": "sos",
+ "unicode": "1F198",
+ "digest": "fadfe8337e133a6f05d205d0807f288e5c230db04cb09f3547ce0cb73cfcf48a"
+ },
+ {
+ "name": "sound",
+ "unicode": "1F509",
+ "digest": "c0074b338fd461f1f9d1143b7f9b3781ddb3fd501ea79b2410630433a8e87b83"
+ },
+ {
+ "name": "space_invader",
+ "unicode": "1F47E",
+ "digest": "d264390004bd28d664dfda0069104be6db32ce477e23a95ac595bac2e29fd4e7"
+ },
+ {
+ "name": "spades",
+ "unicode": "2660",
+ "digest": "d1ad99a4fc20dfea881a9062a9f2109e483dbb5dea3b29e9653cb27ec57b4800"
+ },
+ {
+ "name": "spaghetti",
+ "unicode": "1F35D",
+ "digest": "ac63f9ad143e236ce6068098e5330a333ade9cddfb3dd6b1457ea47ce9dcf7e9"
+ },
+ {
+ "name": "sparkle",
+ "unicode": "2747",
+ "digest": "95b8f4f1bb6080cd1d7bd333c4724dbba43ed196dce72a2bbaab46c4a1bc0e48"
+ },
+ {
+ "name": "sparkler",
+ "unicode": "1F387",
+ "digest": "3a296e4d0081ad1a566e111d218e352e1439bba9fd04e8a1eb9a8e36bd438cb7"
+ },
+ {
+ "name": "sparkles",
+ "unicode": "2728",
+ "digest": "5ab280ea10c30e0e0b5a26ef52b8f47ad44a983330f7ef62ac0c0888752bbdb6"
+ },
+ {
+ "name": "sparkling_heart",
+ "unicode": "1F496",
+ "digest": "f145dab6b597c07e5a851176fabaf56dd857209645483d1acc1490d12c969113"
+ },
+ {
+ "name": "speak_no_evil",
+ "unicode": "1F64A",
+ "digest": "6eae2d066d39c4ba81e58a8327ed875c68bc9b1297c18dc0f5243e477a81040f"
+ },
+ {
+ "name": "speaker",
+ "unicode": "1F508",
+ "digest": "ea59c5a9d994808ff7937c300303e644b5f1ad41097e82f9e73ea6e1c718936c"
+ },
+ {
+ "name": "speaking_head",
+ "unicode": "1F5E3",
+ "digest": "d92cfe1200887300b2f05f9576448a2f2a79d0accd51f323a65ce3db0aa5639b"
+ },
+ {
+ "name": "speech_balloon",
+ "unicode": "1F4AC",
+ "digest": "5dccfda46fc984583bc9eaece66e7e884f2a9eb12a69dbd3493035e3c862edd0"
+ },
+ {
+ "name": "speech_left",
+ "unicode": "1F5E8",
+ "digest": "478b0b07460a9f54b7d0050f886da59fde5e428daa11e899fc31477fda1707ed"
+ },
+ {
+ "name": "speech_right",
+ "unicode": "1F5E9",
+ "digest": "8439b13779163c15e678a78b08ebeeb7d131632df21d2a7868de7fed38ca9d8a"
+ },
+ {
+ "name": "speech_three",
+ "unicode": "1F5EB",
+ "digest": "55a934f3659b6e75fdce0d0c4e2ea56dd34a43892c85a6666bd1882a0bfb92a9"
+ },
+ {
+ "name": "speech_two",
+ "unicode": "1F5EA",
+ "digest": "0563ef0591da243673cf877462acc5d8e1d980a56e81668ac627de74d0c33983"
+ },
+ {
+ "name": "speedboat",
+ "unicode": "1F6A4",
+ "digest": "553a288ab8eeb3dee7b9d1c92eba38016caef7658beaa828136ba1d6ba8ed08a"
+ },
+ {
+ "name": "spider",
+ "unicode": "1F577",
+ "digest": "519f7243b5574102ce3f8953e5480812830a1feb32ae51e8573724c864338481"
+ },
+ {
+ "name": "spider_web",
+ "unicode": "1F578",
+ "digest": "42959fae08a2162d6ee8c8706f823c5932f3801bc90da30d2ca9a48c3ff25572"
+ },
+ {
+ "name": "spy",
+ "unicode": "1F575",
+ "digest": "eaa570a36d83119d0a596228e74affe84d7355714ff6901d88a89410d26dec2a"
+ },
+ {
+ "name": "spy_tone1",
+ "unicode": "1F575-1F3FB",
+ "digest": "abdc066d4cad6a17047faf7806c45feb43ae1e2056cf500536f08f4173dbfa94"
+ },
+ {
+ "name": "spy_tone2",
+ "unicode": "1F575-1F3FC",
+ "digest": "72a3313ef12364105e764cc3deabd47eb6bd086f261c435682ae1cd29dc8230b"
+ },
+ {
+ "name": "spy_tone3",
+ "unicode": "1F575-1F3FD",
+ "digest": "2a1108d3d2e778f88aa5b3ae36705c877b84d0bf6b421409582ba748aeb2aee7"
+ },
+ {
+ "name": "spy_tone4",
+ "unicode": "1F575-1F3FE",
+ "digest": "1d4fe62912384bc0d687bcf4565752caf0ed6146c903a156d1c6ba6ea239b154"
+ },
+ {
+ "name": "spy_tone5",
+ "unicode": "1F575-1F3FF",
+ "digest": "69c1baac73783edb9e2d0c951f922dc7dddac34d0a9c818fee8d1021bc17db0d"
+ },
+ {
+ "name": "stadium",
+ "unicode": "1F3DF",
+ "digest": "4356db5d2cdef8c40830638debaf1f50831130c12ae8d8dc3d9a6bd28fdaa1f7"
+ },
+ {
+ "name": "star",
+ "unicode": "2B50",
+ "digest": "13240b8fada84e7555892996e9f9652503bf9b9a002056c2bae428d543abe2da"
+ },
+ {
+ "name": "star2",
+ "unicode": "1F31F",
+ "digest": "9b56c7548f6a222499d4e848576ea25eab837db72b207ebf8a62a451b35f758f"
+ },
+ {
+ "name": "star_and_crescent",
+ "unicode": "262A",
+ "digest": "10b8a0771e415aa6610fa62185137aa1836c2bb3e82f1a3f601470e94f784923"
+ },
+ {
+ "name": "star_of_david",
+ "unicode": "2721",
+ "digest": "5bc4d1038b8316281e01a9c575ded7ede0fc24c7593db5b5d36ca2e188aa5614"
+ },
+ {
+ "name": "stars",
+ "unicode": "1F320",
+ "digest": "23605eafc949feead3eca145a7ff5ee3b211a8bfd95621bd35dd05df532b97c6"
+ },
+ {
+ "name": "station",
+ "unicode": "1F689",
+ "digest": "c346f12fff64161041af8492550c3541a6304e53f30288224ddd0c6fe08c4d6b"
+ },
+ {
+ "name": "statue_of_liberty",
+ "unicode": "1F5FD",
+ "digest": "56fa27ab059a9fd1f53aec47d9108277a3bf04a73186f36297cd1207c832ee31"
+ },
+ {
+ "name": "steam_locomotive",
+ "unicode": "1F682",
+ "digest": "d0ec2eb3d761ab6157e17eab1b8b4dec3a69f9becc4251592cbb67d71825e661"
+ },
+ {
+ "name": "stereo",
+ "unicode": "1F4FE",
+ "digest": "1ce1f9a83867514b8351ad4fd80c46bba04ad67dfb9874e63d7296e1a21161a5"
+ },
+ {
+ "name": "stew",
+ "unicode": "1F372",
+ "digest": "12e6e4bf48a7296700e07a053d831dd67b70c308ca9522ca96e933a4d1ef6c5e"
+ },
+ {
+ "name": "stock_chart",
+ "unicode": "1F5E0",
+ "digest": "4a0fbf54d19b0b5626f91c932a24e6ac12a65b4fc276d852ff4356c8c579d28a"
+ },
+ {
+ "name": "stop_button",
+ "unicode": "23F9",
+ "digest": "57310962c7738a7da4f2a62cbd5e0b26d7aec357978267a0d8ca8e6cbd7ffb02"
+ },
+ {
+ "name": "stopwatch",
+ "unicode": "23F1",
+ "digest": "c8e69c24f9da98dcb41c9c6355922d08a702f12a35667fbc5beb3f659430333d"
+ },
+ {
+ "name": "straight_ruler",
+ "unicode": "1F4CF",
+ "digest": "55ff7182a3696461df52e3000708083f803bc8bf0f3c25dacb34175cc104b51d"
+ },
+ {
+ "name": "strawberry",
+ "unicode": "1F353",
+ "digest": "fd501e1fefb70242ac7c4dc30ad3d8c3ae200b263a832daedaa984906114afaf"
+ },
+ {
+ "name": "stuck_out_tongue",
+ "unicode": "1F61B",
+ "digest": "1b49956cec511ee382177d95da77c8b6a9214a02c86bf7c6c6fd6cc9df3e9331"
+ },
+ {
+ "name": "stuck_out_tongue_closed_eyes",
+ "unicode": "1F61D",
+ "digest": "60a4d5d92550c6ad4db901d42c9f6434fe94fa3ddb353b6019a93d374d9485e9"
+ },
+ {
+ "name": "stuck_out_tongue_winking_eye",
+ "unicode": "1F61C",
+ "digest": "d9c15ad1c4782a0391a79aeda2745127527385b0b5fc01c8d96c3f3b637a74ae"
+ },
+ {
+ "name": "sun_with_face",
+ "unicode": "1F31E",
+ "digest": "56b14e92f68f8701fdc42763e1f4695ed352845f22bd5d412f827e5cf98dd83b"
+ },
+ {
+ "name": "sunflower",
+ "unicode": "1F33B",
+ "digest": "817dea222a75bb6492c32b4b144d07f48295d7dd113e21760f90b18277612ebb"
+ },
+ {
+ "name": "sunglasses",
+ "unicode": "1F60E",
+ "digest": "16003cc5256397389889f52e0a5e14daea8d8c72f2ea660b8174529868cba9cd"
+ },
+ {
+ "name": "sunny",
+ "unicode": "2600",
+ "digest": "f68a774b7d574fc711111e17368b57c40d973d263c7e857544a09051d4592ab9"
+ },
+ {
+ "name": "sunrise",
+ "unicode": "1F305",
+ "digest": "ce06a9321bc04605538a59f9fca8536d6209d7ded03120e5d2a0be955bb17ddf"
+ },
+ {
+ "name": "sunrise_over_mountains",
+ "unicode": "1F304",
+ "digest": "286244ac2bec8c5c41cf8c7c439702fa525c57fab623f7f9bd7687db0adf75b2"
+ },
+ {
+ "name": "surfer",
+ "unicode": "1F3C4",
+ "digest": "d17c7ea185ca5ef5a2950ef126ee14103bf7769acb419a20d08cc023f619e459"
+ },
+ {
+ "name": "surfer_tone1",
+ "unicode": "1F3C4-1F3FB",
+ "digest": "af66f2f26071b3ba8d7c795139055a58a857212f8cb1f51a507242ad7d2c49c7"
+ },
+ {
+ "name": "surfer_tone2",
+ "unicode": "1F3C4-1F3FC",
+ "digest": "7a34e8b1fdad0a89bbb10333d241583ef018517fdd90f171ad7121de53776a3f"
+ },
+ {
+ "name": "surfer_tone3",
+ "unicode": "1F3C4-1F3FD",
+ "digest": "b2f4cbd59a0aa93c7ee2bbb14ce55c8306dc25884377982a5f132ce6c074fa1d"
+ },
+ {
+ "name": "surfer_tone4",
+ "unicode": "1F3C4-1F3FE",
+ "digest": "b16a02cfcc3606524cca9408e69c654fb83a162eaec8faae8dfd8ec67fe391c5"
+ },
+ {
+ "name": "surfer_tone5",
+ "unicode": "1F3C4-1F3FF",
+ "digest": "b9a156e1aa57544b703db4e4a7773e244a3139e82c2c808c2e5a804fb524f512"
+ },
+ {
+ "name": "sushi",
+ "unicode": "1F363",
+ "digest": "d2709b51ee92997c7fafa1b1517259cb896819c8dc9ba98ae26e1d44ec810d4f"
+ },
+ {
+ "name": "suspension_railway",
+ "unicode": "1F69F",
+ "digest": "48903e103ef00a068b0100b28319b1e41c6a4485cb564f0ca59422ec9d3b259c"
+ },
+ {
+ "name": "sweat",
+ "unicode": "1F613",
+ "digest": "8d684fa882bcbf07f4e91ea02a48cd61f22e7aa206162b8352c26fc19361ed4e"
+ },
+ {
+ "name": "sweat_drops",
+ "unicode": "1F4A6",
+ "digest": "fca48e255dff08dab97ef98b75c67f7504a13be8b90afac88b69a7b7e887e445"
+ },
+ {
+ "name": "sweat_smile",
+ "unicode": "1F605",
+ "digest": "0c8156554eec2396b5fee908da46484945db980d2ebc6dee57b4069a86826182"
+ },
+ {
+ "name": "sweet_potato",
+ "unicode": "1F360",
+ "digest": "3ce74ea9bc14906a3d29a9592c0657aee8f7961d406992752f7580b16ca6bdd0"
+ },
+ {
+ "name": "swimmer",
+ "unicode": "1F3CA",
+ "digest": "05f3aa8544e3b15837bb06ae47344633b3e60d64c572dc6638c4cee19d6e5506"
+ },
+ {
+ "name": "swimmer_tone1",
+ "unicode": "1F3CA-1F3FB",
+ "digest": "85a266a9131f6a1b37e758305ca43ffb46e3e07b0a465c5faefbdb5e5adeb7a4"
+ },
+ {
+ "name": "swimmer_tone2",
+ "unicode": "1F3CA-1F3FC",
+ "digest": "f2afdc4d05a2694e663a420d5ad82bd48c92aedc4137d0fd3725bf08c41bd12a"
+ },
+ {
+ "name": "swimmer_tone3",
+ "unicode": "1F3CA-1F3FD",
+ "digest": "b87ecc38fb9e8eeeef8b120164d758d3f6a68a407053b03261354fd7f90f43b6"
+ },
+ {
+ "name": "swimmer_tone4",
+ "unicode": "1F3CA-1F3FE",
+ "digest": "a08629cf3484953b851b357c6a04891fb97ac15e70c376bbb82af47479835e1c"
+ },
+ {
+ "name": "swimmer_tone5",
+ "unicode": "1F3CA-1F3FF",
+ "digest": "21d83f66b2ef3e348f9e14ec108b9a90262d9934039ebd573471d2bdcde68974"
+ },
+ {
+ "name": "symbols",
+ "unicode": "1F523",
+ "digest": "f33c3ce58374e23b8957c759016fdb5c56ef7fe812bd4e693ae8ff7574cf6bbf"
+ },
+ {
+ "name": "synagogue",
+ "unicode": "1F54D",
+ "digest": "b13402c3c5793ebf924335a87a9f69befb7a6c152fc2a288261b2c2d49842eb6"
+ },
+ {
+ "name": "syringe",
+ "unicode": "1F489",
+ "digest": "39e5e7530255ccf2ff35ec5c653568c8645a4711170c573117f796ea3438c44a"
+ },
+ {
+ "name": "taco",
+ "unicode": "1F32E",
+ "digest": "6b004ce7129e00abcc10278bba1b9c3d5ac71888b99bf353f9878d8e494e3e0d"
+ },
+ {
+ "name": "tada",
+ "unicode": "1F389",
+ "digest": "956a180a1f18e3a1252761e5b3713324f63975ee1fe32168b59b60aa4dd8b72b"
+ },
+ {
+ "name": "tanabata_tree",
+ "unicode": "1F38B",
+ "digest": "d074457ba347687bfc8397ec62edee6325c411356216e7d43acd3f60628a0bb8"
+ },
+ {
+ "name": "tangerine",
+ "unicode": "1F34A",
+ "digest": "1b46bb690458914220cba18c43d7ae0f6914adfee6dba7cf2bb58ed4e1854ad8"
+ },
+ {
+ "name": "taurus",
+ "unicode": "2649",
+ "digest": "ea87fb3baa32605107d63b60847e4873ad9e21b7e7b652e3721cde777168670d"
+ },
+ {
+ "name": "taxi",
+ "unicode": "1F695",
+ "digest": "f44249c643a96d924e1eb35f67a133f3ca61128e610a880afaa09a73c7bcaf9d"
+ },
+ {
+ "name": "tea",
+ "unicode": "1F375",
+ "digest": "56ab8c291de8320c5b339e1cfbe972696e4ea31c592cefa240eda9a3abdf4fa3"
+ },
+ {
+ "name": "telephone",
+ "unicode": "260E",
+ "digest": "609104588e00039199a2fef3190ee6a7be5fca7cb09b36ffe5a7d800aac69d8d"
+ },
+ {
+ "name": "telephone_black",
+ "unicode": "1F57F",
+ "digest": "c3a42a653a91d90c6b668f678419d5438f2e546050914b841623e57107e805db"
+ },
+ {
+ "name": "telephone_receiver",
+ "unicode": "1F4DE",
+ "digest": "e3bf6034de6cf2160893ba4990eba198185a6a3f9cd5767a63b048e41c297640"
+ },
+ {
+ "name": "telephone_white",
+ "unicode": "1F57E",
+ "digest": "62a7e0e50c53e9f85eba51a92882e6064be05997910d3f7700e1e957dbaf0581"
+ },
+ {
+ "name": "telescope",
+ "unicode": "1F52D",
+ "digest": "abe0aca5f2c78105b0e9e4c8ee7a40adcd9bb013e7c49d568076459bade73556"
+ },
+ {
+ "name": "ten",
+ "unicode": "1F51F",
+ "digest": "7593aa7ffe7192a2e35c6ccec76522f6243777783c9152c7c03419835ea58c03"
+ },
+ {
+ "name": "tennis",
+ "unicode": "1F3BE",
+ "digest": "0a5fad3f7f35da0f37761e2279c148dbe154fa14c0e2a0749209b8b2b213a388"
+ },
+ {
+ "name": "tent",
+ "unicode": "26FA",
+ "digest": "7ddf437d8d186e4e3c3e818d137518d590fa06098813c7fe20e1f2a9704feab2"
+ },
+ {
+ "name": "thermometer",
+ "unicode": "1F321",
+ "digest": "597d1714442698a22187fee4d57a2580322f7206c7d51e4519023824598ec08f"
+ },
+ {
+ "name": "thermometer_face",
+ "unicode": "1F912",
+ "digest": "f19c489d89dd2d39770a6c8725a20f3e98f9e5216774af60c0665fd6a03a7687"
+ },
+ {
+ "name": "thinking",
+ "unicode": "1F914",
+ "digest": "f64a9a18dca4c502b46f933838753a818b604a9d0268aa32eda26cbd31abc58c"
+ },
+ {
+ "name": "thought_balloon",
+ "unicode": "1F4AD",
+ "digest": "76c8513191641f0a79e878ccc0d83c4576984609810633f596db2f64cc684b7d"
+ },
+ {
+ "name": "thought_left",
+ "unicode": "1F5EC",
+ "digest": "4fd591bf4318df73d1b17f434a449d8e95f49cca53a3d8f4d1ca983f3809ef46"
+ },
+ {
+ "name": "thought_right",
+ "unicode": "1F5ED",
+ "digest": "0e8c0ce26e2d0e30894f5394b0736456e8268f775e0e7eda4c7dc3c2ff9231ae"
+ },
+ {
+ "name": "three",
+ "unicode": "0033-20E3",
+ "digest": "ca0147a8f67cea3bc2516fa8deef4325188359559786c94ff0b27f90eef04b88"
+ },
+ {
+ "name": "thumbs_down_reverse",
+ "unicode": "1F593",
+ "digest": "a8b561e389bc4e4b07fba70994f6445e5ddc6afe68922fcb6e9e7282d19ad958"
+ },
+ {
+ "name": "thumbs_up_reverse",
+ "unicode": "1F592",
+ "digest": "b6e52715c5ce590bfd08f6e05058ec3765ea2da341b11f9825d100608b173837"
+ },
+ {
+ "name": "thumbsdown",
+ "unicode": "1F44E",
+ "digest": "a98f742c9773e0d95c0de5e1c10d1ab373fa761378a205f27d095e85debe69a3"
+ },
+ {
+ "name": "thumbsdown_tone1",
+ "unicode": "1F44E-1F3FB",
+ "digest": "5d0a7c63d52eafe6267c552168c5557a66622009d565c3cf7b5378c1f6e84bce"
+ },
+ {
+ "name": "thumbsdown_tone2",
+ "unicode": "1F44E-1F3FC",
+ "digest": "ca5c15dc516660b2989a1c717bf3745fdfb6964c7acf3b938285ff6c7caf2ca2"
+ },
+ {
+ "name": "thumbsdown_tone3",
+ "unicode": "1F44E-1F3FD",
+ "digest": "05740e3568795270674dac9134198bf75b1b778c11daa71649c88c231859ec16"
+ },
+ {
+ "name": "thumbsdown_tone4",
+ "unicode": "1F44E-1F3FE",
+ "digest": "5ee93bcc2f515806462a7b303064beade2b22a3f43a8162e39fd65d15d772e27"
+ },
+ {
+ "name": "thumbsdown_tone5",
+ "unicode": "1F44E-1F3FF",
+ "digest": "5c9ef8d53cf6f755668ab6dabfbfcdfd4b95fd59db3b3dd60290efefe9c33994"
+ },
+ {
+ "name": "thumbsup",
+ "unicode": "1F44D",
+ "digest": "28b31df963773ba42a1a089f43cd89d0ce1ab0981e5410f41242e9a125fc1aee"
+ },
+ {
+ "name": "thumbsup_tone1",
+ "unicode": "1F44D-1F3FB",
+ "digest": "f6365942738d2128b6959d6672b3d295757dc8240703cb84a2b014ad78d67de3"
+ },
+ {
+ "name": "thumbsup_tone2",
+ "unicode": "1F44D-1F3FC",
+ "digest": "771d30146e4dc947a69057b05d32c765c8457ab02b5342889c5489acf27ef356"
+ },
+ {
+ "name": "thumbsup_tone3",
+ "unicode": "1F44D-1F3FD",
+ "digest": "0bb7bbfb654c6139260e1786e7ffa5a33f31e19410c1d4d15737fdf5dd4c721d"
+ },
+ {
+ "name": "thumbsup_tone4",
+ "unicode": "1F44D-1F3FE",
+ "digest": "df0927c5342f0075fbf4ea83b724e6f70c0466c54769c9ce4a5c2deb602b28aa"
+ },
+ {
+ "name": "thumbsup_tone5",
+ "unicode": "1F44D-1F3FF",
+ "digest": "0683ae08c50aaf186c6406680a60617679c7b4bccd0817f24b15911dbb06866f"
+ },
+ {
+ "name": "thunder_cloud_rain",
+ "unicode": "26C8",
+ "digest": "dd836f06b41a10d6ed9bcbdae291d2886847ff66dc3ede2427382e469f60674c"
+ },
+ {
+ "name": "ticket",
+ "unicode": "1F3AB",
+ "digest": "a7654a5529535120da3c377e72cd1f7997bdc2dabf1d44b584f7df7852b158f9"
+ },
+ {
+ "name": "tickets",
+ "unicode": "1F39F",
+ "digest": "ccafcc9583a84e847ff1eaa3d53187c5ab150a7d27c6a19363e59b9bc046b567"
+ },
+ {
+ "name": "tiger",
+ "unicode": "1F42F",
+ "digest": "9ebe3117f5f1b589ff8164f8d87dcc275923e0db87121d2cee0fdb9b56dfc4ac"
+ },
+ {
+ "name": "tiger2",
+ "unicode": "1F405",
+ "digest": "212c95dc60d52420a6320917fe3fdd0683b4edc1a2a2c4a1c60920d1f90f4bc3"
+ },
+ {
+ "name": "timer",
+ "unicode": "23F2",
+ "digest": "c48199312ed42ff53a33bb2791db19e2e2521223cd49d8f758ea95b9b379c5ff"
+ },
+ {
+ "name": "tired_face",
+ "unicode": "1F62B",
+ "digest": "ad687a956388ec53ca1e301a0abe2f1e2cfb9f73cd543dd61a21c7335a42e332"
+ },
+ {
+ "name": "tm",
+ "unicode": "2122",
+ "digest": "1156c8b0af40b336bbb6534b3302ac63eab009c4cd0476adcf1fc4669f04b647"
+ },
+ {
+ "name": "toilet",
+ "unicode": "1F6BD",
+ "digest": "a4a24529c21e00e0861f4160c771f0e90aae8f6aee7550ad30d3dbb3fabbd4be"
+ },
+ {
+ "name": "tokyo_tower",
+ "unicode": "1F5FC",
+ "digest": "6324f154f5f5c722044129e5bca03484aca1439911585e42c1c181ffa30b480c"
+ },
+ {
+ "name": "tomato",
+ "unicode": "1F345",
+ "digest": "41bb6de095b27815eacb74a70aea8f7d4fe1ff947182b112001dd47ae7e45fbb"
+ },
+ {
+ "name": "tone1",
+ "unicode": "1F3FB",
+ "digest": "5c62003a098b774c068be45d658db3c0dd38483c0871f7c8ae293bc1222c4f0c"
+ },
+ {
+ "name": "tone2",
+ "unicode": "1F3FC",
+ "digest": "3c636ecbc4e58c7a360f2338daaf44e7da598fd07e0ba1514bb5c0f83fc8819f"
+ },
+ {
+ "name": "tone3",
+ "unicode": "1F3FD",
+ "digest": "398a1e5441b64c9c2d033bbc01d7a8d90b4db30ea9f30e28f0a9120c72a48df8"
+ },
+ {
+ "name": "tone4",
+ "unicode": "1F3FE",
+ "digest": "ff4a12195aeb7494c785b81266efad8cd60c8022c407a0fc032a02e8b83216b3"
+ },
+ {
+ "name": "tone5",
+ "unicode": "1F3FF",
+ "digest": "9e9f0125b5d57011b7456c84719e6be6cf71d06c1b198081d0937c0979164a81"
+ },
+ {
+ "name": "tongue",
+ "unicode": "1F445",
+ "digest": "bf9dd7c65a8dc5d77eb013658a0a12a13f7b224a784e65e203d9584bb6b41427"
+ },
+ {
+ "name": "tools",
+ "unicode": "1F6E0",
+ "digest": "9b0a36dfdb475621d326359662b22cbdb80563c4f476aa5e7d7c00cdba605bd9"
+ },
+ {
+ "name": "top",
+ "unicode": "1F51D",
+ "digest": "d645030099aeb433307569e8e1c4342c1c411a8fefe50fdca7a3207a1a0db671"
+ },
+ {
+ "name": "tophat",
+ "unicode": "1F3A9",
+ "digest": "1082fb2ee2e98fe65d21081b74ca59b07adef85043e2d36f25cac69db2d31fd3"
+ },
+ {
+ "name": "track_next",
+ "unicode": "23ED",
+ "digest": "d5415ed140933f345fea8023a3d8fca30dcfcf7d19d9dc9771fa2cae9df62a3b"
+ },
+ {
+ "name": "track_previous",
+ "unicode": "23EE",
+ "digest": "97ff4a59a236e5cf506fa3577b20715b3b0197e0f343a50615b36185d5b835f1"
+ },
+ {
+ "name": "trackball",
+ "unicode": "1F5B2",
+ "digest": "8332503454ce42059d720c285fe2b15eb0562a0a4b234dccb0f3159bb30a91aa"
+ },
+ {
+ "name": "tractor",
+ "unicode": "1F69C",
+ "digest": "a41d304c41a85d966f6a7c301735fdbe2ae41f4471dd7dcd72023046ca2546d0"
+ },
+ {
+ "name": "traffic_light",
+ "unicode": "1F6A5",
+ "digest": "005f68d028fec8d9ae389cc2b23e1343a82c028eb32820d5e56f5c84eba315d1"
+ },
+ {
+ "name": "train",
+ "unicode": "1F68B",
+ "digest": "bf32893b7b9ecd248e8afe840624061746ac6ceb741e3e861ebfa46014f4bed4"
+ },
+ {
+ "name": "train2",
+ "unicode": "1F686",
+ "digest": "08a9732453a0b4f68dd2d3d3879f04ee538f65897913b5a5157c0585132a374a"
+ },
+ {
+ "name": "train_diesel",
+ "unicode": "1F6F2",
+ "digest": "621bb967cd93fa9f8fd4b155965cc7572d3f91f88d94938ba10c8626718b623c"
+ },
+ {
+ "name": "tram",
+ "unicode": "1F68A",
+ "digest": "5a86d31f7ab677d967fecd75babc900b5169766d0228961912314c4c4d1d64ee"
+ },
+ {
+ "name": "triangle_round",
+ "unicode": "1F6C6",
+ "digest": "e24bb39ecfaaa746b03dc8418697d09ef327d5b077db39014f39d5fb87e23bd5"
+ },
+ {
+ "name": "triangular_flag_on_post",
+ "unicode": "1F6A9",
+ "digest": "d824c973d84cd62c845d64e546de87b094fda8f9972b6a33acd75e1a5ac19f75"
+ },
+ {
+ "name": "triangular_ruler",
+ "unicode": "1F4D0",
+ "digest": "5576802d8bcb8836f473d9c7641ff666250c23c8476c676b253e577695025959"
+ },
+ {
+ "name": "trident",
+ "unicode": "1F531",
+ "digest": "70c1e8254da5b0e4552673b487503a20feeb249484d4596836b75de70220be82"
+ },
+ {
+ "name": "triumph",
+ "unicode": "1F624",
+ "digest": "b09262121b0d3d9d017ded22d0fbb1acaa6ee8c9d38e9ac34292b390d97408fe"
+ },
+ {
+ "name": "trolleybus",
+ "unicode": "1F68E",
+ "digest": "5af943836cc30c3b79160c70b6488c984fa63c104dce08c436597a93d30ff6f4"
+ },
+ {
+ "name": "trophy",
+ "unicode": "1F3C6",
+ "digest": "c249938815042716db2b39cdece6715fabf9e56ed583270c451925e6c91f9191"
+ },
+ {
+ "name": "tropical_drink",
+ "unicode": "1F379",
+ "digest": "352d903e813a27d2a74803322539b50a50aec0ca2ed7ab4a92ec480b1c226cb6"
+ },
+ {
+ "name": "tropical_fish",
+ "unicode": "1F420",
+ "digest": "13a104ca9c326238ab8d85b60759629b4efaa836946fbe58d78d779443475f7b"
+ },
+ {
+ "name": "truck",
+ "unicode": "1F69A",
+ "digest": "13d381d6b43b42350a1e24c02296904b8fdc38c1bf0939fc7037850127e91f21"
+ },
+ {
+ "name": "trumpet",
+ "unicode": "1F3BA",
+ "digest": "df7fb48920ac0919ee2d7b30102016479f747a5d4dd25b3e18d9f17121d232d1"
+ },
+ {
+ "name": "tulip",
+ "unicode": "1F337",
+ "digest": "519a84336464b5dc8db57eecef3e5b8ed82ccfdaa0ed0fa9ef7bcf0e8acea1f8"
+ },
+ {
+ "name": "turkey",
+ "unicode": "1F983",
+ "digest": "e87bff52ad3e301dc62f6832b8a6fcaf99db260a96263e4203a55ce3abda8cf8"
+ },
+ {
+ "name": "turned_ok_hand",
+ "unicode": "1F58F",
+ "digest": "8a6c5b7d4c737866e7e32c6d9f7f447a48a0ac57a8909d43f87367d4a9b59246"
+ },
+ {
+ "name": "turtle",
+ "unicode": "1F422",
+ "digest": "388b3e75b931638a09f65b842d26e2cc87b200ba782dec871f84cddd71aaeaf3"
+ },
+ {
+ "name": "tv",
+ "unicode": "1F4FA",
+ "digest": "dba03be6482d6291599c7393b0f749c0de5c873d45c96a20ccc53b3e104a6a24"
+ },
+ {
+ "name": "twisted_rightwards_arrows",
+ "unicode": "1F500",
+ "digest": "5fcad0247576e10e683f353008749975e9371a4f66c0901a73c3a0c7803c63c7"
+ },
+ {
+ "name": "two",
+ "unicode": "0032-20E3",
+ "digest": "20ad722532a5073fff8aef0a5e890421da0ae97f0723a8a2cc503c13d24ba597"
+ },
+ {
+ "name": "two_hearts",
+ "unicode": "1F495",
+ "digest": "160cb11e3ed2ae1b20957d445c6c4b4bd604d067294818dfeeefba4562425eb9"
+ },
+ {
+ "name": "two_men_holding_hands",
+ "unicode": "1F46C",
+ "digest": "923734704e544f7484fdb424bfe26f51ee07754db712cd151f8fbe955023a1ab"
+ },
+ {
+ "name": "two_women_holding_hands",
+ "unicode": "1F46D",
+ "digest": "58a40e7819cab3589ac81bb4fdc485b7196ee355544b54c6b00169028c260130"
+ },
+ {
+ "name": "u5272",
+ "unicode": "1F239",
+ "digest": "b7e8ad52629a1f1fca77a5c9a51da87ce2b9a81f6af9bcbe9bec9552d398e9bf"
+ },
+ {
+ "name": "u5408",
+ "unicode": "1F234",
+ "digest": "f359799d206cff6aae3af26eb8ad153abd38e817d4c70b2e5e5e8cf2f46e645e"
+ },
+ {
+ "name": "u55b6",
+ "unicode": "1F23A",
+ "digest": "c40293bea0f148e76ca5152e830b1b474380fe259180fbf74fece1ccc9afd8a3"
+ },
+ {
+ "name": "u6307",
+ "unicode": "1F22F",
+ "digest": "45449f7ae29da9e507c19d0f2b22f17f7cbd763f2ec87eb893be5bae49c7f78e"
+ },
+ {
+ "name": "u6708",
+ "unicode": "1F237",
+ "digest": "b897ead8c952013975ce6f381cdb8c584ebe4015311ef87f2a332c8a9e155d75"
+ },
+ {
+ "name": "u6709",
+ "unicode": "1F236",
+ "digest": "8b2f792abc1313a1a58f2fb8b37ad68a964004c962535f7739131257b1331a05"
+ },
+ {
+ "name": "u6e80",
+ "unicode": "1F235",
+ "digest": "fd982a56d4c492e63526b427bb948d7f155b0d5c414a68c7177698a71e72269b"
+ },
+ {
+ "name": "u7121",
+ "unicode": "1F21A",
+ "digest": "334f87a5254b58503d9f7a8ecc3d971a99839ec9c22c443469d72caca1750a48"
+ },
+ {
+ "name": "u7533",
+ "unicode": "1F238",
+ "digest": "3c8e743ae9960e43b9fa0cc698018fcb2a52ae34d143f0561298191f9def019c"
+ },
+ {
+ "name": "u7981",
+ "unicode": "1F232",
+ "digest": "a08bf39be3a54c076de79478c09b79c5c4d221853722870dd6e81abb78a4b64a"
+ },
+ {
+ "name": "u7a7a",
+ "unicode": "1F233",
+ "digest": "5dfb74a534a6490df989f84eac271c79d52f29313b6d43662dd0ff029794367c"
+ },
+ {
+ "name": "umbrella",
+ "unicode": "2614",
+ "digest": "ff1191f6c11b82f5337f78aadb58af50c69abaf676a384b0473bf49004e4018f"
+ },
+ {
+ "name": "umbrella2",
+ "unicode": "2602",
+ "digest": "aa7db9d6ed42dff847a8e5ee48a8eeff7a6e7f30de155a28951407f5aaa3dae2"
+ },
+ {
+ "name": "unamused",
+ "unicode": "1F612",
+ "digest": "efbbcaee6f3178afe509d74d13243ec6befe3112620a01e5079171eac4b32417"
+ },
+ {
+ "name": "underage",
+ "unicode": "1F51E",
+ "digest": "ae9a300fa400a57b7216a0a040fb8a5f02236fbceeeceed58bfd953c87ad51fe"
+ },
+ {
+ "name": "unicorn",
+ "unicode": "1F984",
+ "digest": "1b1e9c209dabe619db76fd346c3fb51b28ace0e4102697fe0973fe2d46aa9f08"
+ },
+ {
+ "name": "unlock",
+ "unicode": "1F513",
+ "digest": "63dbef0855399254ae01cf4ef0676adebc1432ae1ee260b569c23ae8152deaf8"
+ },
+ {
+ "name": "up",
+ "unicode": "1F199",
+ "digest": "902a3ecbcd73099a28476b49bc9e7b06da6cc002ee584e0501e5b625fb515088"
+ },
+ {
+ "name": "upside_down",
+ "unicode": "1F643",
+ "digest": "763fe2baf07a9b04f96958adf38a43c7dd2bc70d57398f49604307bd835cbb53"
+ },
+ {
+ "name": "urn",
+ "unicode": "26B1",
+ "digest": "dbfd5b90709d1b812d2fff71a5cfa10f84a4579866c2d7cd0e80759a22b2ba0e"
+ },
+ {
+ "name": "v",
+ "unicode": "270C",
+ "digest": "df85ad1a3ff365c3232a010701c9b25cd824d19fa2511422dee60ac231f457e3"
+ },
+ {
+ "name": "v_tone1",
+ "unicode": "270C-1F3FB",
+ "digest": "ce45db8de862b6f37d9208920d7c7c19335fac2cbff59b52be1ccbc01e3249da"
+ },
+ {
+ "name": "v_tone2",
+ "unicode": "270C-1F3FC",
+ "digest": "9036c8d793b02b4d2e6a4752b8ec319ec50efd6fcd6feef7b0671a63e5659acc"
+ },
+ {
+ "name": "v_tone3",
+ "unicode": "270C-1F3FD",
+ "digest": "a94b95f7656d62b442c99f2643b96b0c6114683401a94cdda68405c37efecc4c"
+ },
+ {
+ "name": "v_tone4",
+ "unicode": "270C-1F3FE",
+ "digest": "5c75f74993856f2faeeaee68df7689056e60d30e8c573039db8303167f7d0a80"
+ },
+ {
+ "name": "v_tone5",
+ "unicode": "270C-1F3FF",
+ "digest": "bb899672adb3c11f65983fbf9581de7f0a1bbac86fde146e799cea1126fe241e"
+ },
+ {
+ "name": "vertical_traffic_light",
+ "unicode": "1F6A6",
+ "digest": "36296e03620f16d35e5cec195cd97f5b358dfdedcd43bc1b3f7988ff7e85ab47"
+ },
+ {
+ "name": "vhs",
+ "unicode": "1F4FC",
+ "digest": "f4be55f4c23a85e0caacbf569742c117c8fd52c189465a6560cbd2f8873ad74f"
+ },
+ {
+ "name": "vibration_mode",
+ "unicode": "1F4F3",
+ "digest": "b9b8dfa3160c22f78b7d627cb52636d81ca6230a196cee5e94028e32e06b9a98"
+ },
+ {
+ "name": "video_camera",
+ "unicode": "1F4F9",
+ "digest": "3bfaa24e5fb00145e3e4dd07ecf569dabbb3f211551e46085ef23cf23002cfc3"
+ },
+ {
+ "name": "video_game",
+ "unicode": "1F3AE",
+ "digest": "4dcbd76030e37d0f7429852991a5f3f126cbdedfc124ecad0ba29d227375f6e2"
+ },
+ {
+ "name": "violin",
+ "unicode": "1F3BB",
+ "digest": "8ab7adc6e1e934f9e05009cd0a6d4da3136092c8f11c0606b91914be182206f5"
+ },
+ {
+ "name": "virgo",
+ "unicode": "264D",
+ "digest": "aaa19752756d0cac949445de1d2b8bf1f75a071368ae0acf5002f4acdc34826f"
+ },
+ {
+ "name": "volcano",
+ "unicode": "1F30B",
+ "digest": "86c17d61d66bfa868c02f1d31daca22f077c096368ef53cd9bfb9914a2f0b273"
+ },
+ {
+ "name": "volleyball",
+ "unicode": "1F3D0",
+ "digest": "b505684b13f814fbc08dc8ff652849328f46068276e0a24ae1961e2aff15868f"
+ },
+ {
+ "name": "vs",
+ "unicode": "1F19A",
+ "digest": "e31bd8b48b88c21d717964d1360a7751684dd1e0b63fdd655f1a9ec10a952dfb"
+ },
+ {
+ "name": "vulcan",
+ "unicode": "1F596",
+ "digest": "ca800fce797e652c5f47bf44992e8fbe19554688a36423fdf7c29ca6defae1e0"
+ },
+ {
+ "name": "vulcan_tone1",
+ "unicode": "1F596-1F3FB",
+ "digest": "84bafdaca43426b053f5caa4e868ca109d99113a28ea9799db09d3c5d5f645c8"
+ },
+ {
+ "name": "vulcan_tone2",
+ "unicode": "1F596-1F3FC",
+ "digest": "e7cedf63ead957ee5c287e4cb0828ba70673e17b604f92b529875c32d094e7e3"
+ },
+ {
+ "name": "vulcan_tone3",
+ "unicode": "1F596-1F3FD",
+ "digest": "e124fef20f289921553274cf834f6dcc1a012889d30d9874dc5ad01afb8235b8"
+ },
+ {
+ "name": "vulcan_tone4",
+ "unicode": "1F596-1F3FE",
+ "digest": "ea2115f549e4680467521bbf362b229f4a8f0fdadbfaf231378d801f9b369f08"
+ },
+ {
+ "name": "vulcan_tone5",
+ "unicode": "1F596-1F3FF",
+ "digest": "1b322e1252491f35ae02f0b279b6529dad867f2a6b3c2c3e77f981bed07e447d"
+ },
+ {
+ "name": "walking",
+ "unicode": "1F6B6",
+ "digest": "8ec0b2207d4368422261bc58944c17dff2554b2356becfb18f21dd87425cd67b"
+ },
+ {
+ "name": "walking_tone1",
+ "unicode": "1F6B6-1F3FB",
+ "digest": "9ee2224226326833fb0c9598c737fbd2f6bca1c81f082537e9f22ea1de4ff48e"
+ },
+ {
+ "name": "walking_tone2",
+ "unicode": "1F6B6-1F3FC",
+ "digest": "4855d521e937d10d58eeb2bbada493699e31e1098128f81a9e3303bcf3edeb49"
+ },
+ {
+ "name": "walking_tone3",
+ "unicode": "1F6B6-1F3FD",
+ "digest": "82669cf7167054a3615add01059f87dbb809edac3889ee171d5994de90448000"
+ },
+ {
+ "name": "walking_tone4",
+ "unicode": "1F6B6-1F3FE",
+ "digest": "c11f03aa96248272f831f68b93c5b21b2ecbffeb1b4c1c13373bf539ee7db8f8"
+ },
+ {
+ "name": "walking_tone5",
+ "unicode": "1F6B6-1F3FF",
+ "digest": "18238ee121a64211f6bcdbd475cee4ad6debe2bf421daba53d125aa005c26d10"
+ },
+ {
+ "name": "waning_crescent_moon",
+ "unicode": "1F318",
+ "digest": "96ef03ff85247877255a5ca3e8a8bb63f7d41f66531e8db61cbcd863e3ad7355"
+ },
+ {
+ "name": "waning_gibbous_moon",
+ "unicode": "1F316",
+ "digest": "994223113ad151e6b42ee317a10dad18f86759a308e61ab88eeb10ab780aae67"
+ },
+ {
+ "name": "warning",
+ "unicode": "26A0",
+ "digest": "a702e51efd1a3ab425eada008ccf694f38a71db14bb710edacc2e206d61f5ca3"
+ },
+ {
+ "name": "wastebasket",
+ "unicode": "1F5D1",
+ "digest": "afecb31aaf5078298ab9f7c5da29a49ce0cdefe477ee50889be9c0e43ccf1799"
+ },
+ {
+ "name": "watch",
+ "unicode": "231A",
+ "digest": "410334c87b8552f601f4ea1b7e36582a8b22f11b804d5ab1008d4af2b5a0cbe6"
+ },
+ {
+ "name": "water_buffalo",
+ "unicode": "1F403",
+ "digest": "d1becfaea464372c46e5442c6030ea355806ce5864c2435c123a9bb3a2c3c5eb"
+ },
+ {
+ "name": "watermelon",
+ "unicode": "1F349",
+ "digest": "88dd78812520c44080c79fe8cb1825bc713e5155da2ce8c73286333749e7035e"
+ },
+ {
+ "name": "wave",
+ "unicode": "1F44B",
+ "digest": "5103c49914ff1a2d76a1ab6db2530ddd9f48b98b708ab15292ceadf28873c939"
+ },
+ {
+ "name": "wave_tone1",
+ "unicode": "1F44B-1F3FB",
+ "digest": "ef2d79f377d09dedd1e900b2f4e4a2412bf562cd88484f71c52d465053f8aae9"
+ },
+ {
+ "name": "wave_tone2",
+ "unicode": "1F44B-1F3FC",
+ "digest": "d323e6e2e9ce035bc11b98226d46ab393dfdf3909d99e7a828b51950e6574656"
+ },
+ {
+ "name": "wave_tone3",
+ "unicode": "1F44B-1F3FD",
+ "digest": "8a8a386d53252455c20d6b235c462fd9cb3b20c9c19c67e67b3dece4621b5cf6"
+ },
+ {
+ "name": "wave_tone4",
+ "unicode": "1F44B-1F3FE",
+ "digest": "a8281c2ab9cf6e2b3d3cad24707fe412ec2398195530b716a2617477416c0432"
+ },
+ {
+ "name": "wave_tone5",
+ "unicode": "1F44B-1F3FF",
+ "digest": "5ccbee95bfc180580c8a02b88146110c4d132b8ea618dd6a58f03c1db921d58d"
+ },
+ {
+ "name": "wavy_dash",
+ "unicode": "3030",
+ "digest": "b5b67fc12938801a98ff22b6f7b566c603f58c183737fa740a500724879f0e99"
+ },
+ {
+ "name": "waxing_crescent_moon",
+ "unicode": "1F312",
+ "digest": "20446122d170b18f88ea71524f6747d42b97f9d765c52e676e5163fee58ec379"
+ },
+ {
+ "name": "waxing_gibbous_moon",
+ "unicode": "1F314",
+ "digest": "4324e43d4d45e6333f7379c9feb8efd3093d76f3920d7dc5ad3c615e76104998"
+ },
+ {
+ "name": "wc",
+ "unicode": "1F6BE",
+ "digest": "cb7c5d35bf11149d12cda2c0897cb6038e043127055bbe2e8e33c9b422d6d8fc"
+ },
+ {
+ "name": "weary",
+ "unicode": "1F629",
+ "digest": "29a291033a1b67eda3710dffae42d63fcfa663e37dab728c236172f3e877fe8f"
+ },
+ {
+ "name": "wedding",
+ "unicode": "1F492",
+ "digest": "6c7d874f464c9c76b0d767135aa40ced94089b5f71d373098b47488d7f3ef7c4"
+ },
+ {
+ "name": "whale",
+ "unicode": "1F433",
+ "digest": "94168acda6ba502b64ea50ff4aaafb7e6258d7c6806e91f090c8a3c46edc5b6d"
+ },
+ {
+ "name": "whale2",
+ "unicode": "1F40B",
+ "digest": "e1cde2308bd510b2449c96e88ffec796856f98b19ceedc1cd7e9ea009dae1417"
+ },
+ {
+ "name": "wheel_of_dharma",
+ "unicode": "2638",
+ "digest": "bbd6927697c22a1c3e56fd0c9933d9e00dbf120505fe48d02cb486bcd67a8b2c"
+ },
+ {
+ "name": "wheelchair",
+ "unicode": "267F",
+ "digest": "513f759acf528f6a7e39d9de1d171c3faebe645c9cf3bd86b185123016beef95"
+ },
+ {
+ "name": "white_check_mark",
+ "unicode": "2705",
+ "digest": "a0b3bf7c4fb131e7a9fab5169ea4094e2665e02cedaa091f0d6e78609b2f17ed"
+ },
+ {
+ "name": "white_circle",
+ "unicode": "26AA",
+ "digest": "2e7323fa4d1e3929e529d49210a0b82a043eae4f7c95128ec86b98c46fdb0e7c"
+ },
+ {
+ "name": "white_flower",
+ "unicode": "1F4AE",
+ "digest": "a3efea4950e09994f5e9d3d16f0728969238302304a6cce90b293c56e9a3e20c"
+ },
+ {
+ "name": "white_large_square",
+ "unicode": "2B1C",
+ "digest": "99c4442a65f2e3c568f45aed9e74590206c517a716557f4d741d967c9f42ed40"
+ },
+ {
+ "name": "white_medium_small_square",
+ "unicode": "25FD",
+ "digest": "a1edfeb4e540dcc020ba5dde19f7a18d90966788baa5382a22a0f9038d593f01"
+ },
+ {
+ "name": "white_medium_square",
+ "unicode": "25FB",
+ "digest": "794c2339ca71bb6d65ac488fb7b5dc4f0a2412f30890d2c4ece53cdbf52ba78b"
+ },
+ {
+ "name": "white_small_square",
+ "unicode": "25AB",
+ "digest": "9c4c308070a0c4524993cc36feaa778aad8f0df9f209b82d28b1f3811c441bc4"
+ },
+ {
+ "name": "white_square_button",
+ "unicode": "1F533",
+ "digest": "f46e18c7250c874d1b4d6117eda741d86a081352e76f3d019dd64af2669fa4bb"
+ },
+ {
+ "name": "white_sun_cloud",
+ "unicode": "1F325",
+ "digest": "d8ce416e6bdb0e59e06e2fceac3177dbe59fefc248fd8c6d76b80d1418141070"
+ },
+ {
+ "name": "white_sun_rain_cloud",
+ "unicode": "1F326",
+ "digest": "d2b132518261864ac4a95707eaeea335dd8351ed2b8ef4e2272ced456e309bf1"
+ },
+ {
+ "name": "white_sun_small_cloud",
+ "unicode": "1F324",
+ "digest": "b86a72f1cdb4d24fd3ab180aae9db012ca51fc01f3786aab596c2e330066b185"
+ },
+ {
+ "name": "wind_blowing_face",
+ "unicode": "1F32C",
+ "digest": "20bdeb8e39dc637792ac9fbee031c5791889f3126e83556ba51f98809c19763c"
+ },
+ {
+ "name": "wind_chime",
+ "unicode": "1F390",
+ "digest": "1fc26f33ce13b6a969bb76e914de054ec5d1c7c4cd1dc5ee8fea5f3149f794d8"
+ },
+ {
+ "name": "wine_glass",
+ "unicode": "1F377",
+ "digest": "7dfcf9c5195a20fd2745b19e102910392b0fc8f1650b98ab81957807841935e0"
+ },
+ {
+ "name": "wink",
+ "unicode": "1F609",
+ "digest": "404ac6c920414ca35894da1d97b3b2fabe92bd09569274eb5798fbb297129036"
+ },
+ {
+ "name": "wolf",
+ "unicode": "1F43A",
+ "digest": "ebadd7766c4a314b4027c32435a2f5727a6283123dfb8834e10251cbfc07ca2f"
+ },
+ {
+ "name": "woman",
+ "unicode": "1F469",
+ "digest": "9f0dbb5d1e0db4f008141582dcb6413f5aebaa13e191349c976a435b2bee0956"
+ },
+ {
+ "name": "woman_tone1",
+ "unicode": "1F469-1F3FB",
+ "digest": "c1f2a503481fdd96cfbfa7d556500f8e0da0cea1c72ed1078ecbb6962221c22a"
+ },
+ {
+ "name": "woman_tone2",
+ "unicode": "1F469-1F3FC",
+ "digest": "bf78b3a8f7424037069f8ac337e154ef185f55026c71a6cf6dbe15eb42ef9813"
+ },
+ {
+ "name": "woman_tone3",
+ "unicode": "1F469-1F3FD",
+ "digest": "4ccd70a2052b932b3395ac0a957c05815327dc8082fd461abcd797411db8ce05"
+ },
+ {
+ "name": "woman_tone4",
+ "unicode": "1F469-1F3FE",
+ "digest": "71b5efc4a410102e60048ca05f87587384a6db309f3be94109a4f92ea97072dc"
+ },
+ {
+ "name": "woman_tone5",
+ "unicode": "1F469-1F3FF",
+ "digest": "91a1cd015731f4db501c276a8236eb0665e4dc7aa1891e2a67b8d3e543fbea9c"
+ },
+ {
+ "name": "womans_clothes",
+ "unicode": "1F45A",
+ "digest": "599332c0b863a40fd0c319e4e0f52ae847326a96d180c288e0466b3ac308a27e"
+ },
+ {
+ "name": "womans_hat",
+ "unicode": "1F452",
+ "digest": "231ff55c3fa56d8fb5731fe41f547e67ffacfdde82286f45d4ca65a2d2821239"
+ },
+ {
+ "name": "womens",
+ "unicode": "1F6BA",
+ "digest": "f971429456b543804412490af2e27e0b14d0d536a156db898bce67b136e1b563"
+ },
+ {
+ "name": "worried",
+ "unicode": "1F61F",
+ "digest": "e017f636e79b9301f3a06471a5f3513ba7dbb9b97938de1140c1df4c32fd8844"
+ },
+ {
+ "name": "wrench",
+ "unicode": "1F527",
+ "digest": "c9ded4f7f496bad8691677226310bbd31bb485722ea479bc7a68a2b4ef9d55d9"
+ },
+ {
+ "name": "writing_hand",
+ "unicode": "1F58E",
+ "digest": "c4fc18ece6778339ebe14438aaf570e22385c3010c2d341824fa72ac6068cfeb"
+ },
+ {
+ "name": "writing_hand_tone1",
+ "unicode": "270D-1F3FB",
+ "digest": "38e64e6dca4847a12aef8a117c113b2025d841501c4bc8188c57d0c8a4f1e34d"
+ },
+ {
+ "name": "writing_hand_tone2",
+ "unicode": "270D-1F3FC",
+ "digest": "2b2d0ac2701ae707c31d9c85feb2e3700e11398701e2b0519338897817d53baf"
+ },
+ {
+ "name": "writing_hand_tone3",
+ "unicode": "270D-1F3FD",
+ "digest": "85d67f90ff8bd2e7157f28fd857e6730b660a7eb82eb5350f57671f728ce725b"
+ },
+ {
+ "name": "writing_hand_tone4",
+ "unicode": "270D-1F3FE",
+ "digest": "056c05c201b3d0972433f00910967ad7334e37726e2956fee053ec2e1a9153c7"
+ },
+ {
+ "name": "writing_hand_tone5",
+ "unicode": "270D-1F3FF",
+ "digest": "95c59157d301ee08990e4302fd9bdd7953e1d1abed09636d0837d84e44f53ba6"
+ },
+ {
+ "name": "x",
+ "unicode": "274C",
+ "digest": "1d256b0015b9cbdeaa4558f9241782c89d86c79a42e507621f7949c56a90b6c0"
+ },
+ {
+ "name": "yellow_heart",
+ "unicode": "1F49B",
+ "digest": "e869a80266b4379a8d82988fef25e187632bfb076ae619f576e416906cd688a7"
+ },
+ {
+ "name": "yen",
+ "unicode": "1F4B4",
+ "digest": "8f3d801c687e585e4497123c5c91a8b0c558578deec6a8c1591b25e64a3a8992"
+ },
+ {
+ "name": "yin_yang",
+ "unicode": "262F",
+ "digest": "e8ea4c686518ad6165e15ed67b529f2f1e20d648aa2ecb7e9bff5a6067dd3fea"
+ },
+ {
+ "name": "yum",
+ "unicode": "1F60B",
+ "digest": "d9c97bbf6bdb6e39977437680f0b37c9335306c51e01114056ae1d4c9c85b0e0"
+ },
+ {
+ "name": "zap",
+ "unicode": "26A1",
+ "digest": "37588734c7fe330ae35e6ee99e7cf4183e8fe1bc01f6bbbc6293b21076a338cb"
+ },
+ {
+ "name": "zero",
+ "unicode": "0030-20E3",
+ "digest": "519c927db8264d5379ab2c6a18656ea6dd1ceb2afc92eb48563bf86af4697571"
+ },
+ {
+ "name": "zipper_mouth",
+ "unicode": "1F910",
+ "digest": "8396249161b6d865861b56aabd17cae2c821b0d814f4249bf8cab0bb21fa8ee9"
+ },
+ {
+ "name": "zzz",
+ "unicode": "1F4A4",
+ "digest": "f07c56d2d55c0a886c26a8e3d49a9adeab54cc1a0c0354ea8d3bf23aaed3176d"
+ }
+] \ No newline at end of file
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 197e826e5bc..340fc5452ab 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -8,7 +8,7 @@ module API
expose :id, :state, :avatar_url
expose :web_url do |user, options|
- Gitlab::Application.routes.url_helpers.user_url(user)
+ Gitlab::Routing.url_helpers.user_url(user)
end
end
@@ -89,7 +89,7 @@ module API
expose :avatar_url
expose :web_url do |group, options|
- Gitlab::Application.routes.url_helpers.group_url(group)
+ Gitlab::Routing.url_helpers.group_url(group)
end
end
@@ -292,7 +292,7 @@ module API
end
class Label < Grape::Entity
- expose :name, :color
+ expose :name, :color, :description
end
class Compare < Grape::Entity
@@ -334,7 +334,6 @@ module API
expose :updated_at
expose :home_page_url
expose :default_branch_protection
- expose :twitter_sharing_enabled
expose :restricted_visibility_levels
expose :max_attachment_size
expose :session_expire_delay
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index e5ae88eb96f..1fee1dee1a6 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -111,17 +111,21 @@ module API
# Create a new project issue
#
# Parameters:
- # id (required) - The ID of a project
- # title (required) - The title of an issue
- # description (optional) - The description of an issue
- # assignee_id (optional) - The ID of a user to assign issue
+ # id (required) - The ID of a project
+ # title (required) - The title of an issue
+ # description (optional) - The description of an issue
+ # assignee_id (optional) - The ID of a user to assign issue
# milestone_id (optional) - The ID of a milestone to assign issue
- # labels (optional) - The labels of an issue
+ # labels (optional) - The labels of an issue
+ # created_at (optional) - The date
# Example Request:
# POST /projects/:id/issues
post ":id/issues" do
required_attributes! [:title]
- attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id]
+
+ keys = [:title, :description, :assignee_id, :milestone_id]
+ keys << :created_at if current_user.admin? || user_project.owner == current_user
+ attrs = attributes_for_keys(keys)
# Validate label names in advance
if (errors = validate_label_params(params)).any?
diff --git a/lib/api/labels.rb b/lib/api/labels.rb
index 78ca58ad0d1..4af6bef0fa7 100644
--- a/lib/api/labels.rb
+++ b/lib/api/labels.rb
@@ -17,17 +17,18 @@ module API
# Creates a new label
#
# Parameters:
- # id (required) - The ID of a project
- # name (required) - The name of the label to be deleted
- # color (required) - Color of the label given in 6-digit hex
- # notation with leading '#' sign (e.g. #FFAABB)
+ # id (required) - The ID of a project
+ # name (required) - The name of the label to be created
+ # color (required) - Color of the label given in 6-digit hex
+ # notation with leading '#' sign (e.g. #FFAABB)
+ # description (optional) - The description of label to be created
# Example Request:
# POST /projects/:id/labels
post ':id/labels' do
authorize! :admin_label, user_project
required_attributes! [:name, :color]
- attrs = attributes_for_keys [:name, :color]
+ attrs = attributes_for_keys [:name, :color, :description]
label = user_project.find_label(attrs[:name])
conflict!('Label already exists') if label
@@ -62,11 +63,12 @@ module API
# Updates an existing label. At least one optional parameter is required.
#
# Parameters:
- # id (required) - The ID of a project
- # name (required) - The name of the label to be deleted
- # new_name (optional) - The new name of the label
- # color (optional) - Color of the label given in 6-digit hex
- # notation with leading '#' sign (e.g. #FFAABB)
+ # id (required) - The ID of a project
+ # name (required) - The name of the label to be deleted
+ # new_name (optional) - The new name of the label
+ # color (optional) - Color of the label given in 6-digit hex
+ # notation with leading '#' sign (e.g. #FFAABB)
+ # description (optional) - The description of label to be created
# Example Request:
# PUT /projects/:id/labels
put ':id/labels' do
@@ -76,7 +78,7 @@ module API
label = user_project.find_label(params[:name])
not_found!('Label not found') unless label
- attrs = attributes_for_keys [:new_name, :color]
+ attrs = attributes_for_keys [:new_name, :color, :description]
if attrs.empty?
render_api_error!('Required parameters "new_name" or "color" ' \
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 6fcb5261e40..24b31005475 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -244,6 +244,34 @@ module API
end
end
+ # Archive project
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # PUT /projects/:id/archive
+ post ':id/archive' do
+ authorize!(:archive_project, user_project)
+
+ user_project.archive!
+
+ present user_project, with: Entities::Project
+ end
+
+ # Unarchive project
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # PUT /projects/:id/unarchive
+ post ':id/unarchive' do
+ authorize!(:archive_project, user_project)
+
+ user_project.unarchive!
+
+ present user_project, with: Entities::Project
+ end
+
# Remove project
#
# Parameters:
diff --git a/lib/award_emoji.rb b/lib/award_emoji.rb
index 783fcfb61ad..4fc3443ac68 100644
--- a/lib/award_emoji.rb
+++ b/lib/award_emoji.rb
@@ -48,4 +48,23 @@ class AwardEmoji
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')
+ prefix = Gitlab::Application.config.assets.prefix
+ digest = Gitlab::Application.config.assets.digest
+
+ JSON.parse(File.read(path)).map do |hash|
+ if digest
+ fname = "#{hash['unicode']}-#{hash['digest']}"
+ else
+ fname = hash['unicode']
+ end
+
+ { name: hash['name'], path: "#{prefix}/#{fname}.png" }
+ end
+ end
+ end
end
diff --git a/lib/banzai/filter.rb b/lib/banzai/filter.rb
index 905c4c0144e..3eb544dfef9 100644
--- a/lib/banzai/filter.rb
+++ b/lib/banzai/filter.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/string/output_safety'
-
module Banzai
module Filter
def self.[](name)
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index 34c38913474..f21dbef216c 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -11,15 +11,19 @@ module Banzai
end
def self.object_name
- object_class.name.underscore
+ @object_name ||= object_class.name.underscore
end
def self.object_sym
- object_name.to_sym
+ @object_sym ||= object_name.to_sym
end
def self.data_reference
- "data-#{object_name.dasherize}"
+ @data_reference ||= "data-#{object_name.dasherize}"
+ end
+
+ def self.object_class_title
+ @object_title ||= object_class.name.titleize
end
# Public: Find references in text (like `!123` for merge requests)
@@ -53,6 +57,10 @@ module Banzai
self.class.object_sym
end
+ def object_class_title
+ self.class.object_class_title
+ end
+
def references_in(*args, &block)
self.class.references_in(*args, &block)
end
@@ -62,36 +70,81 @@ module Banzai
# Example: project.merge_requests.find
end
+ def find_object_cached(project, id)
+ if RequestStore.active?
+ cache = find_objects_cache[object_class][project.id]
+
+ get_or_set_cache(cache, id) { find_object(project, id) }
+ else
+ find_object(project, id)
+ end
+ end
+
+ def project_from_ref_cache(ref)
+ if RequestStore.active?
+ cache = project_refs_cache
+
+ get_or_set_cache(cache, ref) { project_from_ref(ref) }
+ else
+ project_from_ref(ref)
+ end
+ end
+
def url_for_object(object, project)
# Implement in child class
# Example: project_merge_request_url
end
- def call
- if object_class.reference_pattern
- # `#123`
- replace_text_nodes_matching(object_class.reference_pattern) do |content|
- object_link_filter(content, object_class.reference_pattern)
- end
+ def url_for_object_cached(object, project)
+ if RequestStore.active?
+ cache = url_for_object_cache[object_class][project.id]
- # `[Issue](#123)`, which is turned into
- # `<a href="#123">Issue</a>`
- replace_link_nodes_with_href(object_class.reference_pattern) do |link, text|
- object_link_filter(link, object_class.reference_pattern, link_text: text)
- end
+ get_or_set_cache(cache, object) { url_for_object(object, project) }
+ else
+ url_for_object(object, project)
end
+ end
- if object_class.link_reference_pattern
- # `http://gitlab.example.com/namespace/project/issues/123`, which is turned into
- # `<a href="http://gitlab.example.com/namespace/project/issues/123">http://gitlab.example.com/namespace/project/issues/123</a>`
- replace_link_nodes_with_text(object_class.link_reference_pattern) do |text|
- object_link_filter(text, object_class.link_reference_pattern)
- end
+ def call
+ return doc if project.nil?
+
+ ref_pattern = object_class.reference_pattern
+ link_pattern = object_class.link_reference_pattern
- # `[Issue](http://gitlab.example.com/namespace/project/issues/123)`, which is turned into
- # `<a href="http://gitlab.example.com/namespace/project/issues/123">Issue</a>`
- replace_link_nodes_with_href(object_class.link_reference_pattern) do |link, text|
- object_link_filter(link, object_class.link_reference_pattern, link_text: text)
+ each_node do |node|
+ if text_node?(node) && ref_pattern
+ replace_text_when_pattern_matches(node, ref_pattern) do |content|
+ object_link_filter(content, ref_pattern)
+ end
+
+ elsif element_node?(node)
+ yield_valid_link(node) do |link, text|
+ if ref_pattern && link =~ /\A#{ref_pattern}/
+ replace_link_node_with_href(node, link) do
+ object_link_filter(link, ref_pattern, link_text: text)
+ end
+
+ next
+ end
+
+ next unless link_pattern
+
+ if link == text && text =~ /\A#{link_pattern}/
+ replace_link_node_with_text(node, link) do
+ object_link_filter(text, link_pattern)
+ end
+
+ next
+ end
+
+ if link =~ /\A#{link_pattern}\z/
+ replace_link_node_with_href(node, link) do
+ object_link_filter(link, link_pattern, link_text: text)
+ end
+
+ next
+ end
+ end
end
end
@@ -109,9 +162,9 @@ module Banzai
# have `gfm` and `gfm-OBJECT_NAME` class names attached for styling.
def object_link_filter(text, pattern, link_text: nil)
references_in(text, pattern) do |match, id, project_ref, matches|
- project = project_from_ref(project_ref)
+ project = project_from_ref_cache(project_ref)
- if project && object = find_object(project, id)
+ if project && object = find_object_cached(project, id)
title = object_link_title(object)
klass = reference_class(object_sym)
@@ -121,8 +174,11 @@ module Banzai
object_sym => object.id
)
- url = matches[:url] if matches.names.include?("url")
- url ||= url_for_object(object, project)
+ if matches.names.include?("url") && matches[:url]
+ url = matches[:url]
+ else
+ url = url_for_object_cached(object, project)
+ end
text = link_text || object_link_text(object, matches)
@@ -146,7 +202,7 @@ module Banzai
end
def object_link_title(object)
- "#{object_class.name.titleize}: #{object.title}"
+ "#{object_class_title}: #{object.title}"
end
def object_link_text(object, matches)
@@ -157,6 +213,32 @@ module Banzai
text
end
+
+ private
+
+ def project_refs_cache
+ RequestStore[:banzai_project_refs] ||= {}
+ end
+
+ def find_objects_cache
+ RequestStore[:banzai_find_objects_cache] ||= Hash.new do |hash, key|
+ hash[key] = Hash.new { |h, k| h[k] = {} }
+ end
+ end
+
+ def url_for_object_cache
+ RequestStore[:banzai_url_for_object] ||= Hash.new do |hash, key|
+ hash[key] = Hash.new { |h, k| h[k] = {} }
+ end
+ end
+
+ def get_or_set_cache(cache, key)
+ if cache.key?(key)
+ cache[key]
+ else
+ cache[key] = yield
+ end
+ end
end
end
end
diff --git a/lib/banzai/filter/autolink_filter.rb b/lib/banzai/filter/autolink_filter.rb
index 856f56fb175..fac7dad3243 100644
--- a/lib/banzai/filter/autolink_filter.rb
+++ b/lib/banzai/filter/autolink_filter.rb
@@ -1,4 +1,3 @@
-require 'html/pipeline/filter'
require 'uri'
module Banzai
diff --git a/lib/banzai/filter/commit_range_reference_filter.rb b/lib/banzai/filter/commit_range_reference_filter.rb
index 470727ee312..b469ea0f626 100644
--- a/lib/banzai/filter/commit_range_reference_filter.rb
+++ b/lib/banzai/filter/commit_range_reference_filter.rb
@@ -43,7 +43,7 @@ module Banzai
end
def url_for_object(range, project)
- h = Gitlab::Application.routes.url_helpers
+ h = Gitlab::Routing.url_helpers
h.namespace_project_compare_url(project.namespace, project,
range.to_param.merge(only_path: context[:only_path]))
end
diff --git a/lib/banzai/filter/commit_reference_filter.rb b/lib/banzai/filter/commit_reference_filter.rb
index 713a56ba949..bd88207326c 100644
--- a/lib/banzai/filter/commit_reference_filter.rb
+++ b/lib/banzai/filter/commit_reference_filter.rb
@@ -37,7 +37,7 @@ module Banzai
end
def url_for_object(commit, project)
- h = Gitlab::Application.routes.url_helpers
+ h = Gitlab::Routing.url_helpers
h.namespace_project_commit_url(project.namespace, project, commit,
only_path: context[:only_path])
end
diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb
index 207437ba7cf..d25de900674 100644
--- a/lib/banzai/filter/emoji_filter.rb
+++ b/lib/banzai/filter/emoji_filter.rb
@@ -1,7 +1,3 @@
-require 'action_controller'
-require 'gitlab_emoji'
-require 'html/pipeline/filter'
-
module Banzai
module Filter
# HTML filter that replaces :emoji: with images.
diff --git a/lib/banzai/filter/external_issue_reference_filter.rb b/lib/banzai/filter/external_issue_reference_filter.rb
index edc26386903..37344b90576 100644
--- a/lib/banzai/filter/external_issue_reference_filter.rb
+++ b/lib/banzai/filter/external_issue_reference_filter.rb
@@ -35,15 +35,29 @@ module Banzai
def call
# Early return if the project isn't using an external tracker
- return doc if project.nil? || project.default_issues_tracker?
+ return doc if project.nil? || default_issues_tracker?
- replace_text_nodes_matching(ExternalIssue.reference_pattern) do |content|
- issue_link_filter(content)
- end
+ ref_pattern = ExternalIssue.reference_pattern
+ ref_start_pattern = /\A#{ref_pattern}\z/
+
+ each_node do |node|
+ if text_node?(node)
+ replace_text_when_pattern_matches(node, ref_pattern) do |content|
+ issue_link_filter(content)
+ end
- replace_link_nodes_with_href(ExternalIssue.reference_pattern) do |link, text|
- issue_link_filter(link, link_text: text)
+ elsif element_node?(node)
+ yield_valid_link(node) do |link, text|
+ if link =~ ref_start_pattern
+ replace_link_node_with_href(node, link) do
+ issue_link_filter(link, link_text: text)
+ end
+ end
+ end
+ end
end
+
+ doc
end
# Replace `JIRA-123` issue references in text with links to the referenced
@@ -76,6 +90,21 @@ module Banzai
def url_for_issue(*args)
IssuesHelper.url_for_issue(*args)
end
+
+ def default_issues_tracker?
+ if RequestStore.active?
+ default_issues_tracker_cache[project.id] ||=
+ project.default_issues_tracker?
+ else
+ project.default_issues_tracker?
+ end
+ end
+
+ private
+
+ def default_issues_tracker_cache
+ RequestStore[:banzai_default_issues_tracker_cache] ||= {}
+ end
end
end
end
diff --git a/lib/banzai/filter/external_link_filter.rb b/lib/banzai/filter/external_link_filter.rb
index 8d368f3b9e7..d179bea181e 100644
--- a/lib/banzai/filter/external_link_filter.rb
+++ b/lib/banzai/filter/external_link_filter.rb
@@ -1,5 +1,3 @@
-require 'html/pipeline/filter'
-
module Banzai
module Filter
# HTML Filter to add a `rel="nofollow"` attribute to external links
diff --git a/lib/banzai/filter/gollum_tags_filter.rb b/lib/banzai/filter/gollum_tags_filter.rb
index f31f921903b..7ce26db1b90 100644
--- a/lib/banzai/filter/gollum_tags_filter.rb
+++ b/lib/banzai/filter/gollum_tags_filter.rb
@@ -1,6 +1,3 @@
-require 'banzai'
-require 'html/pipeline/filter'
-
module Banzai
module Filter
# HTML Filter for parsing Gollum's tags in HTML. It's only parses the
diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb
index 8147e5ed3c7..a2987850d03 100644
--- a/lib/banzai/filter/label_reference_filter.rb
+++ b/lib/banzai/filter/label_reference_filter.rb
@@ -31,7 +31,7 @@ module Banzai
end
def url_for_object(label, project)
- h = Gitlab::Application.routes.url_helpers
+ h = Gitlab::Routing.url_helpers
h.namespace_project_issues_url(project.namespace, project, label_name: label.name,
only_path: context[:only_path])
end
diff --git a/lib/banzai/filter/markdown_filter.rb b/lib/banzai/filter/markdown_filter.rb
index 0659fed1419..9b209533a89 100644
--- a/lib/banzai/filter/markdown_filter.rb
+++ b/lib/banzai/filter/markdown_filter.rb
@@ -1,5 +1,3 @@
-require 'html/pipeline/filter'
-
module Banzai
module Filter
class MarkdownFilter < HTML::Pipeline::TextFilter
diff --git a/lib/banzai/filter/merge_request_reference_filter.rb b/lib/banzai/filter/merge_request_reference_filter.rb
index 57c71708992..cad38a51851 100644
--- a/lib/banzai/filter/merge_request_reference_filter.rb
+++ b/lib/banzai/filter/merge_request_reference_filter.rb
@@ -14,7 +14,7 @@ module Banzai
end
def url_for_object(mr, project)
- h = Gitlab::Application.routes.url_helpers
+ h = Gitlab::Routing.url_helpers
h.namespace_project_merge_request_url(project.namespace, project, mr,
only_path: context[:only_path])
end
diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb
index e88b27c1fae..4cb82178024 100644
--- a/lib/banzai/filter/milestone_reference_filter.rb
+++ b/lib/banzai/filter/milestone_reference_filter.rb
@@ -1,5 +1,3 @@
-require 'banzai'
-
module Banzai
module Filter
# HTML filter that replaces milestone references with links.
@@ -13,7 +11,7 @@ module Banzai
end
def url_for_object(issue, project)
- h = Gitlab::Application.routes.url_helpers
+ h = Gitlab::Routing.url_helpers
h.namespace_project_milestone_url(project.namespace, project, milestone,
only_path: context[:only_path])
end
diff --git a/lib/banzai/filter/redactor_filter.rb b/lib/banzai/filter/redactor_filter.rb
index 7141ed7c9bd..e589b5df6ec 100644
--- a/lib/banzai/filter/redactor_filter.rb
+++ b/lib/banzai/filter/redactor_filter.rb
@@ -1,5 +1,3 @@
-require 'html/pipeline/filter'
-
module Banzai
module Filter
# HTML filter that removes references to records that the current user does
diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb
index 132f0a4bd93..31386cf851c 100644
--- a/lib/banzai/filter/reference_filter.rb
+++ b/lib/banzai/filter/reference_filter.rb
@@ -1,6 +1,3 @@
-require 'active_support/core_ext/string/output_safety'
-require 'html/pipeline/filter'
-
module Banzai
module Filter
# Base class for GitLab Flavored Markdown reference filters.
@@ -55,18 +52,13 @@ module Banzai
html.html_safe? ? html : ERB::Util.html_escape_once(html)
end
- def ignore_parents
- @ignore_parents ||= begin
- # Don't look for references in text nodes that are children of these
- # elements.
+ def ignore_ancestor_query
+ @ignore_ancestor_query ||= begin
parents = %w(pre code a style)
parents << 'blockquote' if context[:ignore_blockquotes]
- parents.to_set
- end
- end
- def ignored_ancestry?(node)
- has_ancestor?(node, ignore_parents)
+ parents.map { |n| "ancestor::#{n}" }.join(' or ')
+ end
end
def project
@@ -77,119 +69,66 @@ module Banzai
"gfm gfm-#{type}"
end
- # Iterate through the document's text nodes, yielding the current node's
- # content if:
- #
- # * The `project` context value is present AND
- # * The node's content matches `pattern` AND
- # * The node is not an ancestor of an ignored node type
- #
- # pattern - Regex pattern against which to match the node's content
- #
- # Yields the current node's String contents. The result of the block will
- # replace the node's existing content and update the current document.
+ # Ensure that a :project key exists in context
#
- # Returns the updated Nokogiri::HTML::DocumentFragment object.
- def replace_text_nodes_matching(pattern)
- return doc if project.nil?
-
- search_text_nodes(doc).each do |node|
- next if ignored_ancestry?(node)
- next unless node.text =~ pattern
-
- content = node.to_html
-
- html = yield content
-
- next if html == content
-
- node.replace(html)
- end
-
- doc
+ # Note that while the key might exist, its value could be nil!
+ def validate
+ needs :project
end
- # Iterate through the document's link nodes, yielding the current node's
- # content if:
- #
- # * The `project` context value is present AND
- # * The node's content matches `pattern`
- #
- # pattern - Regex pattern against which to match the node's content
- #
- # Yields the current node's String contents. The result of the block will
- # replace the node and update the current document.
+ # Iterates over all <a> and text() nodes in a document.
#
- # Returns the updated Nokogiri::HTML::DocumentFragment object.
- def replace_link_nodes_with_text(pattern)
- return doc if project.nil?
+ # Nodes are skipped whenever their ancestor is one of the nodes returned
+ # by `ignore_ancestor_query`. Link tags are not processed if they have a
+ # "gfm" class or the "href" attribute is empty.
+ def each_node
+ query = %Q{descendant-or-self::text()[not(#{ignore_ancestor_query})]
+ | descendant-or-self::a[
+ not(contains(concat(" ", @class, " "), " gfm ")) and not(@href = "")
+ ]}
- doc.xpath('descendant-or-self::a').each do |node|
- klass = node.attr('class')
- next if klass && klass.include?('gfm')
-
- link = node.attr('href')
- text = node.text
-
- next unless link && text
-
- link = CGI.unescape(link)
- next unless link.force_encoding('UTF-8').valid_encoding?
- # Ignore ending punctionation like periods or commas
- next unless link == text && text =~ /\A#{pattern}/
-
- html = yield text
+ doc.xpath(query).each do |node|
+ yield node
+ end
+ end
- next if html == text
+ # Yields the link's URL and text whenever the node is a valid <a> tag.
+ def yield_valid_link(node)
+ link = CGI.unescape(node.attr('href').to_s)
+ text = node.text
- node.replace(html)
- end
+ return unless link.force_encoding('UTF-8').valid_encoding?
- doc
+ yield link, text
end
- # Iterate through the document's link nodes, yielding the current node's
- # content if:
- #
- # * The `project` context value is present AND
- # * The node's HREF matches `pattern`
- #
- # pattern - Regex pattern against which to match the node's HREF
- #
- # Yields the current node's String HREF and String content.
- # The result of the block will replace the node and update the current document.
- #
- # Returns the updated Nokogiri::HTML::DocumentFragment object.
- def replace_link_nodes_with_href(pattern)
- return doc if project.nil?
+ def replace_text_when_pattern_matches(node, pattern)
+ return unless node.text =~ pattern
- doc.xpath('descendant-or-self::a').each do |node|
- klass = node.attr('class')
- next if klass && klass.include?('gfm')
+ content = node.to_html
+ html = yield content
- link = node.attr('href')
- text = node.text
+ node.replace(html) unless content == html
+ end
- next unless link && text
- link = CGI.unescape(link)
- next unless link.force_encoding('UTF-8').valid_encoding?
- next unless link && link =~ /\A#{pattern}\z/
+ def replace_link_node_with_text(node, link)
+ html = yield
- html = yield link, text
+ node.replace(html) unless html == node.text
+ end
- next if html == link
+ def replace_link_node_with_href(node, link)
+ html = yield
- node.replace(html)
- end
+ node.replace(html) unless html == link
+ end
- doc
+ def text_node?(node)
+ node.is_a?(Nokogiri::XML::Text)
end
- # Ensure that a :project key exists in context
- #
- # Note that while the key might exist, its value could be nil!
- def validate
- needs :project
+ def element_node?(node)
+ node.is_a?(Nokogiri::XML::Element)
end
end
end
diff --git a/lib/banzai/filter/reference_gatherer_filter.rb b/lib/banzai/filter/reference_gatherer_filter.rb
index 86d484feb90..96fdb06304e 100644
--- a/lib/banzai/filter/reference_gatherer_filter.rb
+++ b/lib/banzai/filter/reference_gatherer_filter.rb
@@ -1,5 +1,3 @@
-require 'html/pipeline/filter'
-
module Banzai
module Filter
# HTML filter that gathers all referenced records that the current user has
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index 41380627d39..ea21c7b041c 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -1,4 +1,3 @@
-require 'html/pipeline/filter'
require 'uri'
module Banzai
diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb
index e8011519608..42dbab9d27e 100644
--- a/lib/banzai/filter/sanitization_filter.rb
+++ b/lib/banzai/filter/sanitization_filter.rb
@@ -1,6 +1,3 @@
-require 'html/pipeline/filter'
-require 'html/pipeline/sanitization_filter'
-
module Banzai
module Filter
# Sanitize HTML
diff --git a/lib/banzai/filter/snippet_reference_filter.rb b/lib/banzai/filter/snippet_reference_filter.rb
index c870a42f741..d507eb5ebe1 100644
--- a/lib/banzai/filter/snippet_reference_filter.rb
+++ b/lib/banzai/filter/snippet_reference_filter.rb
@@ -14,7 +14,7 @@ module Banzai
end
def url_for_object(snippet, project)
- h = Gitlab::Application.routes.url_helpers
+ h = Gitlab::Routing.url_helpers
h.namespace_project_snippet_url(project.namespace, project, snippet,
only_path: context[:only_path])
end
diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb
index 8c5855e5ffc..62a79c62e20 100644
--- a/lib/banzai/filter/syntax_highlight_filter.rb
+++ b/lib/banzai/filter/syntax_highlight_filter.rb
@@ -1,4 +1,3 @@
-require 'html/pipeline/filter'
require 'rouge/plugins/redcarpet'
module Banzai
diff --git a/lib/banzai/filter/table_of_contents_filter.rb b/lib/banzai/filter/table_of_contents_filter.rb
index 4056dcd6d64..a4eda6fdf76 100644
--- a/lib/banzai/filter/table_of_contents_filter.rb
+++ b/lib/banzai/filter/table_of_contents_filter.rb
@@ -1,5 +1,3 @@
-require 'html/pipeline/filter'
-
module Banzai
module Filter
# HTML filter that adds an anchor child element to all Headers in a
diff --git a/lib/banzai/filter/upload_link_filter.rb b/lib/banzai/filter/upload_link_filter.rb
index f642aee0967..7edfe5ade2d 100644
--- a/lib/banzai/filter/upload_link_filter.rb
+++ b/lib/banzai/filter/upload_link_filter.rb
@@ -1,4 +1,3 @@
-require 'html/pipeline/filter'
require 'uri'
module Banzai
diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb
index 24f16f8b547..eea3af842b6 100644
--- a/lib/banzai/filter/user_reference_filter.rb
+++ b/lib/banzai/filter/user_reference_filter.rb
@@ -59,13 +59,28 @@ module Banzai
end
def call
- replace_text_nodes_matching(User.reference_pattern) do |content|
- user_link_filter(content)
+ return doc if project.nil?
+
+ ref_pattern = User.reference_pattern
+ ref_pattern_start = /\A#{ref_pattern}\z/
+
+ each_node do |node|
+ if text_node?(node)
+ replace_text_when_pattern_matches(node, ref_pattern) do |content|
+ user_link_filter(content)
+ end
+ elsif element_node?(node)
+ yield_valid_link(node) do |link, text|
+ if link =~ ref_pattern_start
+ replace_link_node_with_href(node, link) do
+ user_link_filter(link, link_text: text)
+ end
+ end
+ end
+ end
end
- replace_link_nodes_with_href(User.reference_pattern) do |link, text|
- user_link_filter(link, link_text: text)
- end
+ doc
end
# Replace `@user` user references in text with links to the referenced
@@ -90,7 +105,7 @@ module Banzai
private
def urls
- Gitlab::Application.routes.url_helpers
+ Gitlab::Routing.url_helpers
end
def link_class
diff --git a/lib/banzai/filter/yaml_front_matter_filter.rb b/lib/banzai/filter/yaml_front_matter_filter.rb
index e4e2f3f228d..58e3e81209e 100644
--- a/lib/banzai/filter/yaml_front_matter_filter.rb
+++ b/lib/banzai/filter/yaml_front_matter_filter.rb
@@ -1,6 +1,3 @@
-require 'html/pipeline/filter'
-require 'yaml'
-
module Banzai
module Filter
class YamlFrontMatterFilter < HTML::Pipeline::Filter
diff --git a/lib/banzai/pipeline/base_pipeline.rb b/lib/banzai/pipeline/base_pipeline.rb
index f60966c3c0f..321fd5bbe14 100644
--- a/lib/banzai/pipeline/base_pipeline.rb
+++ b/lib/banzai/pipeline/base_pipeline.rb
@@ -1,5 +1,3 @@
-require 'html/pipeline'
-
module Banzai
module Pipeline
class BasePipeline
diff --git a/lib/banzai/pipeline/wiki_pipeline.rb b/lib/banzai/pipeline/wiki_pipeline.rb
index 9b4ff0f0f80..0b5a9e0b2b8 100644
--- a/lib/banzai/pipeline/wiki_pipeline.rb
+++ b/lib/banzai/pipeline/wiki_pipeline.rb
@@ -1,5 +1,3 @@
-require 'banzai'
-
module Banzai
module Pipeline
class WikiPipeline < FullPipeline
diff --git a/lib/gitlab/badge/build.rb b/lib/gitlab/badge/build.rb
new file mode 100644
index 00000000000..28a2391dbf8
--- /dev/null
+++ b/lib/gitlab/badge/build.rb
@@ -0,0 +1,24 @@
+module Gitlab
+ module Badge
+ ##
+ # Build badge
+ #
+ class Build
+ def initialize(project, ref)
+ @image = ::Ci::ImageForBuildService.new.execute(project, ref: ref)
+ end
+
+ def to_s
+ @image[:name].sub(/\.svg$/, '')
+ end
+
+ def type
+ 'image/svg+xml'
+ end
+
+ def data
+ File.read(@image[:path])
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 761b63e98f6..1acc22fe5bf 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -21,7 +21,6 @@ module Gitlab
default_branch_protection: Settings.gitlab['default_branch_protection'],
signup_enabled: Settings.gitlab['signup_enabled'],
signin_enabled: Settings.gitlab['signin_enabled'],
- twitter_sharing_enabled: Settings.gitlab['twitter_sharing_enabled'],
gravatar_enabled: Settings.gravatar['enabled'],
sign_in_text: Settings.extra['sign_in_text'],
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb
index 41f0edcaf7e..8f9be6cd9a3 100644
--- a/lib/gitlab/email/message/repository_push.rb
+++ b/lib/gitlab/email/message/repository_push.rb
@@ -5,7 +5,7 @@ module Gitlab
attr_accessor :recipient
attr_reader :author_id, :ref, :action
- include Gitlab::Application.routes.url_helpers
+ include Gitlab::Routing.url_helpers
delegate :namespace, :name_with_namespace, to: :project, prefix: :project
delegate :name, to: :author, prefix: :author
diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb
index d4b6f6d120d..97ef9851d71 100644
--- a/lib/gitlab/email/receiver.rb
+++ b/lib/gitlab/email/receiver.rb
@@ -63,6 +63,10 @@ module Gitlab
end
def reply_key
+ key_from_to_header || key_from_additional_headers
+ end
+
+ def key_from_to_header
key = nil
message.to.each do |address|
key = Gitlab::IncomingEmail.key_from_address(address)
@@ -72,6 +76,17 @@ module Gitlab
key
end
+ def key_from_additional_headers
+ reply_key = nil
+
+ Array(message.references).each do |message_id|
+ reply_key = Gitlab::IncomingEmail.key_from_fallback_reply_message_id(message_id)
+ break if reply_key
+ end
+
+ reply_key
+ end
+
def sent_notification
return nil unless reply_key
diff --git a/lib/gitlab/fogbugz_import/client.rb b/lib/gitlab/fogbugz_import/client.rb
index 431d50882fd..2152182b37f 100644
--- a/lib/gitlab/fogbugz_import/client.rb
+++ b/lib/gitlab/fogbugz_import/client.rb
@@ -26,7 +26,7 @@ module Gitlab
def user_map
users = {}
res = @api.command(:listPeople)
- res['people']['person'].each do |user|
+ [res['people']['person']].flatten.each do |user|
users[user['ixPerson']] = { name: user['sFullName'], email: user['sEmail'] }
end
users
diff --git a/lib/gitlab/gfm/reference_rewriter.rb b/lib/gitlab/gfm/reference_rewriter.rb
index a1c6ee7bd69..78d7a4f27cf 100644
--- a/lib/gitlab/gfm/reference_rewriter.rb
+++ b/lib/gitlab/gfm/reference_rewriter.rb
@@ -34,16 +34,21 @@ module Gitlab
@source_project = source_project
@current_user = current_user
@original_html = markdown(text)
+ @pattern = Gitlab::ReferenceExtractor.references_pattern
end
def rewrite(target_project)
- pattern = Gitlab::ReferenceExtractor.references_pattern
+ return @text unless needs_rewrite?
- @text.gsub(pattern) do |reference|
+ @text.gsub(@pattern) do |reference|
unfold_reference(reference, Regexp.last_match, target_project)
end
end
+ def needs_rewrite?
+ @text =~ @pattern
+ end
+
private
def unfold_reference(reference, match, target_project)
diff --git a/lib/gitlab/gfm/uploads_rewriter.rb b/lib/gitlab/gfm/uploads_rewriter.rb
new file mode 100644
index 00000000000..abc8c8c55e6
--- /dev/null
+++ b/lib/gitlab/gfm/uploads_rewriter.rb
@@ -0,0 +1,51 @@
+module Gitlab
+ module Gfm
+ ##
+ # Class that rewrites markdown links for uploads
+ #
+ # Using a pattern defined in `FileUploader` it copies files to a new
+ # project and rewrites all links to uploads in in a given text.
+ #
+ #
+ class UploadsRewriter
+ def initialize(text, source_project, _current_user)
+ @text = text
+ @source_project = source_project
+ @pattern = FileUploader::MARKDOWN_PATTERN
+ end
+
+ def rewrite(target_project)
+ return @text unless needs_rewrite?
+
+ @text.gsub(@pattern) do |markdown|
+ file = find_file(@source_project, $~[:secret], $~[:file])
+ return markdown unless file.try(:exists?)
+
+ new_uploader = FileUploader.new(target_project)
+ new_uploader.store!(file)
+ new_uploader.to_markdown
+ end
+ end
+
+ def needs_rewrite?
+ files.any?
+ end
+
+ def files
+ referenced_files = @text.scan(@pattern).map do
+ find_file(@source_project, $~[:secret], $~[:file])
+ end
+
+ referenced_files.compact.select(&:exists?)
+ end
+
+ private
+
+ def find_file(project, secret, file)
+ uploader = FileUploader.new(project, secret)
+ uploader.retrieve_from_store!(file)
+ uploader.file
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/incoming_email.rb b/lib/gitlab/incoming_email.rb
index 9068d79c95e..8ce9d32abe0 100644
--- a/lib/gitlab/incoming_email.rb
+++ b/lib/gitlab/incoming_email.rb
@@ -1,13 +1,10 @@
module Gitlab
module IncomingEmail
class << self
- def enabled?
- config.enabled && address_formatted_correctly?
- end
+ FALLBACK_REPLY_MESSAGE_ID_REGEX = /\Areply\-(.+)@#{Gitlab.config.gitlab.host}\Z/.freeze
- def address_formatted_correctly?
- config.address &&
- config.address.include?("%{key}")
+ def enabled?
+ config.enabled && config.address
end
def reply_address(key)
@@ -24,6 +21,13 @@ module Gitlab
match[1]
end
+ def key_from_fallback_reply_message_id(message_id)
+ match = message_id.match(FALLBACK_REPLY_MESSAGE_ID_REGEX)
+ return unless match
+
+ match[1]
+ end
+
def config
Gitlab.config.incoming_email
end
diff --git a/lib/gitlab/note_data_builder.rb b/lib/gitlab/note_data_builder.rb
index 71cf6a0d886..18523e0aefe 100644
--- a/lib/gitlab/note_data_builder.rb
+++ b/lib/gitlab/note_data_builder.rb
@@ -41,7 +41,7 @@ module Gitlab
data[:issue] = note.noteable.hook_attrs
elsif note.for_merge_request?
data[:merge_request] = note.noteable.hook_attrs
- elsif note.for_project_snippet?
+ elsif note.for_snippet?
data[:snippet] = note.noteable.hook_attrs
end
diff --git a/lib/gitlab/routing.rb b/lib/gitlab/routing.rb
new file mode 100644
index 00000000000..5132177de51
--- /dev/null
+++ b/lib/gitlab/routing.rb
@@ -0,0 +1,13 @@
+module Gitlab
+ module Routing
+ # Returns the URL helpers Module.
+ #
+ # This method caches the output as Rails' "url_helpers" method creates an
+ # anonymous module every time it's called.
+ #
+ # Returns a Module.
+ def self.url_helpers
+ @url_helpers ||= Gitlab::Application.routes.url_helpers
+ end
+ end
+end
diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb
index 6f0d02cafd1..f301d42939d 100644
--- a/lib/gitlab/url_builder.rb
+++ b/lib/gitlab/url_builder.rb
@@ -1,7 +1,8 @@
module Gitlab
class UrlBuilder
- include Gitlab::Application.routes.url_helpers
+ include Gitlab::Routing.url_helpers
include GitlabRoutingHelper
+ include ActionView::RecordIdentifier
def initialize(type)
@type = type
@@ -37,19 +38,16 @@ module Gitlab
namespace_project_commit_url(namespace_id: note.project.namespace,
id: note.commit_id,
project_id: note.project,
- anchor: "note_#{note.id}")
+ anchor: dom_id(note))
elsif note.for_issue?
issue = Issue.find(note.noteable_id)
- issue_url(issue,
- anchor: "note_#{note.id}")
+ issue_url(issue, anchor: dom_id(note))
elsif note.for_merge_request?
merge_request = MergeRequest.find(note.noteable_id)
- merge_request_url(merge_request,
- anchor: "note_#{note.id}")
- elsif note.for_project_snippet?
+ merge_request_url(merge_request, anchor: dom_id(note))
+ elsif note.for_snippet?
snippet = Snippet.find(note.noteable_id)
- project_snippet_url(snippet,
- anchor: "note_#{note.id}")
+ project_snippet_url(snippet, anchor: dom_id(note))
end
end
end
diff --git a/lib/tasks/gemojione.rake b/lib/tasks/gemojione.rake
index cfaf4a129b1..7ec00a898fd 100644
--- a/lib/tasks/gemojione.rake
+++ b/lib/tasks/gemojione.rake
@@ -1,19 +1,39 @@
-# This task will generate a standard and Retina sprite of all of the current
-# Gemojione Emojis, with the accompanying SCSS map.
-#
-# It will not appear in `rake -T` output, and the dependent gems are not
-# included in the Gemfile by default, because this task will only be needed
-# occasionally, such as when new Emojis are added to Gemojione.
-
-begin
- require 'sprite_factory'
- require 'rmagick'
-rescue LoadError
- # noop
-end
-
namespace :gemojione do
+ desc 'Generates Emoji SHA256 digests'
+ task digests: :environment do
+ require 'digest/sha2'
+ require 'json'
+
+ dir = Gemojione.index.images_path
+
+ digests = AwardEmoji.emojis.map do |name, emoji_hash|
+ fpath = File.join(dir, "#{emoji_hash['unicode']}.png")
+ digest = Digest::SHA256.file(fpath).hexdigest
+
+ { name: name, unicode: emoji_hash['unicode'], digest: digest }
+ end
+
+ out = File.join(Rails.root, 'fixtures', 'emojis', 'digests.json')
+
+ File.open(out, 'w') do |handle|
+ handle.write(JSON.pretty_generate(digests))
+ end
+ end
+
+ # This task will generate a standard and Retina sprite of all of the current
+ # Gemojione Emojis, with the accompanying SCSS map.
+ #
+ # It will not appear in `rake -T` output, and the dependent gems are not
+ # included in the Gemfile by default, because this task will only be needed
+ # occasionally, such as when new Emojis are added to Gemojione.
task sprite: :environment do
+ begin
+ require 'sprite_factory'
+ require 'rmagick'
+ rescue LoadError
+ # noop
+ end
+
check_requirements!
SIZE = 20
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 27ed57efe55..effb8eb6001 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -623,7 +623,6 @@ namespace :gitlab do
start_checking "Reply by email"
if Gitlab.config.incoming_email.enabled
- check_address_formatted_correctly
check_imap_authentication
if Rails.env.production?
@@ -643,20 +642,6 @@ namespace :gitlab do
# Checks
########################
- def check_address_formatted_correctly
- print "Address formatted correctly? ... "
-
- if Gitlab::IncomingEmail.address_formatted_correctly?
- puts "yes".green
- else
- puts "no".red
- try_fixing_it(
- "Make sure that the address in config/gitlab.yml includes the '%{key}' placeholder."
- )
- fix_and_rerun
- end
- end
-
def check_initd_configured_correctly
print "Init.d configured correctly? ... "
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index 5b1f65d7aff..9ef8ba1b097 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -1,15 +1,14 @@
require 'spec_helper'
describe Admin::UsersController do
- let(:admin) { create(:admin) }
+ let(:user) { create(:user) }
before do
- sign_in(admin)
+ sign_in(create(:admin))
end
describe 'DELETE #user with projects' do
- let(:user) { create(:user) }
- let(:project) { create(:project, namespace: user.namespace) }
+ let(:project) { create(:empty_project, namespace: user.namespace) }
before do
project.team << [user, :developer]
@@ -23,8 +22,6 @@ describe Admin::UsersController do
end
describe 'PUT block/:id' do
- let(:user) { create(:user) }
-
it 'blocks user' do
put :block, id: user.username
user.reload
@@ -50,8 +47,6 @@ describe Admin::UsersController do
end
context 'manually blocked users' do
- let(:user) { create(:user) }
-
before do
user.block
end
@@ -66,8 +61,6 @@ describe Admin::UsersController do
end
describe 'PUT unlock/:id' do
- let(:user) { create(:user) }
-
before do
request.env["HTTP_REFERER"] = "/"
user.lock_access!
@@ -95,8 +88,6 @@ describe Admin::UsersController do
end
describe 'PATCH disable_two_factor' do
- let(:user) { create(:user) }
-
it 'disables 2FA for the user' do
expect(user).to receive(:disable_two_factor!)
allow(subject).to receive(:user).and_return(user)
diff --git a/spec/controllers/ci/projects_controller_spec.rb b/spec/controllers/ci/projects_controller_spec.rb
index db0748f323f..5022a3e2c80 100644
--- a/spec/controllers/ci/projects_controller_spec.rb
+++ b/spec/controllers/ci/projects_controller_spec.rb
@@ -5,6 +5,27 @@ describe Ci::ProjectsController do
let!(:project) { create(:project, visibility, ci_id: 1) }
let(:ci_id) { project.ci_id }
+ describe '#index' do
+ context 'user signed in' do
+ before do
+ sign_in(create(:user))
+ get(:index)
+ end
+
+ it 'redirects to /' do
+ expect(response).to redirect_to(root_path)
+ end
+ end
+
+ context 'user not signed in' do
+ before { get(:index) }
+
+ it 'redirects to sign in page' do
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+ end
+
##
# Specs for *deprecated* CI badge
#
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index c5b034dc064..75e6b6f45a7 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -63,7 +63,7 @@ describe Projects::MergeRequestsController do
id: merge_request.iid,
format: format)
- expect(response.body).to eq((merge_request.send(:"to_#{format}",user)).to_s)
+ expect(response.body).to eq((merge_request.send(:"to_#{format}")).to_s)
end
it "should not escape Html" do
diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb
new file mode 100644
index 00000000000..0f32a30f18b
--- /dev/null
+++ b/spec/controllers/projects/snippets_controller_spec.rb
@@ -0,0 +1,107 @@
+require 'spec_helper'
+
+describe Projects::SnippetsController do
+ let(:project) { create(:project_empty_repo, :public, snippets_enabled: true) }
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+
+ before do
+ project.team << [user, :master]
+ project.team << [user2, :master]
+ end
+
+ describe 'GET #index' do
+ context 'when the project snippet is private' do
+ let!(:project_snippet) { create(:project_snippet, :private, project: project, author: user) }
+
+ context 'when anonymous' do
+ it 'does not include the private snippet' do
+ get :index, namespace_id: project.namespace.path, project_id: project.path
+
+ expect(assigns(:snippets)).not_to include(project_snippet)
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context 'when signed in as the author' do
+ before { sign_in(user) }
+
+ it 'renders the snippet' do
+ get :index, namespace_id: project.namespace.path, project_id: project.path
+
+ expect(assigns(:snippets)).to include(project_snippet)
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context 'when signed in as a project member' do
+ before { sign_in(user2) }
+
+ it 'renders the snippet' do
+ get :index, namespace_id: project.namespace.path, project_id: project.path
+
+ expect(assigns(:snippets)).to include(project_snippet)
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+ end
+
+ %w[show raw].each do |action|
+ describe "GET ##{action}" do
+ context 'when the project snippet is private' do
+ let(:project_snippet) { create(:project_snippet, :private, project: project, author: user) }
+
+ context 'when anonymous' do
+ it 'responds with status 404' do
+ get action, namespace_id: project.namespace.path, project_id: project.path, id: project_snippet.to_param
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'when signed in as the author' do
+ before { sign_in(user) }
+
+ it 'renders the snippet' do
+ get action, namespace_id: project.namespace.path, project_id: project.path, id: project_snippet.to_param
+
+ expect(assigns(:snippet)).to eq(project_snippet)
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context 'when signed in as a project member' do
+ before { sign_in(user2) }
+
+ it 'renders the snippet' do
+ get action, namespace_id: project.namespace.path, project_id: project.path, id: project_snippet.to_param
+
+ expect(assigns(:snippet)).to eq(project_snippet)
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+
+ context 'when the project snippet does not exist' do
+ context 'when anonymous' do
+ it 'responds with status 404' do
+ get action, namespace_id: project.namespace.path, project_id: project.path, id: 42
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'when signed in' do
+ before { sign_in(user) }
+
+ it 'responds with status 404' do
+ get action, namespace_id: project.namespace.path, project_id: project.path, id: 42
+
+ expect(response.status).to eq(404)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/factories/file_uploader.rb b/spec/factories/file_uploader.rb
new file mode 100644
index 00000000000..1b36e21f2b0
--- /dev/null
+++ b/spec/factories/file_uploader.rb
@@ -0,0 +1,20 @@
+FactoryGirl.define do
+ factory :file_uploader do
+ project
+ secret nil
+
+ transient do
+ fixture { 'rails_sample.jpg' }
+ path { File.join(Rails.root, 'spec/fixtures', fixture) }
+ file { Rack::Test::UploadedFile.new(path) }
+ end
+
+ after(:build) do |uploader, evaluator|
+ uploader.store!(evaluator.file)
+ end
+
+ initialize_with do
+ new(project, secret)
+ end
+ end
+end
diff --git a/spec/factories_spec.rb b/spec/factories_spec.rb
index 457859dedaf..62de081661d 100644
--- a/spec/factories_spec.rb
+++ b/spec/factories_spec.rb
@@ -1,9 +1,17 @@
require 'spec_helper'
-FactoryGirl.factories.map(&:name).each do |factory_name|
- describe "#{factory_name} factory" do
- it 'should be valid' do
- expect(build(factory_name)).to be_valid
+describe 'factories' do
+ FactoryGirl.factories.each do |factory|
+ describe "#{factory.name} factory" do
+ let(:entity) { build(factory.name) }
+
+ it 'does not raise error when created 'do
+ expect { entity }.to_not raise_error
+ end
+
+ it 'should be valid', if: factory.build_class < ActiveRecord::Base do
+ expect(entity).to be_valid
+ end
end
end
end
diff --git a/spec/features/issues/filter_by_milestone_spec.rb b/spec/features/issues/filter_by_milestone_spec.rb
index f6e33f651c4..99445185893 100644
--- a/spec/features/issues/filter_by_milestone_spec.rb
+++ b/spec/features/issues/filter_by_milestone_spec.rb
@@ -11,7 +11,41 @@ feature 'Issue filtering by Milestone', feature: true do
visit_issues(project)
filter_by_milestone(Milestone::None.title)
- expect(page).to have_css('.issue .title', count: 1)
+ expect(page).to have_css('.issue', count: 1)
+ end
+
+ context 'filters by upcoming milestone', js: true do
+ it 'should not show issues with no expiry' do
+ create(:issue, project: project)
+ create(:issue, project: project, milestone: milestone)
+
+ visit_issues(project)
+ filter_by_milestone(Milestone::Upcoming.title)
+
+ expect(page).to have_css('.issue', count: 0)
+ end
+
+ it 'should show issues in future' do
+ milestone = create(:milestone, project: project, due_date: Date.tomorrow)
+ create(:issue, project: project)
+ create(:issue, project: project, milestone: milestone)
+
+ visit_issues(project)
+ filter_by_milestone(Milestone::Upcoming.title)
+
+ expect(page).to have_css('.issue', count: 1)
+ end
+
+ it 'should not show issues in past' do
+ milestone = create(:milestone, project: project, due_date: Date.yesterday)
+ create(:issue, project: project)
+ create(:issue, project: project, milestone: milestone)
+
+ visit_issues(project)
+ filter_by_milestone(Milestone::Upcoming.title)
+
+ expect(page).to have_css('.issue', count: 0)
+ end
end
scenario 'filters by a specific Milestone', js: true do
@@ -21,7 +55,7 @@ feature 'Issue filtering by Milestone', feature: true do
visit_issues(project)
filter_by_milestone(milestone.title)
- expect(page).to have_css('.issue .title', count: 1)
+ expect(page).to have_css('.issue', count: 1)
end
def visit_issues(project)
@@ -30,8 +64,6 @@ feature 'Issue filtering by Milestone', feature: true do
def filter_by_milestone(title)
find(".js-milestone-select").click
- sleep 0.5
- find(".milestone-filter a", text: title).click
- sleep 1
+ find(".milestone-filter .dropdown-content a", text: title).click
end
end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index e844e681ebf..db46657c36a 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -34,20 +34,7 @@ describe 'Issues', feature: true do
fill_in 'issue_title', with: 'bug 345'
fill_in 'issue_description', with: 'bug description'
end
-
- it 'does not change issue count' do
- expect { click_button 'Save changes' }.to_not change { Issue.count }
- end
-
- it 'should update issue fields' do
- click_button 'Save changes'
-
- expect(page).to have_content @user.name
- expect(page).to have_content 'bug 345'
- expect(page).to have_content project.name
- end
end
-
end
describe 'Editing issue assignee' do
@@ -70,7 +57,7 @@ describe 'Issues', feature: true do
click_button 'Save changes'
page.within('.assignee') do
- expect(page).to have_content 'None'
+ expect(page).to have_content 'No assignee - assign yourself'
end
expect(issue.reload.assignee).to be_nil
@@ -198,20 +185,26 @@ describe 'Issues', feature: true do
end
describe 'update assignee from issue#show' do
- let(:issue) { create(:issue, project: project, author: @user) }
+ let(:issue) { create(:issue, project: project, author: @user, assignee: @user) }
context 'by autorized user' do
- it 'with dropdown menu' do
+ it 'allows user to select unassigned', js: true do
visit namespace_project_issue_path(project.namespace, project, issue)
- find('.issuable-sidebar #issue_assignee_id').
- set project.team.members.first.id
- click_button 'Update Issue'
+ page.within('.assignee') do
+ expect(page).to have_content "#{@user.name}"
+ end
+
+ find('.block.assignee .edit-link').click
+ sleep 2 # wait for ajax stuff to complete
+ first('.dropdown-menu-user-link').click
+ sleep 2
+ page.within('.assignee') do
+ expect(page).to have_content 'No assignee'
+ end
- expect(page).to have_content 'Assignee'
- has_select?('issue_assignee_id',
- selected: project.team.members.first.name)
+ expect(issue.reload.assignee).to be_nil
end
end
@@ -221,8 +214,6 @@ describe 'Issues', feature: true do
before :each do
project.team << [[guest], :guest]
- issue.assignee = @user
- issue.save
end
it 'shows assignee text', js: true do
@@ -241,20 +232,23 @@ describe 'Issues', feature: true do
context 'by authorized user' do
- it 'with dropdown menu' do
- visit namespace_project_issue_path(project.namespace, project, issue)
- find('.issuable-sidebar').
- select(milestone.title, from: 'issue_milestone_id')
- click_button 'Update Issue'
+ it 'allows user to select unassigned', js: true do
+ visit namespace_project_issue_path(project.namespace, project, issue)
- expect(page).to have_content "Milestone changed to #{milestone.title}"
+ page.within('.milestone') do
+ expect(page).to have_content "None"
+ end
+ find('.block.milestone .edit-link').click
+ sleep 2 # wait for ajax stuff to complete
+ first('.dropdown-content li').click
+ sleep 2
page.within('.milestone') do
- expect(page).to have_content milestone.title
+ expect(page).to have_content 'None'
end
- has_select?('issue_assignee_id', selected: milestone.title)
+ expect(issue.reload.milestone).to be_nil
end
end
@@ -283,25 +277,6 @@ describe 'Issues', feature: true do
issue.assignee = user2
issue.save
end
-
- it 'allows user to remove assignee', js: true do
- visit namespace_project_issue_path(project.namespace, project, issue)
-
- page.within('.assignee') do
- expect(page).to have_content user2.name
- end
-
- find('.assignee .edit-link').click
- sleep 2 # wait for ajax stuff to complete
- first('.user-result').click
-
- page.within('.assignee') do
- expect(page).to have_content 'None'
- end
-
- sleep 2 # wait for ajax stuff to complete
- expect(issue.reload.assignee).to be_nil
- end
end
end
diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb
new file mode 100644
index 00000000000..fd02d584848
--- /dev/null
+++ b/spec/features/merge_requests/create_new_mr_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+feature 'Create New Merge Request', feature: true, js: false do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+
+ before do
+ project.team << [user, :master]
+
+ login_as user
+ visit namespace_project_merge_requests_path(project.namespace, project)
+ end
+
+ it 'generates a diff for an orphaned branch' do
+ click_link 'New Merge Request'
+ select "orphaned-branch", from: "merge_request_source_branch"
+ select "master", from: "merge_request_target_branch"
+ click_button "Compare branches"
+
+ expect(page).to have_content "README.md"
+ expect(page).to have_content "wm.png"
+
+ fill_in "merge_request_title", with: "Orphaned MR test"
+ click_button "Submit merge request"
+
+ expect(page).to have_content 'git checkout -b orphaned-branch origin/orphaned-branch'
+ end
+end
diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb
index b76e4c74c79..c57ab5f3b03 100644
--- a/spec/features/merge_requests/filter_by_milestone_spec.rb
+++ b/spec/features/merge_requests/filter_by_milestone_spec.rb
@@ -11,7 +11,41 @@ feature 'Merge Request filtering by Milestone', feature: true do
visit_merge_requests(project)
filter_by_milestone(Milestone::None.title)
- expect(page).to have_css('.merge-request-title', count: 1)
+ expect(page).to have_css('.merge-request', count: 1)
+ end
+
+ context 'filters by upcoming milestone', js: true do
+ it 'should not show issues with no expiry' do
+ create(:merge_request, :with_diffs, source_project: project)
+ create(:merge_request, :simple, source_project: project, milestone: milestone)
+
+ visit_merge_requests(project)
+ filter_by_milestone(Milestone::Upcoming.title)
+
+ expect(page).to have_css('.merge-request', count: 0)
+ end
+
+ it 'should show issues in future' do
+ milestone = create(:milestone, project: project, due_date: Date.tomorrow)
+ create(:merge_request, :with_diffs, source_project: project)
+ create(:merge_request, :simple, source_project: project, milestone: milestone)
+
+ visit_merge_requests(project)
+ filter_by_milestone(Milestone::Upcoming.title)
+
+ expect(page).to have_css('.merge-request', count: 1)
+ end
+
+ it 'should not show issues in past' do
+ milestone = create(:milestone, project: project, due_date: Date.yesterday)
+ create(:merge_request, :with_diffs, source_project: project)
+ create(:merge_request, :simple, source_project: project, milestone: milestone)
+
+ visit_merge_requests(project)
+ filter_by_milestone(Milestone::Upcoming.title)
+
+ expect(page).to have_css('.merge-request', count: 0)
+ end
end
scenario 'filters by a specific Milestone', js: true do
@@ -21,7 +55,7 @@ feature 'Merge Request filtering by Milestone', feature: true do
visit_merge_requests(project)
filter_by_milestone(milestone.title)
- expect(page).to have_css('.merge-request-title', count: 1)
+ expect(page).to have_css('.merge-request', count: 1)
end
def visit_merge_requests(project)
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
index 84c036e59c0..3e6289a46b1 100644
--- a/spec/features/search_spec.rb
+++ b/spec/features/search_spec.rb
@@ -1,19 +1,46 @@
require 'spec_helper'
describe "Search", feature: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace) }
+
before do
- login_as :user
- @project = create(:project, namespace: @user.namespace)
- @project.team << [@user, :reporter]
+ login_with(user)
+ project.team << [user, :reporter]
visit search_path
+ end
- page.within '.search-holder' do
- fill_in "search", with: @project.name[0..3]
- click_button "Search"
+ describe 'searching for Projects' do
+ it 'finds a project' do
+ page.within '.search-holder' do
+ fill_in "search", with: project.name[0..3]
+ click_button "Search"
+ end
+
+ expect(page).to have_content project.name
end
end
- it "should show project in search results" do
- expect(page).to have_content @project.name
+ context 'search for comments' do
+ it 'finds a snippet' do
+ snippet = create(:project_snippet, :private, project: project, author: user, title: 'Some title')
+ note = create(:note,
+ noteable: snippet,
+ author: user,
+ note: 'Supercalifragilisticexpialidocious',
+ project: project)
+ # Must visit project dashboard since global search won't search
+ # everything (e.g. comments, snippets, etc.)
+ visit namespace_project_path(project.namespace, project)
+
+ page.within '.search' do
+ fill_in 'search', with: note.note
+ click_button 'Go'
+ end
+
+ click_link 'Comments'
+
+ expect(page).to have_link(snippet.title)
+ end
end
end
diff --git a/spec/features/security/project/snippet/internal_access_spec.rb b/spec/features/security/project/snippet/internal_access_spec.rb
new file mode 100644
index 00000000000..db53a9cec97
--- /dev/null
+++ b/spec/features/security/project/snippet/internal_access_spec.rb
@@ -0,0 +1,78 @@
+require 'spec_helper'
+
+describe "Internal Project Snippets Access", feature: true do
+ include AccessMatchers
+
+ let(:project) { create(:project, :internal) }
+
+ let(:owner) { project.owner }
+ let(:master) { create(:user) }
+ let(:developer) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:guest) { create(:user) }
+ let(:internal_snippet) { create(:project_snippet, :internal, project: project, author: owner) }
+ let(:private_snippet) { create(:project_snippet, :private, project: project, author: owner) }
+
+ before do
+ project.team << [master, :master]
+ project.team << [developer, :developer]
+ project.team << [reporter, :reporter]
+ project.team << [guest, :guest]
+ end
+
+ describe "GET /:project_path/snippets" do
+ subject { namespace_project_snippets_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/snippets/new" do
+ subject { new_namespace_project_snippet_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_denied_for guest }
+ 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/snippets/:id for an internal snippet" do
+ subject { namespace_project_snippet_path(project.namespace, project, internal_snippet) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/snippets/:id for a private snippet" do
+ subject { namespace_project_snippet_path(project.namespace, project, private_snippet) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ 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
+end
diff --git a/spec/features/security/project/snippet/private_access_spec.rb b/spec/features/security/project/snippet/private_access_spec.rb
new file mode 100644
index 00000000000..d23d645c8e5
--- /dev/null
+++ b/spec/features/security/project/snippet/private_access_spec.rb
@@ -0,0 +1,63 @@
+require 'spec_helper'
+
+describe "Private Project Snippets Access", feature: true do
+ include AccessMatchers
+
+ let(:project) { create(:project, :private) }
+
+ let(:owner) { project.owner }
+ let(:master) { create(:user) }
+ let(:developer) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:guest) { create(:user) }
+ let(:private_snippet) { create(:project_snippet, :private, project: project, author: owner) }
+
+ before do
+ project.team << [master, :master]
+ project.team << [developer, :developer]
+ project.team << [reporter, :reporter]
+ project.team << [guest, :guest]
+ end
+
+ describe "GET /:project_path/snippets" do
+ subject { namespace_project_snippets_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ 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/snippets/new" do
+ subject { new_namespace_project_snippet_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_denied_for guest }
+ 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/snippets/:id for a private snippet" do
+ subject { namespace_project_snippet_path(project.namespace, project, private_snippet) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ 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
+end
diff --git a/spec/features/security/project/snippet/public_access_spec.rb b/spec/features/security/project/snippet/public_access_spec.rb
new file mode 100644
index 00000000000..e3665b6116a
--- /dev/null
+++ b/spec/features/security/project/snippet/public_access_spec.rb
@@ -0,0 +1,93 @@
+require 'spec_helper'
+
+describe "Public Project Snippets Access", feature: true do
+ include AccessMatchers
+
+ let(:project) { create(:project, :public) }
+
+ let(:owner) { project.owner }
+ let(:master) { create(:user) }
+ let(:developer) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:guest) { create(:user) }
+ let(:public_snippet) { create(:project_snippet, :public, project: project, author: owner) }
+ let(:internal_snippet) { create(:project_snippet, :internal, project: project, author: owner) }
+ let(:private_snippet) { create(:project_snippet, :private, project: project, author: owner) }
+
+ before do
+ project.team << [master, :master]
+ project.team << [developer, :developer]
+ project.team << [reporter, :reporter]
+ project.team << [guest, :guest]
+ end
+
+ describe "GET /:project_path/snippets" do
+ subject { namespace_project_snippets_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
+ it { is_expected.to be_allowed_for :visitor }
+ end
+
+ describe "GET /:project_path/snippets/new" do
+ subject { new_namespace_project_snippet_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_denied_for guest }
+ 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/snippets/:id for a public snippet" do
+ subject { namespace_project_snippet_path(project.namespace, project, public_snippet) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
+ it { is_expected.to be_allowed_for :visitor }
+ end
+
+ describe "GET /:project_path/snippets/:id for an internal snippet" do
+ subject { namespace_project_snippet_path(project.namespace, project, internal_snippet) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/snippets/:id for a private snippet" do
+ subject { namespace_project_snippet_path(project.namespace, project, private_snippet) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ 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
+end
diff --git a/spec/fixtures/emails/reply_without_subaddressing_and_key_inside_references.eml b/spec/fixtures/emails/reply_without_subaddressing_and_key_inside_references.eml
new file mode 100644
index 00000000000..39d5cefbc2a
--- /dev/null
+++ b/spec/fixtures/emails/reply_without_subaddressing_and_key_inside_references.eml
@@ -0,0 +1,42 @@
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <reply@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: reply@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+In-Reply-To: <issue_1@localhost>
+References: <issue_1@localhost> <reply-59d8df8370b7e95c5a49fbf86aeb2c93@localhost>
+Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux'
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
+ 13 Jun 2013 14:03:48 -0700 (PDT)
+X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
+
+I could not disagree more. I am obviously biased but adventure time is the
+greatest show ever created. Everyone should watch it.
+
+- Jake out
+
+
+On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta
+<reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo> wrote:
+>
+>
+>
+> eviltrout posted in 'Adventure Time Sux' on Discourse Meta:
+>
+> ---
+> hey guys everyone knows adventure time sucks!
+>
+> ---
+> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3
+>
+> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences).
+>
diff --git a/spec/fixtures/emails/valid_reply.eml b/spec/fixtures/emails/valid_reply.eml
index 1e696389954..980e10a8812 100644
--- a/spec/fixtures/emails/valid_reply.eml
+++ b/spec/fixtures/emails/valid_reply.eml
@@ -7,6 +7,8 @@ Date: Thu, 13 Jun 2013 17:03:48 -0400
From: Jake the Dog <jake@adventuretime.ooo>
To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo
Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+In-Reply-To: <issue_1@localhost>
+References: <issue_1@localhost> <reply-59d8df8370b7e95c5a49fbf86aeb2c93@localhost>
Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux'
Mime-Version: 1.0
Content-Type: text/plain;
@@ -37,4 +39,4 @@ On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta
> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3
>
> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences).
-> \ No newline at end of file
+>
diff --git a/spec/lib/award_emoji_spec.rb b/spec/lib/award_emoji_spec.rb
new file mode 100644
index 00000000000..330678f7f16
--- /dev/null
+++ b/spec/lib/award_emoji_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe AwardEmoji do
+ describe '.urls' do
+ subject { AwardEmoji.urls }
+
+ it { is_expected.to be_an_instance_of(Array) }
+ it { is_expected.to_not 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
+ end
+end
diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb
index f38fadda9ba..566035c60d0 100644
--- a/spec/lib/extracts_path_spec.rb
+++ b/spec/lib/extracts_path_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe ExtractsPath, lib: true do
include ExtractsPath
include RepoHelpers
- include Gitlab::Application.routes.url_helpers
+ include Gitlab::Routing.url_helpers
let(:project) { double('project') }
diff --git a/spec/lib/gitlab/badge/build_spec.rb b/spec/lib/gitlab/badge/build_spec.rb
new file mode 100644
index 00000000000..b78c2b6224f
--- /dev/null
+++ b/spec/lib/gitlab/badge/build_spec.rb
@@ -0,0 +1,72 @@
+require 'spec_helper'
+
+describe Gitlab::Badge::Build do
+ let(:project) { create(:project) }
+ let(:sha) { project.commit.sha }
+ let(:badge) { described_class.new(project, 'master') }
+
+ describe '#type' do
+ subject { badge.type }
+ it { is_expected.to eq 'image/svg+xml' }
+ end
+
+ context 'build exists' do
+ let(:ci_commit) { create(:ci_commit, project: project, sha: sha) }
+ let!(:build) { create(:ci_build, commit: ci_commit) }
+
+
+ context 'build success' do
+ before { build.success! }
+
+ describe '#to_s' do
+ subject { badge.to_s }
+ it { is_expected.to eq 'build-success' }
+ end
+
+ describe '#data' do
+ let(:data) { badge.data }
+
+ it 'contains infromation about success' do
+ expect(status_node(data, 'success')).to be_truthy
+ end
+ end
+ end
+
+ context 'build failed' do
+ before { build.drop! }
+
+ describe '#to_s' do
+ subject { badge.to_s }
+ it { is_expected.to eq 'build-failed' }
+ end
+
+ describe '#data' do
+ let(:data) { badge.data }
+
+ it 'contains infromation about failure' do
+ expect(status_node(data, 'failed')).to be_truthy
+ end
+ end
+ end
+ end
+
+ context 'build does not exist' do
+ describe '#to_s' do
+ subject { badge.to_s }
+ it { is_expected.to eq 'build-unknown' }
+ end
+
+ describe '#data' do
+ let(:data) { badge.data }
+
+ it 'contains infromation about unknown build' do
+ expect(status_node(data, 'unknown')).to be_truthy
+ end
+ end
+ end
+
+ def status_node(data, status)
+ xml = Nokogiri::XML.parse(data)
+ xml.at(%Q{text:contains("#{status}")})
+ end
+end
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index 844fd79c991..a1f51429a79 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -236,6 +236,6 @@ describe Gitlab::ClosingIssueExtractor, lib: true do
end
def urls
- Gitlab::Application.routes.url_helpers
+ Gitlab::Routing.url_helpers
end
end
diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb
index abe179cd4af..36267faeb93 100644
--- a/spec/lib/gitlab/email/receiver_spec.rb
+++ b/spec/lib/gitlab/email/receiver_spec.rb
@@ -3,6 +3,7 @@ require "spec_helper"
describe Gitlab::Email::Receiver, lib: true do
before do
stub_incoming_email_setting(enabled: true, address: "reply+%{key}@appmail.adventuretime.ooo")
+ stub_config_setting(host: 'localhost')
end
let(:reply_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" }
@@ -137,5 +138,27 @@ describe Gitlab::Email::Receiver, lib: true do
expect(note.note).to include(markdown)
end
+
+ context 'when sub-addressing is not supported' do
+ before do
+ stub_incoming_email_setting(enabled: true, address: nil)
+ end
+
+ shared_examples 'an email that contains a reply key' do |header|
+ it "fetches the reply key from the #{header} header and creates a comment" do
+ expect { receiver.execute }.to change { noteable.notes.count }.by(1)
+ note = noteable.notes.last
+
+ expect(note.author).to eq(sent_notification.recipient)
+ expect(note.note).to include('I could not disagree more.')
+ end
+ end
+
+ context 'reply key is in the References header' do
+ let(:email_raw) { fixture_file('emails/reply_without_subaddressing_and_key_inside_references.eml') }
+
+ it_behaves_like 'an email that contains a reply key', 'References'
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/fogbugz_import/client_spec.rb b/spec/lib/gitlab/fogbugz_import/client_spec.rb
new file mode 100644
index 00000000000..2dc71be0254
--- /dev/null
+++ b/spec/lib/gitlab/fogbugz_import/client_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+describe Gitlab::FogbugzImport::Client, lib: true do
+
+ let(:client) { described_class.new(uri: '', token: '') }
+ let(:one_user) { { 'people' => { 'person' => { "ixPerson" => "2", "sFullName" => "James" } } } }
+ let(:two_users) { { 'people' => { 'person' => [one_user, { "ixPerson" => "3" }] } } }
+
+ it 'retrieves user_map with one user' do
+ stub_api(one_user)
+
+ expect(client.user_map.count).to eq(1)
+ end
+
+ it 'retrieves user_map with two users' do
+ stub_api(two_users)
+
+ expect(client.user_map.count).to eq(2)
+ end
+
+ def stub_api(users)
+ allow_any_instance_of(::Fogbugz::Interface).to receive(:command).with(:listPeople).and_return(users)
+ end
+end
diff --git a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
new file mode 100644
index 00000000000..eda956e6f0a
--- /dev/null
+++ b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+describe Gitlab::Gfm::UploadsRewriter do
+ let(:user) { create(:user) }
+ let(:old_project) { create(:project) }
+ let(:new_project) { create(:project) }
+ let(:rewriter) { described_class.new(text, old_project, user) }
+
+ context 'text contains links to uploads' do
+ let(:image_uploader) do
+ build(:file_uploader, project: old_project)
+ end
+
+ let(:zip_uploader) do
+ build(:file_uploader, project: old_project,
+ fixture: 'ci_build_artifacts.zip')
+ end
+
+ let(:text) do
+ "Text and #{image_uploader.to_markdown} and #{zip_uploader.to_markdown}"
+ end
+
+ describe '#rewrite' do
+ let!(:new_text) { rewriter.rewrite(new_project) }
+
+ let(:old_files) { [image_uploader, zip_uploader].map(&:file) }
+ let(:new_files) do
+ described_class.new(new_text, new_project, user).files
+ end
+
+ let(:old_paths) { old_files.map(&:path) }
+ let(:new_paths) { new_files.map(&:path) }
+
+ it 'rewrites content' do
+ expect(new_text).to_not eq text
+ expect(new_text.length).to eq text.length
+ end
+
+ it 'copies files' do
+ expect(new_files).to all(exist)
+ expect(old_paths).to_not match_array new_paths
+ expect(old_paths).to all(include(old_project.path_with_namespace))
+ expect(new_paths).to all(include(new_project.path_with_namespace))
+ end
+
+ it 'does not remove old files' do
+ expect(old_files).to all(exist)
+ end
+
+ it 'generates a new secret for each file' do
+ expect(new_paths).to_not include image_uploader.secret
+ expect(new_paths).to_not include zip_uploader.secret
+ end
+ end
+
+ describe '#needs_rewrite?' do
+ subject { rewriter.needs_rewrite? }
+ it { is_expected.to eq true }
+ end
+
+ describe '#files' do
+ subject { rewriter.files }
+ it { is_expected.to be_an(Array) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb
index bcdba8d4c12..afb3e26f8fb 100644
--- a/spec/lib/gitlab/incoming_email_spec.rb
+++ b/spec/lib/gitlab/incoming_email_spec.rb
@@ -7,24 +7,8 @@ describe Gitlab::IncomingEmail, lib: true do
stub_incoming_email_setting(enabled: true)
end
- context "when the address is valid" do
- before do
- stub_incoming_email_setting(address: "replies+%{key}@example.com")
- end
-
- it "returns true" do
- expect(described_class.enabled?).to be_truthy
- end
- end
-
- context "when the address is invalid" do
- before do
- stub_incoming_email_setting(address: "replies@example.com")
- end
-
- it "returns false" do
- expect(described_class.enabled?).to be_falsey
- end
+ it 'returns true' do
+ expect(described_class.enabled?).to be_truthy
end
end
@@ -58,4 +42,10 @@ describe Gitlab::IncomingEmail, lib: true do
expect(described_class.key_from_address("replies+key@example.com")).to eq("key")
end
end
+
+ context 'self.key_from_fallback_reply_message_id' do
+ it 'returns reply key' do
+ expect(described_class.key_from_fallback_reply_message_id('reply-key@localhost')).to eq('key')
+ end
+ end
end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 9b47acfe0cd..631b5094f42 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -35,7 +35,9 @@ describe Notify do
subject { Notify.new_issue_email(issue.assignee_id, issue.id) }
it_behaves_like 'an assignee email'
- it_behaves_like 'an email starting a new thread', 'issue'
+ it_behaves_like 'an email starting a new thread with reply-by-email enabled' do
+ let(:model) { issue }
+ end
it_behaves_like 'it should show Gmail Actions View Issue link'
it_behaves_like 'an unsubscribeable thread'
@@ -73,9 +75,11 @@ describe Notify do
subject { Notify.reassigned_issue_email(recipient.id, issue.id, previous_assignee.id, current_user.id) }
it_behaves_like 'a multiple recipients email'
- it_behaves_like 'an answer to an existing thread', 'issue'
+ it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
+ let(:model) { issue }
+ end
it_behaves_like 'it should show Gmail Actions View Issue link'
- it_behaves_like "an unsubscribeable thread"
+ it_behaves_like 'an unsubscribeable thread'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -104,7 +108,9 @@ describe Notify do
subject { Notify.relabeled_issue_email(recipient.id, issue.id, %w[foo bar baz], current_user.id) }
it_behaves_like 'a multiple recipients email'
- it_behaves_like 'an answer to an existing thread', 'issue'
+ it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
+ let(:model) { issue }
+ end
it_behaves_like 'it should show Gmail Actions View Issue link'
it_behaves_like 'a user cannot unsubscribe through footer link'
it_behaves_like 'an email with a labels subscriptions link in its footer'
@@ -132,7 +138,9 @@ describe Notify do
let(:status) { 'closed' }
subject { Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user.id) }
- it_behaves_like 'an answer to an existing thread', 'issue'
+ it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
+ let(:model) { issue }
+ end
it_behaves_like 'it should show Gmail Actions View Issue link'
it_behaves_like 'an unsubscribeable thread'
@@ -163,7 +171,9 @@ describe Notify do
let(:new_issue) { create(:issue) }
subject { Notify.issue_moved_email(recipient, issue, new_issue, current_user) }
- it_behaves_like 'an answer to an existing thread', 'issue'
+ it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
+ let(:model) { issue }
+ end
it_behaves_like 'it should show Gmail Actions View Issue link'
it_behaves_like 'an unsubscribeable thread'
@@ -196,9 +206,11 @@ describe Notify do
subject { Notify.new_merge_request_email(merge_request.assignee_id, merge_request.id) }
it_behaves_like 'an assignee email'
- it_behaves_like 'an email starting a new thread', 'merge_request'
+ it_behaves_like 'an email starting a new thread with reply-by-email enabled' do
+ let(:model) { merge_request }
+ end
it_behaves_like 'it should show Gmail Actions View Merge request link'
- it_behaves_like "an unsubscribeable thread"
+ it_behaves_like 'an unsubscribeable thread'
it 'has the correct subject' do
is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
@@ -216,10 +228,6 @@ describe Notify do
is_expected.to have_body_text /#{merge_request.target_branch}/
end
- it 'has the correct message-id set' do
- is_expected.to have_header 'Message-ID', "<merge_request_#{merge_request.id}@#{Gitlab.config.gitlab.host}>"
- end
-
context 'when enabled email_author_in_body' do
before do
allow(current_application_settings).to receive(:email_author_in_body).and_return(true)
@@ -247,7 +255,9 @@ describe Notify do
subject { Notify.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id) }
it_behaves_like 'a multiple recipients email'
- it_behaves_like 'an answer to an existing thread', 'merge_request'
+ it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
+ let(:model) { merge_request }
+ end
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like "an unsubscribeable thread"
@@ -278,7 +288,9 @@ describe Notify do
subject { Notify.relabeled_merge_request_email(recipient.id, merge_request.id, %w[foo bar baz], current_user.id) }
it_behaves_like 'a multiple recipients email'
- it_behaves_like 'an answer to an existing thread', 'merge_request'
+ it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
+ let(:model) { merge_request }
+ end
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like 'a user cannot unsubscribe through footer link'
it_behaves_like 'an email with a labels subscriptions link in its footer'
@@ -306,9 +318,11 @@ describe Notify do
let(:status) { 'reopened' }
subject { Notify.merge_request_status_email(recipient.id, merge_request.id, status, current_user.id) }
- it_behaves_like 'an answer to an existing thread', 'merge_request'
+ it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
+ let(:model) { merge_request }
+ end
it_behaves_like 'it should show Gmail Actions View Merge request link'
- it_behaves_like "an unsubscribeable thread"
+ it_behaves_like 'an unsubscribeable thread'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -337,9 +351,11 @@ describe Notify do
subject { Notify.merged_merge_request_email(recipient.id, merge_request.id, merge_author.id) }
it_behaves_like 'a multiple recipients email'
- it_behaves_like 'an answer to an existing thread', 'merge_request'
+ it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
+ let(:model) { merge_request }
+ end
it_behaves_like 'it should show Gmail Actions View Merge request link'
- it_behaves_like "an unsubscribeable thread"
+ it_behaves_like 'an unsubscribeable thread'
it 'is sent as the merge author' do
sender = subject.header[:from].addrs[0]
@@ -456,9 +472,11 @@ describe Notify do
subject { Notify.note_commit_email(recipient.id, note.id) }
it_behaves_like 'a note email'
- it_behaves_like 'an answer to an existing thread', 'commit'
+ it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
+ let(:model) { commit }
+ end
it_behaves_like 'it should show Gmail Actions View Commit link'
- it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'a user cannot unsubscribe through footer link'
it 'has the correct subject' do
is_expected.to have_subject /#{commit.title} \(#{commit.short_id}\)/
@@ -477,7 +495,9 @@ describe Notify do
subject { Notify.note_merge_request_email(recipient.id, note.id) }
it_behaves_like 'a note email'
- it_behaves_like 'an answer to an existing thread', 'merge_request'
+ it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
+ let(:model) { merge_request }
+ end
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like 'an unsubscribeable thread'
@@ -498,7 +518,9 @@ describe Notify do
subject { Notify.note_issue_email(recipient.id, note.id) }
it_behaves_like 'a note email'
- it_behaves_like 'an answer to an existing thread', 'issue'
+ it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
+ let(:model) { issue }
+ end
it_behaves_like 'it should show Gmail Actions View Issue link'
it_behaves_like 'an unsubscribeable thread'
diff --git a/spec/mailers/shared/notify.rb b/spec/mailers/shared/notify.rb
index 6019af544d3..56a6dbf96f9 100644
--- a/spec/mailers/shared/notify.rb
+++ b/spec/mailers/shared/notify.rb
@@ -10,6 +10,13 @@ shared_context 'gitlab email notification' do
ActionMailer::Base.deliveries.clear
email = recipient.emails.create(email: "notifications@example.com")
recipient.update_attribute(:notification_email, email.email)
+ stub_incoming_email_setting(enabled: true, address: "reply+%{key}@#{Gitlab.config.gitlab.host}")
+ end
+end
+
+shared_context 'reply-by-email is enabled with incoming address without %{key}' do
+ before do
+ stub_incoming_email_setting(enabled: true, address: "reply@#{Gitlab.config.gitlab.host}")
end
end
@@ -46,25 +53,76 @@ shared_examples 'an email with X-GitLab headers containing project details' do
end
end
-shared_examples 'an email starting a new thread' do |message_id_prefix|
- include_examples 'an email with X-GitLab headers containing project details'
+shared_examples 'a new thread email with reply-by-email enabled' do
+ let(:regex) { /\A<reply\-(.*)@#{Gitlab.config.gitlab.host}>\Z/ }
+
+ it 'has a Message-ID header' do
+ is_expected.to have_header 'Message-ID', "<#{model.class.model_name.singular_route_key}_#{model.id}@#{Gitlab.config.gitlab.host}>"
+ end
- it 'has a discussion identifier' do
- is_expected.to have_header 'Message-ID', /<#{message_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
+ it 'has a References header' do
+ is_expected.to have_header 'References', regex
end
end
-shared_examples 'an answer to an existing thread' do |thread_id_prefix|
+shared_examples 'a thread answer email with reply-by-email enabled' do
include_examples 'an email with X-GitLab headers containing project details'
+ let(:regex) { /\A<#{model.class.model_name.singular_route_key}_#{model.id}@#{Gitlab.config.gitlab.host}> <reply\-(.*)@#{Gitlab.config.gitlab.host}>\Z/ }
+
+ it 'has a Message-ID header' do
+ is_expected.to have_header 'Message-ID', /\A<(.*)@#{Gitlab.config.gitlab.host}>\Z/
+ end
+
+ it 'has a In-Reply-To header' do
+ is_expected.to have_header 'In-Reply-To', "<#{model.class.model_name.singular_route_key}_#{model.id}@#{Gitlab.config.gitlab.host}>"
+ end
+
+ it 'has a References header' do
+ is_expected.to have_header 'References', regex
+ end
it 'has a subject that begins with Re: ' do
is_expected.to have_subject /^Re: /
end
+end
+
+shared_examples 'an email starting a new thread with reply-by-email enabled' do
+ include_examples 'an email with X-GitLab headers containing project details'
+ include_examples 'a new thread email with reply-by-email enabled'
+
+ context 'when reply-by-email is enabled with incoming address with %{key}' do
+ it 'has a Reply-To header' do
+ is_expected.to have_header 'Reply-To', /<reply+(.*)@#{Gitlab.config.gitlab.host}>\Z/
+ end
+ end
+
+ context 'when reply-by-email is enabled with incoming address without %{key}' do
+ include_context 'reply-by-email is enabled with incoming address without %{key}'
+ include_examples 'a new thread email with reply-by-email enabled'
+
+ it 'has a Reply-To header' do
+ is_expected.to have_header 'Reply-To', /<reply@#{Gitlab.config.gitlab.host}>\Z/
+ end
+ end
+end
+
+shared_examples 'an answer to an existing thread with reply-by-email enabled' do
+ include_examples 'an email with X-GitLab headers containing project details'
+ include_examples 'a thread answer email with reply-by-email enabled'
+
+ context 'when reply-by-email is enabled with incoming address with %{key}' do
+ it 'has a Reply-To header' do
+ is_expected.to have_header 'Reply-To', /<reply+(.*)@#{Gitlab.config.gitlab.host}>\Z/
+ end
+ end
+
+ context 'when reply-by-email is enabled with incoming address without %{key}' do
+ include_context 'reply-by-email is enabled with incoming address without %{key}'
+ include_examples 'a thread answer email with reply-by-email enabled'
- it 'has headers that reference an existing thread' do
- is_expected.to have_header 'Message-ID', /<(.*)@#{Gitlab.config.gitlab.host}>/
- is_expected.to have_header 'References', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
- is_expected.to have_header 'In-Reply-To', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
+ it 'has a Reply-To header' do
+ is_expected.to have_header 'Reply-To', /<reply@#{Gitlab.config.gitlab.host}>\Z/
+ end
end
end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index b1764d7ac09..520cf1b75de 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -12,7 +12,6 @@
# updated_at :datetime
# home_page_url :string(255)
# default_branch_protection :integer default(2)
-# twitter_sharing_enabled :boolean default(TRUE)
# restricted_visibility_levels :text
# version_check_enabled :boolean default(TRUE)
# max_attachment_size :integer default(10), not null
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index be29b6d66ff..b16ccc6e305 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -9,6 +9,7 @@ describe Issue, "Issuable" do
it { is_expected.to belong_to(:author) }
it { is_expected.to belong_to(:assignee) }
it { is_expected.to have_many(:notes).dependent(:destroy) }
+ it { is_expected.to have_many(:todos).dependent(:destroy) }
end
describe "Validation" do
diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
index fd1513cab1b..56a9fbe9720 100644
--- a/spec/models/hooks/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -20,24 +20,27 @@ require "spec_helper"
describe SystemHook, models: true do
describe "execute" do
- before(:each) do
- @system_hook = create(:system_hook)
- WebMock.stub_request(:post, @system_hook.url)
+ let(:system_hook) { create(:system_hook) }
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace) }
+ let(:group) { create(:group) }
+
+ before do
+ WebMock.stub_request(:post, system_hook.url)
end
it "project_create hook" do
- Projects::CreateService.new(create(:user), name: 'empty').execute
- expect(WebMock).to have_requested(:post, @system_hook.url).with(
+ Projects::CreateService.new(user, name: 'empty').execute
+ expect(WebMock).to have_requested(:post, system_hook.url).with(
body: /project_create/,
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
).once
end
it "project_destroy hook" do
- user = create(:user)
- project = create(:empty_project, namespace: user.namespace)
Projects::DestroyService.new(project, user, {}).pending_delete!
- expect(WebMock).to have_requested(:post, @system_hook.url).with(
+
+ expect(WebMock).to have_requested(:post, system_hook.url).with(
body: /project_destroy/,
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
).once
@@ -45,37 +48,36 @@ describe SystemHook, models: true do
it "user_create hook" do
create(:user)
- expect(WebMock).to have_requested(:post, @system_hook.url).with(
+
+ expect(WebMock).to have_requested(:post, system_hook.url).with(
body: /user_create/,
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
).once
end
it "user_destroy hook" do
- user = create(:user)
user.destroy
- expect(WebMock).to have_requested(:post, @system_hook.url).with(
+
+ expect(WebMock).to have_requested(:post, system_hook.url).with(
body: /user_destroy/,
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
).once
end
it "project_create hook" do
- user = create(:user)
- project = create(:project)
project.team << [user, :master]
- expect(WebMock).to have_requested(:post, @system_hook.url).with(
+
+ expect(WebMock).to have_requested(:post, system_hook.url).with(
body: /user_add_to_team/,
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
).once
end
it "project_destroy hook" do
- user = create(:user)
- project = create(:project)
project.team << [user, :master]
project.project_members.destroy_all
- expect(WebMock).to have_requested(:post, @system_hook.url).with(
+
+ expect(WebMock).to have_requested(:post, system_hook.url).with(
body: /user_remove_from_team/,
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
).once
@@ -83,41 +85,39 @@ describe SystemHook, models: true do
it 'group create hook' do
create(:group)
- expect(WebMock).to have_requested(:post, @system_hook.url).with(
+
+ expect(WebMock).to have_requested(:post, system_hook.url).with(
body: /group_create/,
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
).once
end
it 'group destroy hook' do
- group = create(:group)
group.destroy
- expect(WebMock).to have_requested(:post, @system_hook.url).with(
+
+ expect(WebMock).to have_requested(:post, system_hook.url).with(
body: /group_destroy/,
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
).once
end
it 'group member create hook' do
- group = create(:group)
- user = create(:user)
group.add_master(user)
- expect(WebMock).to have_requested(:post, @system_hook.url).with(
+
+ expect(WebMock).to have_requested(:post, system_hook.url).with(
body: /user_add_to_group/,
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
).once
end
it 'group member destroy hook' do
- group = create(:group)
- user = create(:user)
group.add_master(user)
group.group_members.destroy_all
- expect(WebMock).to have_requested(:post, @system_hook.url).with(
+
+ expect(WebMock).to have_requested(:post, system_hook.url).with(
body: /user_remove_from_group/,
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
).once
end
-
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index bd0a4ebe337..6f5d912fe5d 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -224,22 +224,22 @@ describe MergeRequest, models: true do
['WIP ', 'WIP:', 'WIP: ', '[WIP]', '[WIP] ', ' [WIP] WIP [WIP] WIP: WIP '].each do |wip_prefix|
it "detects the '#{wip_prefix}' prefix" do
subject.title = "#{wip_prefix}#{subject.title}"
- expect(subject).to be_work_in_progress
+ expect(subject.work_in_progress?).to eq true
end
end
it "doesn't detect WIP for words starting with WIP" do
subject.title = "Wipwap #{subject.title}"
- expect(subject).not_to be_work_in_progress
+ expect(subject.work_in_progress?).to eq false
end
it "doesn't detect WIP for words containing with WIP" do
subject.title = "WupWipwap #{subject.title}"
- expect(subject).not_to be_work_in_progress
+ expect(subject.work_in_progress?).to eq false
end
it "doesn't detect WIP by default" do
- expect(subject).not_to be_work_in_progress
+ expect(subject.work_in_progress?).to eq false
end
end
diff --git a/spec/models/project_services/slack_service/issue_message_spec.rb b/spec/models/project_services/slack_service/issue_message_spec.rb
index 97e6f03e308..f648cbe2dee 100644
--- a/spec/models/project_services/slack_service/issue_message_spec.rb
+++ b/spec/models/project_services/slack_service/issue_message_spec.rb
@@ -27,6 +27,16 @@ describe SlackService::IssueMessage, models: true do
let(:color) { '#345' }
+ context '#initialize' do
+ before do
+ args[:object_attributes][:description] = nil
+ end
+
+ it 'returns a non-null description' do
+ expect(subject.description).to eq('')
+ end
+ end
+
context 'open' do
it 'returns a message regarding opening of issues' do
expect(subject.pretext).to eq(
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 55f1c665b86..f29c389e094 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -104,6 +104,15 @@ describe Project, models: true do
end
end
+ describe 'default_scope' do
+ it 'excludes projects pending deletion from the results' do
+ project = create(:empty_project)
+ create(:empty_project, pending_delete: true)
+
+ expect(Project.all).to eq [project]
+ end
+ end
+
describe 'project token' do
it 'should set an random token if none provided' do
project = FactoryGirl.create :empty_project, runners_token: ''
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index f10d671104c..c5d5a1c2492 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -303,7 +303,7 @@ describe Repository, models: true do
describe 'when there are no branches' do
before do
- allow(repository.raw_repository).to receive(:branch_count).and_return(0)
+ allow(repository).to receive(:branch_count).and_return(0)
end
it { is_expected.to eq(false) }
@@ -311,13 +311,13 @@ describe Repository, models: true do
describe 'when there are branches' do
it 'returns true' do
- expect(repository.raw_repository).to receive(:branch_count).and_return(3)
+ expect(repository).to receive(:branch_count).and_return(3)
expect(subject).to eq(true)
end
it 'caches the output' do
- expect(repository.raw_repository).to receive(:branch_count).
+ expect(repository).to receive(:branch_count).
once.
and_return(3)
@@ -436,7 +436,7 @@ describe Repository, models: true do
it 'expires the visible content cache' do
repository.has_visible_content?
- expect(repository.raw_repository).to receive(:branch_count).
+ expect(repository).to receive(:branch_count).
once.
and_return(0)
@@ -558,7 +558,7 @@ describe Repository, models: true do
end
it 'flushes the exists cache' do
- expect(repository).to receive(:expire_exists_cache)
+ expect(repository).to receive(:expire_exists_cache).twice
repository.before_delete
end
@@ -865,4 +865,21 @@ describe Repository, models: true do
repository.build_cache
end
end
+
+ describe '#local_branches' do
+ it 'returns the local branches' do
+ masterrev = repository.find_branch('master').target
+ create_remote_branch('joe', 'remote_branch', masterrev)
+ repository.add_branch(user, 'local_branch', masterrev)
+
+ expect(repository.local_branches.any? { |branch| branch.name == 'remote_branch' }).to eq(false)
+ expect(repository.local_branches.any? { |branch| branch.name == 'local_branch' }).to eq(true)
+ end
+ end
+
+ def create_remote_branch(remote_name, branch_name, target)
+ rugged = repository.rugged
+ rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", target)
+ end
+
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 0ab7fd88ce6..8b2fb77e28e 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -173,6 +173,13 @@ describe User, models: true do
expect(user).to be_invalid
end
end
+
+ context 'owns_notification_email' do
+ it 'accepts temp_oauth_email emails' do
+ user = build(:user, email: "temp-email-for-oauth@example.com")
+ expect(user).to be_valid
+ end
+ end
end
end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index ce55cb7b0ae..822d3ad3017 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -318,6 +318,17 @@ describe API::API, api: true do
'is too long (maximum is 255 characters)'
])
end
+
+ context 'when an admin or owner makes the request' do
+ it "accepts the creation date to be set" do
+ post api("/projects/#{project.id}/issues", user),
+ title: 'new issue', labels: 'label, label2', created_at: 2.weeks.ago
+
+ expect(response.status).to eq(201)
+ # this take about a second, so probably not equal
+ expect(Time.parse(json_response['created_at'])).to be <= 2.weeks.ago
+ end
+ end
end
describe 'POST /projects/:id/issues with spam filtering' do
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index 667f0dbea5c..6943ff9d26c 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -23,13 +23,25 @@ describe API::API, api: true do
end
describe 'POST /projects/:id/labels' do
- it 'should return created label' do
+ it 'should return created label when all params' do
+ post api("/projects/#{project.id}/labels", user),
+ name: 'Foo',
+ color: '#FFAABB',
+ description: 'test'
+ expect(response.status).to eq(201)
+ expect(json_response['name']).to eq('Foo')
+ expect(json_response['color']).to eq('#FFAABB')
+ expect(json_response['description']).to eq('test')
+ end
+
+ it 'should return created label when only required params' do
post api("/projects/#{project.id}/labels", user),
name: 'Foo',
color: '#FFAABB'
expect(response.status).to eq(201)
expect(json_response['name']).to eq('Foo')
expect(json_response['color']).to eq('#FFAABB')
+ expect(json_response['description']).to be_nil
end
it 'should return a 400 bad request if name not given' do
@@ -94,14 +106,16 @@ describe API::API, api: true do
end
describe 'PUT /projects/:id/labels' do
- it 'should return 200 if name and colors are changed' do
+ it 'should return 200 if name and colors and description are changed' do
put api("/projects/#{project.id}/labels", user),
name: 'label1',
new_name: 'New Label',
- color: '#FFFFFF'
+ color: '#FFFFFF',
+ description: 'test'
expect(response.status).to eq(200)
expect(json_response['name']).to eq('New Label')
expect(json_response['color']).to eq('#FFFFFF')
+ expect(json_response['description']).to eq('test')
end
it 'should return 200 if name is changed' do
@@ -122,6 +136,15 @@ describe API::API, api: true do
expect(json_response['color']).to eq('#FFFFFF')
end
+ it 'should return 200 if description is changed' do
+ put api("/projects/#{project.id}/labels", user),
+ name: 'label1',
+ description: 'test'
+ expect(response.status).to eq(200)
+ expect(json_response['name']).to eq(label1.name)
+ expect(json_response['description']).to eq('test')
+ end
+
it 'should return 404 if label does not exist' do
put api("/projects/#{project.id}/labels", user),
name: 'label2',
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index c9175a4d6eb..25fa30b2f21 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -118,6 +118,7 @@ describe API::API, api: true do
expect(response.status).to eq(200)
expect(json_response['title']).to eq(merge_request.title)
expect(json_response['iid']).to eq(merge_request.iid)
+ expect(json_response['work_in_progress']).to eq(false)
expect(json_response['merge_status']).to eq('can_be_merged')
end
@@ -133,6 +134,16 @@ describe API::API, api: true do
get api("/projects/#{project.id}/merge_requests/999", user)
expect(response.status).to eq(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 "should return merge_request" do
+ get api("/projects/#{project.id}/merge_requests/#{merge_request_wip.id}", user)
+ expect(response.status).to eq(200)
+ expect(json_response['work_in_progress']).to eq(true)
+ end
+ end
end
describe 'GET /projects/:id/merge_requests/:merge_request_id/commits' do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index a5d4985dc78..be2034e0f39 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -948,6 +948,78 @@ describe API::API, api: true do
end
end
+ describe 'POST /projects/:id/archive' do
+ context 'on an unarchived project' do
+ it 'archives the project' do
+ post api("/projects/#{project.id}/archive", user)
+
+ expect(response.status).to eq(201)
+ expect(json_response['archived']).to be_truthy
+ end
+ end
+
+ context 'on an archived project' do
+ before do
+ project.archive!
+ end
+
+ it 'remains archived' do
+ post api("/projects/#{project.id}/archive", user)
+
+ expect(response.status).to eq(201)
+ expect(json_response['archived']).to be_truthy
+ end
+ end
+
+ context 'user without archiving rights to the project' do
+ before do
+ project.team << [user3, :developer]
+ end
+
+ it 'rejects the action' do
+ post api("/projects/#{project.id}/archive", user3)
+
+ expect(response.status).to eq(403)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/unarchive' do
+ context 'on an unarchived project' do
+ it 'remains unarchived' do
+ post api("/projects/#{project.id}/unarchive", user)
+
+ expect(response.status).to eq(201)
+ expect(json_response['archived']).to be_falsey
+ end
+ end
+
+ context 'on an archived project' do
+ before do
+ project.archive!
+ end
+
+ it 'unarchives the project' do
+ post api("/projects/#{project.id}/unarchive", user)
+
+ expect(response.status).to eq(201)
+ expect(json_response['archived']).to be_falsey
+ end
+ end
+
+ context 'user without archiving rights to the project' do
+ before do
+ project.team << [user3, :developer]
+ end
+
+ it 'rejects the action' do
+ post api("/projects/#{project.id}/unarchive", user3)
+
+ expect(response.status).to eq(403)
+ end
+ end
+ end
+
describe 'DELETE /projects/:id' do
context 'when authenticated as user' do
it 'should remove project' do
diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb
index 9b0c73aaf37..2a5e4ac3ec4 100644
--- a/spec/services/issues/move_service_spec.rb
+++ b/spec/services/issues/move_service_spec.rb
@@ -160,6 +160,20 @@ describe Issues::MoveService, services: true do
.to eq "Note with reference to merge request #{old_project.to_reference}!1"
end
end
+
+ context 'issue description with uploads' do
+ let(:uploader) { build(:file_uploader, project: old_project) }
+ let(:description) { "Text and #{uploader.to_markdown}" }
+
+ include_context 'issue move executed'
+
+ it 'rewrites uploads in description' do
+ expect(new_issue.description).to_not eq description
+ expect(new_issue.description)
+ .to match(/Text and #{FileUploader::MARKDOWN_PATTERN}/)
+ expect(new_issue.description).to_not include uploader.secret
+ end
+ end
end
describe 'rewritting references' do
diff --git a/spec/services/projects/unlink_fork_service_spec.rb b/spec/services/projects/unlink_fork_service_spec.rb
new file mode 100644
index 00000000000..23f5555d3e0
--- /dev/null
+++ b/spec/services/projects/unlink_fork_service_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe Projects::UnlinkForkService, services: true do
+ subject { Projects::UnlinkForkService.new(fork_project, user) }
+
+ let(:fork_link) { create(:forked_project_link) }
+ let(:fork_project) { fork_link.forked_to_project }
+ let(:user) { create(:user) }
+
+ context 'with opened merge request on the source project' do
+ let(:merge_request) { create(:merge_request, source_project: fork_project, target_project: fork_link.forked_from_project) }
+ let(:mr_close_service) { MergeRequests::CloseService.new(fork_project, user) }
+
+ before do
+ allow(MergeRequests::CloseService).to receive(:new).
+ with(fork_project, user).
+ and_return(mr_close_service)
+ end
+
+ it 'close all pending merge requests' do
+ expect(mr_close_service).to receive(:execute).with(merge_request)
+
+ subject.execute
+ end
+ end
+
+ it 'remove fork relation' do
+ expect(fork_project.forked_project_link).to receive(:destroy)
+
+ subject.execute
+ end
+end
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index b4728807b8b..82b7fbfa816 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -2,22 +2,25 @@ require 'spec_helper'
describe TodoService, services: true do
let(:author) { create(:user) }
- let(:john_doe) { create(:user, username: 'john_doe') }
- let(:michael) { create(:user, username: 'michael') }
- let(:stranger) { create(:user, username: 'stranger') }
+ let(:assignee) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:member) { create(:user) }
+ let(:admin) { create(:admin) }
+ let(:john_doe) { create(:user) }
let(:project) { create(:project) }
- let(:mentions) { [author.to_reference, john_doe.to_reference, michael.to_reference, stranger.to_reference].join(' ') }
+ let(:mentions) { [author, assignee, john_doe, member, non_member, admin].map(&:to_reference).join(' ') }
let(:service) { described_class.new }
before do
project.team << [author, :developer]
+ project.team << [member, :developer]
project.team << [john_doe, :developer]
- project.team << [michael, :developer]
end
describe 'Issues' do
let(:issue) { create(:issue, project: project, assignee: john_doe, author: author, description: mentions) }
let(:unassigned_issue) { create(:issue, project: project, assignee: nil) }
+ let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee, description: mentions) }
describe '#new_issue' do
it 'creates a todo if assigned' do
@@ -37,10 +40,20 @@ describe TodoService, services: true do
it 'creates a todo for each valid mentioned user' do
service.new_issue(issue, author)
- should_create_todo(user: michael, target: issue, action: Todo::MENTIONED)
+ should_create_todo(user: member, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED)
- should_not_create_todo(user: stranger, target: issue, action: Todo::MENTIONED)
+ should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED)
+ end
+
+ it 'does not create todo for non project members when issue is confidential' do
+ service.new_issue(confidential_issue, john_doe)
+
+ should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::ASSIGNED)
+ should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
+ should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
+ should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
+ should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
end
end
@@ -48,16 +61,26 @@ describe TodoService, services: true do
it 'creates a todo for each valid mentioned user' do
service.update_issue(issue, author)
- should_create_todo(user: michael, target: issue, action: Todo::MENTIONED)
+ should_create_todo(user: member, target: issue, action: Todo::MENTIONED)
should_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED)
- should_not_create_todo(user: stranger, target: issue, action: Todo::MENTIONED)
+ should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED)
end
it 'does not create a todo if user was already mentioned' do
- create(:todo, :mentioned, user: michael, project: project, target: issue, author: author)
+ create(:todo, :mentioned, user: member, project: project, target: issue, author: author)
- expect { service.update_issue(issue, author) }.not_to change(michael.todos, :count)
+ expect { service.update_issue(issue, author) }.not_to change(member.todos, :count)
+ end
+
+ it 'does not create todo for non project members when issue is confidential' do
+ service.update_issue(confidential_issue, john_doe)
+
+ should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
+ should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
+ should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
+ should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
+ should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
end
end
@@ -109,8 +132,10 @@ describe TodoService, services: true do
describe '#new_note' do
let!(:first_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) }
let!(:second_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) }
+ let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee) }
let(:note) { create(:note, project: project, noteable: issue, author: john_doe, note: mentions) }
let(:note_on_commit) { create(:note_on_commit, project: project, author: john_doe, note: mentions) }
+ let(:note_on_confidential_issue) { create(:note_on_issue, noteable: confidential_issue, project: project, note: mentions) }
let(:note_on_project_snippet) { create(:note_on_project_snippet, project: project, author: john_doe, note: mentions) }
let(:award_note) { create(:note, :award, project: project, noteable: issue, author: john_doe, note: 'thumbsup') }
let(:system_note) { create(:system_note, project: project, noteable: issue) }
@@ -142,19 +167,29 @@ describe TodoService, services: true do
it 'creates a todo for each valid mentioned user' do
service.new_note(note, john_doe)
- should_create_todo(user: michael, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
+ should_create_todo(user: member, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
should_create_todo(user: author, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
should_not_create_todo(user: john_doe, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
- should_not_create_todo(user: stranger, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
+ should_not_create_todo(user: non_member, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
+ end
+
+ it 'does not create todo for non project members when leaving a note on a confidential issue' do
+ service.new_note(note_on_confidential_issue, john_doe)
+
+ should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
+ should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
+ should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
+ should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
+ should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
end
it 'creates a todo for each valid mentioned user when leaving a note on commit' do
service.new_note(note_on_commit, john_doe)
- should_create_todo(user: michael, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
+ should_create_todo(user: member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
should_create_todo(user: author, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
should_not_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
- should_not_create_todo(user: stranger, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
+ should_not_create_todo(user: non_member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
end
it 'does not create todo when leaving a note on snippet' do
@@ -185,10 +220,10 @@ describe TodoService, services: true do
it 'creates a todo for each valid mentioned user' do
service.new_merge_request(mr_assigned, author)
- should_create_todo(user: michael, target: mr_assigned, action: Todo::MENTIONED)
+ should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED)
- should_not_create_todo(user: stranger, target: mr_assigned, action: Todo::MENTIONED)
+ should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED)
end
end
@@ -196,16 +231,16 @@ describe TodoService, services: true do
it 'creates a todo for each valid mentioned user' do
service.update_merge_request(mr_assigned, author)
- should_create_todo(user: michael, target: mr_assigned, action: Todo::MENTIONED)
+ should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED)
should_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED)
- should_not_create_todo(user: stranger, target: mr_assigned, action: Todo::MENTIONED)
+ should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED)
end
it 'does not create a todo if user was already mentioned' do
- create(:todo, :mentioned, user: michael, project: project, target: mr_assigned, author: author)
+ create(:todo, :mentioned, user: member, project: project, target: mr_assigned, author: author)
- expect { service.update_merge_request(mr_assigned, author) }.not_to change(michael.todos, :count)
+ expect { service.update_merge_request(mr_assigned, author) }.not_to change(member.todos, :count)
end
end
diff --git a/spec/support/carrierwave.rb b/spec/support/carrierwave.rb
new file mode 100644
index 00000000000..aa89afd8fb3
--- /dev/null
+++ b/spec/support/carrierwave.rb
@@ -0,0 +1,7 @@
+CarrierWave.root = 'tmp/tests/uploads'
+
+RSpec.configure do |config|
+ config.after(:suite) do
+ FileUtils.rm_rf('tmp/tests/uploads')
+ end
+end
diff --git a/spec/support/filter_spec_helper.rb b/spec/support/filter_spec_helper.rb
index ef5ea7d626e..e849a9633b9 100644
--- a/spec/support/filter_spec_helper.rb
+++ b/spec/support/filter_spec_helper.rb
@@ -78,6 +78,6 @@ module FilterSpecHelper
# Shortcut to Rails' auto-generated routes helpers, to avoid including the
# module
def urls
- Gitlab::Application.routes.url_helpers
+ Gitlab::Routing.url_helpers
end
end
diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb
index 73c6792b65f..b87cd6bbca2 100644
--- a/spec/support/markdown_feature.rb
+++ b/spec/support/markdown_feature.rb
@@ -106,7 +106,7 @@ class MarkdownFeature
end
def urls
- Gitlab::Application.routes.url_helpers
+ Gitlab::Routing.url_helpers
end
def raw_markdown
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 0d1bd030f3c..71664bb192e 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -15,6 +15,7 @@ module TestEnv
'lfs' => 'be93687',
'master' => '5937ac0',
"'test'" => 'e56497b',
+ 'orphaned-branch' => '45127a9',
}
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index 320be9a0b61..05fc4c4554f 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -7,8 +7,12 @@ describe 'gitlab:app namespace rake task' do
Rake.application.rake_require 'tasks/gitlab/backup'
Rake.application.rake_require 'tasks/gitlab/shell'
Rake.application.rake_require 'tasks/gitlab/db'
+
# empty task as env is already loaded
Rake::Task.define_task :environment
+
+ # We need this directory to run `gitlab:backup:create` task
+ FileUtils.mkdir_p('public/uploads')
end
def run_rake_task(task_name)
diff --git a/spec/workers/merge_worker_spec.rb b/spec/workers/merge_worker_spec.rb
index b11c5de94e3..1abd87d7d33 100644
--- a/spec/workers/merge_worker_spec.rb
+++ b/spec/workers/merge_worker_spec.rb
@@ -22,6 +22,8 @@ describe MergeWorker do
merge_request.reload
expect(merge_request).to be_merged
+
+ source_project.repository.expire_branches_cache
expect(source_project.repository.branch_names).not_to include('markdown')
end
end
diff --git a/spec/workers/project_cache_worker_spec.rb b/spec/workers/project_cache_worker_spec.rb
new file mode 100644
index 00000000000..7e59bd2fced
--- /dev/null
+++ b/spec/workers/project_cache_worker_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe ProjectCacheWorker do
+ let(:project) { create(:project) }
+
+ subject { described_class.new }
+
+ describe '#perform' do
+ it 'updates project cache data' do
+
+ expect_any_instance_of(Repository).to receive(:size)
+ expect_any_instance_of(Repository).to receive(:commit_count)
+
+ expect_any_instance_of(Project).to receive(:update_repository_size)
+ expect_any_instance_of(Project).to receive(:update_commit_count)
+
+ subject.perform(project.id)
+ end
+
+ it 'handles missing repository data' do
+ expect_any_instance_of(Repository).to receive(:exists?).and_return(false)
+ expect_any_instance_of(Repository).not_to receive(:size)
+
+ subject.perform(project.id)
+ end
+ end
+end
diff --git a/vendor/assets/stylesheets/animate.css b/vendor/assets/stylesheets/animate.css
new file mode 100644
index 00000000000..b6f61295392
--- /dev/null
+++ b/vendor/assets/stylesheets/animate.css
@@ -0,0 +1,11 @@
+@charset "UTF-8";
+
+/*!
+ * animate.css -http://daneden.me/animate
+ * Version - 3.5.1
+ * Licensed under the MIT license - http://opensource.org/licenses/MIT
+ *
+ * Copyright (c) 2016 Daniel Eden
+ */
+
+.animated{-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-fill-mode:both;animation-fill-mode:both}.animated.infinite{-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.animated.hinge{-webkit-animation-duration:2s;animation-duration:2s}.animated.bounceIn,.animated.bounceOut,.animated.flipOutX,.animated.flipOutY{-webkit-animation-duration:.75s;animation-duration:.75s}@-webkit-keyframes bounce{0%,20%,53%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1);-webkit-transform:translateZ(0);transform:translateZ(0)}40%,43%{-webkit-transform:translate3d(0,-30px,0);transform:translate3d(0,-30px,0)}40%,43%,70%{-webkit-animation-timing-function:cubic-bezier(.755,.05,.855,.06);animation-timing-function:cubic-bezier(.755,.05,.855,.06)}70%{-webkit-transform:translate3d(0,-15px,0);transform:translate3d(0,-15px,0)}90%{-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0)}}@keyframes bounce{0%,20%,53%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1);-webkit-transform:translateZ(0);transform:translateZ(0)}40%,43%{-webkit-transform:translate3d(0,-30px,0);transform:translate3d(0,-30px,0)}40%,43%,70%{-webkit-animation-timing-function:cubic-bezier(.755,.05,.855,.06);animation-timing-function:cubic-bezier(.755,.05,.855,.06)}70%{-webkit-transform:translate3d(0,-15px,0);transform:translate3d(0,-15px,0)}90%{-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0)}}.bounce{-webkit-animation-name:bounce;animation-name:bounce;-webkit-transform-origin:center bottom;transform-origin:center bottom}@-webkit-keyframes flash{0%,50%,to{opacity:1}25%,75%{opacity:0}}@keyframes flash{0%,50%,to{opacity:1}25%,75%{opacity:0}}.flash{-webkit-animation-name:flash;animation-name:flash}@-webkit-keyframes pulse{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}50%{-webkit-transform:scale3d(1.05,1.05,1.05);transform:scale3d(1.05,1.05,1.05)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes pulse{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}50%{-webkit-transform:scale3d(1.05,1.05,1.05);transform:scale3d(1.05,1.05,1.05)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}.pulse{-webkit-animation-name:pulse;animation-name:pulse}@-webkit-keyframes rubberBand{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}30%{-webkit-transform:scale3d(1.25,.75,1);transform:scale3d(1.25,.75,1)}40%{-webkit-transform:scale3d(.75,1.25,1);transform:scale3d(.75,1.25,1)}50%{-webkit-transform:scale3d(1.15,.85,1);transform:scale3d(1.15,.85,1)}65%{-webkit-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)}75%{-webkit-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes rubberBand{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}30%{-webkit-transform:scale3d(1.25,.75,1);transform:scale3d(1.25,.75,1)}40%{-webkit-transform:scale3d(.75,1.25,1);transform:scale3d(.75,1.25,1)}50%{-webkit-transform:scale3d(1.15,.85,1);transform:scale3d(1.15,.85,1)}65%{-webkit-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)}75%{-webkit-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}.rubberBand{-webkit-animation-name:rubberBand;animation-name:rubberBand}@-webkit-keyframes shake{0%,to{-webkit-transform:translateZ(0);transform:translateZ(0)}10%,30%,50%,70%,90%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}20%,40%,60%,80%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}}@keyframes shake{0%,to{-webkit-transform:translateZ(0);transform:translateZ(0)}10%,30%,50%,70%,90%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}20%,40%,60%,80%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}}.shake{-webkit-animation-name:shake;animation-name:shake}@-webkit-keyframes headShake{0%{-webkit-transform:translateX(0);transform:translateX(0)}6.5%{-webkit-transform:translateX(-6px) rotateY(-9deg);transform:translateX(-6px) rotateY(-9deg)}18.5%{-webkit-transform:translateX(5px) rotateY(7deg);transform:translateX(5px) rotateY(7deg)}31.5%{-webkit-transform:translateX(-3px) rotateY(-5deg);transform:translateX(-3px) rotateY(-5deg)}43.5%{-webkit-transform:translateX(2px) rotateY(3deg);transform:translateX(2px) rotateY(3deg)}50%{-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes headShake{0%{-webkit-transform:translateX(0);transform:translateX(0)}6.5%{-webkit-transform:translateX(-6px) rotateY(-9deg);transform:translateX(-6px) rotateY(-9deg)}18.5%{-webkit-transform:translateX(5px) rotateY(7deg);transform:translateX(5px) rotateY(7deg)}31.5%{-webkit-transform:translateX(-3px) rotateY(-5deg);transform:translateX(-3px) rotateY(-5deg)}43.5%{-webkit-transform:translateX(2px) rotateY(3deg);transform:translateX(2px) rotateY(3deg)}50%{-webkit-transform:translateX(0);transform:translateX(0)}}.headShake{-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;-webkit-animation-name:headShake;animation-name:headShake}@-webkit-keyframes swing{20%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}40%{-webkit-transform:rotate(-10deg);transform:rotate(-10deg)}60%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}80%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}@keyframes swing{20%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}40%{-webkit-transform:rotate(-10deg);transform:rotate(-10deg)}60%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}80%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}.swing{-webkit-transform-origin:top center;transform-origin:top center;-webkit-animation-name:swing;animation-name:swing}@-webkit-keyframes tada{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}10%,20%{-webkit-transform:scale3d(.9,.9,.9) rotate(-3deg);transform:scale3d(.9,.9,.9) rotate(-3deg)}30%,50%,70%,90%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate(3deg);transform:scale3d(1.1,1.1,1.1) rotate(3deg)}40%,60%,80%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate(-3deg);transform:scale3d(1.1,1.1,1.1) rotate(-3deg)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes tada{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}10%,20%{-webkit-transform:scale3d(.9,.9,.9) rotate(-3deg);transform:scale3d(.9,.9,.9) rotate(-3deg)}30%,50%,70%,90%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate(3deg);transform:scale3d(1.1,1.1,1.1) rotate(3deg)}40%,60%,80%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate(-3deg);transform:scale3d(1.1,1.1,1.1) rotate(-3deg)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}.tada{-webkit-animation-name:tada;animation-name:tada}@-webkit-keyframes wobble{0%{-webkit-transform:none;transform:none}15%{-webkit-transform:translate3d(-25%,0,0) rotate(-5deg);transform:translate3d(-25%,0,0) rotate(-5deg)}30%{-webkit-transform:translate3d(20%,0,0) rotate(3deg);transform:translate3d(20%,0,0) rotate(3deg)}45%{-webkit-transform:translate3d(-15%,0,0) rotate(-3deg);transform:translate3d(-15%,0,0) rotate(-3deg)}60%{-webkit-transform:translate3d(10%,0,0) rotate(2deg);transform:translate3d(10%,0,0) rotate(2deg)}75%{-webkit-transform:translate3d(-5%,0,0) rotate(-1deg);transform:translate3d(-5%,0,0) rotate(-1deg)}to{-webkit-transform:none;transform:none}}@keyframes wobble{0%{-webkit-transform:none;transform:none}15%{-webkit-transform:translate3d(-25%,0,0) rotate(-5deg);transform:translate3d(-25%,0,0) rotate(-5deg)}30%{-webkit-transform:translate3d(20%,0,0) rotate(3deg);transform:translate3d(20%,0,0) rotate(3deg)}45%{-webkit-transform:translate3d(-15%,0,0) rotate(-3deg);transform:translate3d(-15%,0,0) rotate(-3deg)}60%{-webkit-transform:translate3d(10%,0,0) rotate(2deg);transform:translate3d(10%,0,0) rotate(2deg)}75%{-webkit-transform:translate3d(-5%,0,0) rotate(-1deg);transform:translate3d(-5%,0,0) rotate(-1deg)}to{-webkit-transform:none;transform:none}}.wobble{-webkit-animation-name:wobble;animation-name:wobble}@-webkit-keyframes jello{0%,11.1%,to{-webkit-transform:none;transform:none}22.2%{-webkit-transform:skewX(-12.5deg) skewY(-12.5deg);transform:skewX(-12.5deg) skewY(-12.5deg)}33.3%{-webkit-transform:skewX(6.25deg) skewY(6.25deg);transform:skewX(6.25deg) skewY(6.25deg)}44.4%{-webkit-transform:skewX(-3.125deg) skewY(-3.125deg);transform:skewX(-3.125deg) skewY(-3.125deg)}55.5%{-webkit-transform:skewX(1.5625deg) skewY(1.5625deg);transform:skewX(1.5625deg) skewY(1.5625deg)}66.6%{-webkit-transform:skewX(-.78125deg) skewY(-.78125deg);transform:skewX(-.78125deg) skewY(-.78125deg)}77.7%{-webkit-transform:skewX(.390625deg) skewY(.390625deg);transform:skewX(.390625deg) skewY(.390625deg)}88.8%{-webkit-transform:skewX(-.1953125deg) skewY(-.1953125deg);transform:skewX(-.1953125deg) skewY(-.1953125deg)}}@keyframes jello{0%,11.1%,to{-webkit-transform:none;transform:none}22.2%{-webkit-transform:skewX(-12.5deg) skewY(-12.5deg);transform:skewX(-12.5deg) skewY(-12.5deg)}33.3%{-webkit-transform:skewX(6.25deg) skewY(6.25deg);transform:skewX(6.25deg) skewY(6.25deg)}44.4%{-webkit-transform:skewX(-3.125deg) skewY(-3.125deg);transform:skewX(-3.125deg) skewY(-3.125deg)}55.5%{-webkit-transform:skewX(1.5625deg) skewY(1.5625deg);transform:skewX(1.5625deg) skewY(1.5625deg)}66.6%{-webkit-transform:skewX(-.78125deg) skewY(-.78125deg);transform:skewX(-.78125deg) skewY(-.78125deg)}77.7%{-webkit-transform:skewX(.390625deg) skewY(.390625deg);transform:skewX(.390625deg) skewY(.390625deg)}88.8%{-webkit-transform:skewX(-.1953125deg) skewY(-.1953125deg);transform:skewX(-.1953125deg) skewY(-.1953125deg)}}.jello{-webkit-animation-name:jello;animation-name:jello;-webkit-transform-origin:center;transform-origin:center}@-webkit-keyframes bounceIn{0%,20%,40%,60%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}20%{-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}40%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}60%{opacity:1;-webkit-transform:scale3d(1.03,1.03,1.03);transform:scale3d(1.03,1.03,1.03)}80%{-webkit-transform:scale3d(.97,.97,.97);transform:scale3d(.97,.97,.97)}to{opacity:1;-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes bounceIn{0%,20%,40%,60%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}20%{-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}40%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}60%{opacity:1;-webkit-transform:scale3d(1.03,1.03,1.03);transform:scale3d(1.03,1.03,1.03)}80%{-webkit-transform:scale3d(.97,.97,.97);transform:scale3d(.97,.97,.97)}to{opacity:1;-webkit-transform:scaleX(1);transform:scaleX(1)}}.bounceIn{-webkit-animation-name:bounceIn;animation-name:bounceIn}@-webkit-keyframes bounceInDown{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,-3000px,0);transform:translate3d(0,-3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,25px,0);transform:translate3d(0,25px,0)}75%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}90%{-webkit-transform:translate3d(0,5px,0);transform:translate3d(0,5px,0)}to{-webkit-transform:none;transform:none}}@keyframes bounceInDown{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,-3000px,0);transform:translate3d(0,-3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,25px,0);transform:translate3d(0,25px,0)}75%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}90%{-webkit-transform:translate3d(0,5px,0);transform:translate3d(0,5px,0)}to{-webkit-transform:none;transform:none}}.bounceInDown{-webkit-animation-name:bounceInDown;animation-name:bounceInDown}@-webkit-keyframes bounceInLeft{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(-3000px,0,0);transform:translate3d(-3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(25px,0,0);transform:translate3d(25px,0,0)}75%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}90%{-webkit-transform:translate3d(5px,0,0);transform:translate3d(5px,0,0)}to{-webkit-transform:none;transform:none}}@keyframes bounceInLeft{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(-3000px,0,0);transform:translate3d(-3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(25px,0,0);transform:translate3d(25px,0,0)}75%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}90%{-webkit-transform:translate3d(5px,0,0);transform:translate3d(5px,0,0)}to{-webkit-transform:none;transform:none}}.bounceInLeft{-webkit-animation-name:bounceInLeft;animation-name:bounceInLeft}@-webkit-keyframes bounceInRight{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(3000px,0,0);transform:translate3d(3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(-25px,0,0);transform:translate3d(-25px,0,0)}75%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}90%{-webkit-transform:translate3d(-5px,0,0);transform:translate3d(-5px,0,0)}to{-webkit-transform:none;transform:none}}@keyframes bounceInRight{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(3000px,0,0);transform:translate3d(3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(-25px,0,0);transform:translate3d(-25px,0,0)}75%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}90%{-webkit-transform:translate3d(-5px,0,0);transform:translate3d(-5px,0,0)}to{-webkit-transform:none;transform:none}}.bounceInRight{-webkit-animation-name:bounceInRight;animation-name:bounceInRight}@-webkit-keyframes bounceInUp{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,3000px,0);transform:translate3d(0,3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}75%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}90%{-webkit-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0)}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes bounceInUp{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,3000px,0);transform:translate3d(0,3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}75%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}90%{-webkit-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0)}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.bounceInUp{-webkit-animation-name:bounceInUp;animation-name:bounceInUp}@-webkit-keyframes bounceOut{20%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}50%,55%{opacity:1;-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}to{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}}@keyframes bounceOut{20%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}50%,55%{opacity:1;-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}to{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}}.bounceOut{-webkit-animation-name:bounceOut;animation-name:bounceOut}@-webkit-keyframes bounceOutDown{20%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}to{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}@keyframes bounceOutDown{20%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}to{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}.bounceOutDown{-webkit-animation-name:bounceOutDown;animation-name:bounceOutDown}@-webkit-keyframes bounceOutLeft{20%{opacity:1;-webkit-transform:translate3d(20px,0,0);transform:translate3d(20px,0,0)}to{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}@keyframes bounceOutLeft{20%{opacity:1;-webkit-transform:translate3d(20px,0,0);transform:translate3d(20px,0,0)}to{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}.bounceOutLeft{-webkit-animation-name:bounceOutLeft;animation-name:bounceOutLeft}@-webkit-keyframes bounceOutRight{20%{opacity:1;-webkit-transform:translate3d(-20px,0,0);transform:translate3d(-20px,0,0)}to{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}@keyframes bounceOutRight{20%{opacity:1;-webkit-transform:translate3d(-20px,0,0);transform:translate3d(-20px,0,0)}to{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}.bounceOutRight{-webkit-animation-name:bounceOutRight;animation-name:bounceOutRight}@-webkit-keyframes bounceOutUp{20%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0)}to{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}@keyframes bounceOutUp{20%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0)}to{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}.bounceOutUp{-webkit-animation-name:bounceOutUp;animation-name:bounceOutUp}@-webkit-keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.fadeIn{-webkit-animation-name:fadeIn;animation-name:fadeIn}@-webkit-keyframes fadeInDown{0%{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInDown{0%{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInDown{-webkit-animation-name:fadeInDown;animation-name:fadeInDown}@-webkit-keyframes fadeInDownBig{0%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInDownBig{0%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInDownBig{-webkit-animation-name:fadeInDownBig;animation-name:fadeInDownBig}@-webkit-keyframes fadeInLeft{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInLeft{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInLeft{-webkit-animation-name:fadeInLeft;animation-name:fadeInLeft}@-webkit-keyframes fadeInLeftBig{0%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInLeftBig{0%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInLeftBig{-webkit-animation-name:fadeInLeftBig;animation-name:fadeInLeftBig}@-webkit-keyframes fadeInRight{0%{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInRight{0%{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInRight{-webkit-animation-name:fadeInRight;animation-name:fadeInRight}@-webkit-keyframes fadeInRightBig{0%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInRightBig{0%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInRightBig{-webkit-animation-name:fadeInRightBig;animation-name:fadeInRightBig}@-webkit-keyframes fadeInUp{0%{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInUp{0%{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInUp{-webkit-animation-name:fadeInUp;animation-name:fadeInUp}@-webkit-keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInUpBig{-webkit-animation-name:fadeInUpBig;animation-name:fadeInUpBig}@-webkit-keyframes fadeOut{0%{opacity:1}to{opacity:0}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.fadeOut{-webkit-animation-name:fadeOut;animation-name:fadeOut}@-webkit-keyframes fadeOutDown{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}@keyframes fadeOutDown{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}.fadeOutDown{-webkit-animation-name:fadeOutDown;animation-name:fadeOutDown}@-webkit-keyframes fadeOutDownBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}@keyframes fadeOutDownBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}.fadeOutDownBig{-webkit-animation-name:fadeOutDownBig;animation-name:fadeOutDownBig}@-webkit-keyframes fadeOutLeft{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}@keyframes fadeOutLeft{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.fadeOutLeft{-webkit-animation-name:fadeOutLeft;animation-name:fadeOutLeft}@-webkit-keyframes fadeOutLeftBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}@keyframes fadeOutLeftBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}.fadeOutLeftBig{-webkit-animation-name:fadeOutLeftBig;animation-name:fadeOutLeftBig}@-webkit-keyframes fadeOutRight{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}@keyframes fadeOutRight{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.fadeOutRight{-webkit-animation-name:fadeOutRight;animation-name:fadeOutRight}@-webkit-keyframes fadeOutRightBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}@keyframes fadeOutRightBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}.fadeOutRightBig{-webkit-animation-name:fadeOutRightBig;animation-name:fadeOutRightBig}@-webkit-keyframes fadeOutUp{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}@keyframes fadeOutUp{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}.fadeOutUp{-webkit-animation-name:fadeOutUp;animation-name:fadeOutUp}@-webkit-keyframes fadeOutUpBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}@keyframes fadeOutUpBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}.fadeOutUpBig{-webkit-animation-name:fadeOutUpBig;animation-name:fadeOutUpBig}@-webkit-keyframes flip{0%{-webkit-transform:perspective(400px) rotateY(-1turn);transform:perspective(400px) rotateY(-1turn)}0%,40%{-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}40%{-webkit-transform:perspective(400px) translateZ(150px) rotateY(-190deg);transform:perspective(400px) translateZ(150px) rotateY(-190deg)}50%{-webkit-transform:perspective(400px) translateZ(150px) rotateY(-170deg);transform:perspective(400px) translateZ(150px) rotateY(-170deg)}50%,80%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}80%{-webkit-transform:perspective(400px) scale3d(.95,.95,.95);transform:perspective(400px) scale3d(.95,.95,.95)}to{-webkit-transform:perspective(400px);transform:perspective(400px);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}}@keyframes flip{0%{-webkit-transform:perspective(400px) rotateY(-1turn);transform:perspective(400px) rotateY(-1turn)}0%,40%{-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}40%{-webkit-transform:perspective(400px) translateZ(150px) rotateY(-190deg);transform:perspective(400px) translateZ(150px) rotateY(-190deg)}50%{-webkit-transform:perspective(400px) translateZ(150px) rotateY(-170deg);transform:perspective(400px) translateZ(150px) rotateY(-170deg)}50%,80%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}80%{-webkit-transform:perspective(400px) scale3d(.95,.95,.95);transform:perspective(400px) scale3d(.95,.95,.95)}to{-webkit-transform:perspective(400px);transform:perspective(400px);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}}.animated.flip{-webkit-backface-visibility:visible;backface-visibility:visible;-webkit-animation-name:flip;animation-name:flip}@-webkit-keyframes flipInX{0%{-webkit-transform:perspective(400px) rotateX(90deg);transform:perspective(400px) rotateX(90deg);opacity:0}0%,40%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}40%{-webkit-transform:perspective(400px) rotateX(-20deg);transform:perspective(400px) rotateX(-20deg)}60%{-webkit-transform:perspective(400px) rotateX(10deg);transform:perspective(400px) rotateX(10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotateX(-5deg);transform:perspective(400px) rotateX(-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}@keyframes flipInX{0%{-webkit-transform:perspective(400px) rotateX(90deg);transform:perspective(400px) rotateX(90deg);opacity:0}0%,40%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}40%{-webkit-transform:perspective(400px) rotateX(-20deg);transform:perspective(400px) rotateX(-20deg)}60%{-webkit-transform:perspective(400px) rotateX(10deg);transform:perspective(400px) rotateX(10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotateX(-5deg);transform:perspective(400px) rotateX(-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}.flipInX{-webkit-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipInX;animation-name:flipInX}@-webkit-keyframes flipInY{0%{-webkit-transform:perspective(400px) rotateY(90deg);transform:perspective(400px) rotateY(90deg);opacity:0}0%,40%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}40%{-webkit-transform:perspective(400px) rotateY(-20deg);transform:perspective(400px) rotateY(-20deg)}60%{-webkit-transform:perspective(400px) rotateY(10deg);transform:perspective(400px) rotateY(10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotateY(-5deg);transform:perspective(400px) rotateY(-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}@keyframes flipInY{0%{-webkit-transform:perspective(400px) rotateY(90deg);transform:perspective(400px) rotateY(90deg);opacity:0}0%,40%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}40%{-webkit-transform:perspective(400px) rotateY(-20deg);transform:perspective(400px) rotateY(-20deg)}60%{-webkit-transform:perspective(400px) rotateY(10deg);transform:perspective(400px) rotateY(10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotateY(-5deg);transform:perspective(400px) rotateY(-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}.flipInY{-webkit-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipInY;animation-name:flipInY}@-webkit-keyframes flipOutX{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotateX(-20deg);transform:perspective(400px) rotateX(-20deg);opacity:1}to{-webkit-transform:perspective(400px) rotateX(90deg);transform:perspective(400px) rotateX(90deg);opacity:0}}@keyframes flipOutX{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotateX(-20deg);transform:perspective(400px) rotateX(-20deg);opacity:1}to{-webkit-transform:perspective(400px) rotateX(90deg);transform:perspective(400px) rotateX(90deg);opacity:0}}.flipOutX{-webkit-animation-name:flipOutX;animation-name:flipOutX;-webkit-backface-visibility:visible!important;backface-visibility:visible!important}@-webkit-keyframes flipOutY{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotateY(-15deg);transform:perspective(400px) rotateY(-15deg);opacity:1}to{-webkit-transform:perspective(400px) rotateY(90deg);transform:perspective(400px) rotateY(90deg);opacity:0}}@keyframes flipOutY{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotateY(-15deg);transform:perspective(400px) rotateY(-15deg);opacity:1}to{-webkit-transform:perspective(400px) rotateY(90deg);transform:perspective(400px) rotateY(90deg);opacity:0}}.flipOutY{-webkit-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipOutY;animation-name:flipOutY}@-webkit-keyframes lightSpeedIn{0%{-webkit-transform:translate3d(100%,0,0) skewX(-30deg);transform:translate3d(100%,0,0) skewX(-30deg);opacity:0}60%{-webkit-transform:skewX(20deg);transform:skewX(20deg)}60%,80%{opacity:1}80%{-webkit-transform:skewX(-5deg);transform:skewX(-5deg)}to{-webkit-transform:none;transform:none;opacity:1}}@keyframes lightSpeedIn{0%{-webkit-transform:translate3d(100%,0,0) skewX(-30deg);transform:translate3d(100%,0,0) skewX(-30deg);opacity:0}60%{-webkit-transform:skewX(20deg);transform:skewX(20deg)}60%,80%{opacity:1}80%{-webkit-transform:skewX(-5deg);transform:skewX(-5deg)}to{-webkit-transform:none;transform:none;opacity:1}}.lightSpeedIn{-webkit-animation-name:lightSpeedIn;animation-name:lightSpeedIn;-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}@-webkit-keyframes lightSpeedOut{0%{opacity:1}to{-webkit-transform:translate3d(100%,0,0) skewX(30deg);transform:translate3d(100%,0,0) skewX(30deg);opacity:0}}@keyframes lightSpeedOut{0%{opacity:1}to{-webkit-transform:translate3d(100%,0,0) skewX(30deg);transform:translate3d(100%,0,0) skewX(30deg);opacity:0}}.lightSpeedOut{-webkit-animation-name:lightSpeedOut;animation-name:lightSpeedOut;-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}@-webkit-keyframes rotateIn{0%{transform-origin:center;-webkit-transform:rotate(-200deg);transform:rotate(-200deg);opacity:0}0%,to{-webkit-transform-origin:center}to{transform-origin:center;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateIn{0%{transform-origin:center;-webkit-transform:rotate(-200deg);transform:rotate(-200deg);opacity:0}0%,to{-webkit-transform-origin:center}to{transform-origin:center;-webkit-transform:none;transform:none;opacity:1}}.rotateIn{-webkit-animation-name:rotateIn;animation-name:rotateIn}@-webkit-keyframes rotateInDownLeft{0%{transform-origin:left bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInDownLeft{0%{transform-origin:left bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInDownLeft{-webkit-animation-name:rotateInDownLeft;animation-name:rotateInDownLeft}@-webkit-keyframes rotateInDownRight{0%{transform-origin:right bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInDownRight{0%{transform-origin:right bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInDownRight{-webkit-animation-name:rotateInDownRight;animation-name:rotateInDownRight}@-webkit-keyframes rotateInUpLeft{0%{transform-origin:left bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInUpLeft{0%{transform-origin:left bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInUpLeft{-webkit-animation-name:rotateInUpLeft;animation-name:rotateInUpLeft}@-webkit-keyframes rotateInUpRight{0%{transform-origin:right bottom;-webkit-transform:rotate(-90deg);transform:rotate(-90deg);opacity:0}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInUpRight{0%{transform-origin:right bottom;-webkit-transform:rotate(-90deg);transform:rotate(-90deg);opacity:0}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInUpRight{-webkit-animation-name:rotateInUpRight;animation-name:rotateInUpRight}@-webkit-keyframes rotateOut{0%{transform-origin:center;opacity:1}0%,to{-webkit-transform-origin:center}to{transform-origin:center;-webkit-transform:rotate(200deg);transform:rotate(200deg);opacity:0}}@keyframes rotateOut{0%{transform-origin:center;opacity:1}0%,to{-webkit-transform-origin:center}to{transform-origin:center;-webkit-transform:rotate(200deg);transform:rotate(200deg);opacity:0}}.rotateOut{-webkit-animation-name:rotateOut;animation-name:rotateOut}@-webkit-keyframes rotateOutDownLeft{0%{transform-origin:left bottom;opacity:1}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}}@keyframes rotateOutDownLeft{0%{transform-origin:left bottom;opacity:1}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}}.rotateOutDownLeft{-webkit-animation-name:rotateOutDownLeft;animation-name:rotateOutDownLeft}@-webkit-keyframes rotateOutDownRight{0%{transform-origin:right bottom;opacity:1}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}}@keyframes rotateOutDownRight{0%{transform-origin:right bottom;opacity:1}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}}.rotateOutDownRight{-webkit-animation-name:rotateOutDownRight;animation-name:rotateOutDownRight}@-webkit-keyframes rotateOutUpLeft{0%{transform-origin:left bottom;opacity:1}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}}@keyframes rotateOutUpLeft{0%{transform-origin:left bottom;opacity:1}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}}.rotateOutUpLeft{-webkit-animation-name:rotateOutUpLeft;animation-name:rotateOutUpLeft}@-webkit-keyframes rotateOutUpRight{0%{transform-origin:right bottom;opacity:1}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:rotate(90deg);transform:rotate(90deg);opacity:0}}@keyframes rotateOutUpRight{0%{transform-origin:right bottom;opacity:1}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:rotate(90deg);transform:rotate(90deg);opacity:0}}.rotateOutUpRight{-webkit-animation-name:rotateOutUpRight;animation-name:rotateOutUpRight}@-webkit-keyframes hinge{0%{transform-origin:top left}0%,20%,60%{-webkit-transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}20%,60%{-webkit-transform:rotate(80deg);transform:rotate(80deg);transform-origin:top left}40%,80%{-webkit-transform:rotate(60deg);transform:rotate(60deg);-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;opacity:1}to{-webkit-transform:translate3d(0,700px,0);transform:translate3d(0,700px,0);opacity:0}}@keyframes hinge{0%{transform-origin:top left}0%,20%,60%{-webkit-transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}20%,60%{-webkit-transform:rotate(80deg);transform:rotate(80deg);transform-origin:top left}40%,80%{-webkit-transform:rotate(60deg);transform:rotate(60deg);-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;opacity:1}to{-webkit-transform:translate3d(0,700px,0);transform:translate3d(0,700px,0);opacity:0}}.hinge{-webkit-animation-name:hinge;animation-name:hinge}@-webkit-keyframes rollIn{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0) rotate(-120deg);transform:translate3d(-100%,0,0) rotate(-120deg)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes rollIn{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0) rotate(-120deg);transform:translate3d(-100%,0,0) rotate(-120deg)}to{opacity:1;-webkit-transform:none;transform:none}}.rollIn{-webkit-animation-name:rollIn;animation-name:rollIn}@-webkit-keyframes rollOut{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(100%,0,0) rotate(120deg);transform:translate3d(100%,0,0) rotate(120deg)}}@keyframes rollOut{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(100%,0,0) rotate(120deg);transform:translate3d(100%,0,0) rotate(120deg)}}.rollOut{-webkit-animation-name:rollOut;animation-name:rollOut}@-webkit-keyframes zoomIn{0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%{opacity:1}}@keyframes zoomIn{0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%{opacity:1}}.zoomIn{-webkit-animation-name:zoomIn;animation-name:zoomIn}@-webkit-keyframes zoomInDown{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomInDown{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInDown{-webkit-animation-name:zoomInDown;animation-name:zoomInDown}@-webkit-keyframes zoomInLeft{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(10px,0,0);transform:scale3d(.475,.475,.475) translate3d(10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomInLeft{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(10px,0,0);transform:scale3d(.475,.475,.475) translate3d(10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInLeft{-webkit-animation-name:zoomInLeft;animation-name:zoomInLeft}@-webkit-keyframes zoomInRight{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomInRight{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInRight{-webkit-animation-name:zoomInRight;animation-name:zoomInRight}@-webkit-keyframes zoomInUp{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomInUp{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInUp{-webkit-animation-name:zoomInUp;animation-name:zoomInUp}@-webkit-keyframes zoomOut{0%{opacity:1}50%{-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%,to{opacity:0}}@keyframes zoomOut{0%{opacity:1}50%{-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%,to{opacity:0}}.zoomOut{-webkit-animation-name:zoomOut;animation-name:zoomOut}@-webkit-keyframes zoomOutDown{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}to{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomOutDown{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}to{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomOutDown{-webkit-animation-name:zoomOutDown;animation-name:zoomOutDown}@-webkit-keyframes zoomOutLeft{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(42px,0,0);transform:scale3d(.475,.475,.475) translate3d(42px,0,0)}to{opacity:0;-webkit-transform:scale(.1) translate3d(-2000px,0,0);transform:scale(.1) translate3d(-2000px,0,0);-webkit-transform-origin:left center;transform-origin:left center}}@keyframes zoomOutLeft{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(42px,0,0);transform:scale3d(.475,.475,.475) translate3d(42px,0,0)}to{opacity:0;-webkit-transform:scale(.1) translate3d(-2000px,0,0);transform:scale(.1) translate3d(-2000px,0,0);-webkit-transform-origin:left center;transform-origin:left center}}.zoomOutLeft{-webkit-animation-name:zoomOutLeft;animation-name:zoomOutLeft}@-webkit-keyframes zoomOutRight{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-42px,0,0);transform:scale3d(.475,.475,.475) translate3d(-42px,0,0)}to{opacity:0;-webkit-transform:scale(.1) translate3d(2000px,0,0);transform:scale(.1) translate3d(2000px,0,0);-webkit-transform-origin:right center;transform-origin:right center}}@keyframes zoomOutRight{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-42px,0,0);transform:scale3d(.475,.475,.475) translate3d(-42px,0,0)}to{opacity:0;-webkit-transform:scale(.1) translate3d(2000px,0,0);transform:scale(.1) translate3d(2000px,0,0);-webkit-transform-origin:right center;transform-origin:right center}}.zoomOutRight{-webkit-animation-name:zoomOutRight;animation-name:zoomOutRight}@-webkit-keyframes zoomOutUp{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}to{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomOutUp{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}to{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomOutUp{-webkit-animation-name:zoomOutUp;animation-name:zoomOutUp}@-webkit-keyframes slideInDown{0%{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInDown{0%{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInDown{-webkit-animation-name:slideInDown;animation-name:slideInDown}@-webkit-keyframes slideInLeft{0%{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInLeft{0%{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInLeft{-webkit-animation-name:slideInLeft;animation-name:slideInLeft}@-webkit-keyframes slideInRight{0%{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInRight{0%{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInRight{-webkit-animation-name:slideInRight;animation-name:slideInRight}@-webkit-keyframes slideInUp{0%{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInUp{0%{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInUp{-webkit-animation-name:slideInUp;animation-name:slideInUp}@-webkit-keyframes slideOutDown{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}@keyframes slideOutDown{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}.slideOutDown{-webkit-animation-name:slideOutDown;animation-name:slideOutDown}@-webkit-keyframes slideOutLeft{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}@keyframes slideOutLeft{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.slideOutLeft{-webkit-animation-name:slideOutLeft;animation-name:slideOutLeft}@-webkit-keyframes slideOutRight{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}@keyframes slideOutRight{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.slideOutRight{-webkit-animation-name:slideOutRight;animation-name:slideOutRight}@-webkit-keyframes slideOutUp{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}@keyframes slideOutUp{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}.slideOutUp{-webkit-animation-name:slideOutUp;animation-name:slideOutUp} \ No newline at end of file